Как стать автором
Обновить

Компьютерное зрение на Java? Элементарно вместе с OpenCV

Время на прочтение5 мин
Количество просмотров19K

Ас-саляму алейкум, братья!

В этом посте хочу показать вам как при помощи программного кода обрабатывать изображение и видео. Материал разобью на 4 шага, работать будем на Java. Для начала потребуется поставить библиотеку openCV, которую можно скачать на официальном сайте ' https://opencv.org/ ', более подробная инструкция в прикреплённом видео.

Шаг 1: [Обработка изображения]

GitHub

Давайте создадим всплывающее окно для отображения изображений.

        // Создаём окно для просмотра изображения.
        JFrame window = new JFrame("Window:");
        // Создаём контейнер для изображения.
        JLabel screen = new JLabel();
        // Установлимаем операцию закрытия по умолчанию.
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // Делаем окно отображения контента видимым.
        window.setVisible(true);

Подгрузим изображение прописав путь к нему в метод Imgcodecs.imread()

        // Загружаем изображение, храним его как объект матрицы.
        Mat img = Imgcodecs.imread("src\\a_Image\\butterfly.png");

Производим поток преобразований над изображением, после чего выводим его в окне созданном выше.

				/* Преобразуем изображение в матрицу байтов с целью
           получить массив байтов (пикселей). */
        MatOfByte buf = new MatOfByte();
        Imgcodecs.imencode(".png", img, buf);

        /* Преобразуем массив пикселей в ImageIcon,
           изображение которое будет отображатся. */
        ImageIcon ic = new ImageIcon(buf.toArray());

        // Привязываем изображение к контейнеру.
        screen.setIcon(ic);
        // Привязываем контейнер к окну отображения.
        window.getContentPane().add(screen);
        window.pack();

Поздравляю! Первый навык разблокирован, теперь мы можем отобразить .jpg в всплывающем окне при помощи джавы. Давайте на этом не будем останавливаться и отредактируем .jpg.

Редактировать .jpg будем при помощи матрицы трансформаций (kernel). Суть состоит в следующем, представим kernel как прямоугольную матрицу, каждый элемент это множитель, разные наборы множителей вызывают разные эфекты. Kernel проходит через все элементы .jpg, домнажает их на себя, тем самым изменяя .jpg. Более наглядно можно посмотреть на картинках.

        // Создаём ядро (kernel) для обработки изображения (матрица трансформации).
        Mat kernel = new Mat (20,20, CvType.CV_8UC1, new Scalar(1.0));

Теперь на основе kernel мы можем использовать фильтры, эфекты и искожения. Обработываемое фото можно менять по размеру, обрезать и сохранить.

				// Расширяем светлые и сужаем тёмные области.
        Imgproc.dilate(img, imgEmpty, kernel);
        // Расширяем тёмные и сужаем светлые области.
        Imgproc.erode(img, imgEmpty, kernel);
        // Конвертируем изображение в другое цветовое пространство.
        Imgproc.cvtColor(img, imgEmpty, Imgproc.COLOR_BGR2GRAY);
        // Размываем изображение (параметры ядра должны быть нечетными).
        Imgproc.GaussianBlur(img, imgEmpty, new Size (15,15), 0);
        // Выделяем границы изображения.
        Imgproc.Canny(img, imgEmpty, 2,2);
        // Изменяем размер изображения.
        Imgproc.resize(img, imgEmpty, new Size(200,200));
        // Обрезаем изображение.
        Mat imgCrop = img.colRange(400,600).rowRange(200,400);
        // Обрабатываем часть изображения.
        Imgproc.erode(img.colRange(400,600).rowRange(200,400), img.colRange(400,600).rowRange(200,400), kernel);
        // Сохраняем изображение.
        Imgcodecs.imwrite("src\\a_Image\\savedImg.png", img);

Шаг 2: [Обработка видео и видеопотока]

GitHub

Для работы с видео создадим объект класса VideoCapture(). Его функционал позволит отображать изображение с веб-камеры и воспроизводить видеозаписи. В качестве аргумента передаём на вход путь к видеофайлу или id веб-камеры (default: 0).

       /* Инициализируем видеопоток. Класс VideoCapture предназначен для захвата кадра
          из видео или видеопотока. */
        VideoCapture cap = new VideoCapture("src/b_Video/testVideo.mp4");

Обращаясь к объекту класса VideoCapture() в цикле поочередно извлекаем кадры, присваиваем матрице frame значение текущего кадра.

        // До тех пор пока поступает кадр из видеопотока выполняем следующие действия:
        while (cap.grab()) {
            // Извлекаем кадр из видеопотока.
            cap.read(frame);

Вывод всплывающего окна следует проводить в цикле, так же используя следующие функции можно нарисрвать линию, прямоугольник, круг и поместить на видео текст:

            // Инициализируем цвет (BGR)
            Scalar color = new Scalar(0,255,0);
            /* Наносим линию на изображение.
               Обрабатываемое изображение, начальная точкаб конечная точкаб цвет, толщина линии */
            Imgproc.line(frame, new Point(0,0), new Point(640,480), color,2);
            //
            Imgproc.rectangle(frame, new Rect(200, 100,240, 280), new Scalar(255, 0, 0),3);
            //
            Imgproc.circle(frame, new Point(320,240), 120, new Scalar(0, 0, 255), 5);
            //
            Imgproc.putText(frame, "SDBproduction", new Point(150,50),
                    4, 1.5, new Scalar(5, 5, 5));

Шаг 3: [Стриминг видео "UDP Socket"]

GitHub

Для передачи видеопотока по локальной сети мы выстроим следующую логику работы. Сервер всегда находится в режиме ожидания. Клиент может подключиться к серверу и начать передавать видео, в этом случае сервер переключится в режим приёма видео от клиента, как только клиент отключится сервер уйдет в режим ожидания.

Давайте пропишим код для клиента:

Создаём соединение со стороны клиента.

 			 	/* Инициализируем видеопоток. Класс VideoCapture предназначен для захвата кадра
           из видео или видеопотока. */
        VideoCapture cap = new VideoCapture(1);

        // Создаём сокет, точку соединения между двумя компьютерами.
        Socket socket = new Socket("192.168.1.159",1234);
        /* Создаём объект DataOutputStream который связываем с нашим сокетом.
           Данный объект позволяет отправлять примитивные типы данных. */
        DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());

Каждый кадр конвертируем в примитивный тип данных и отправляем на сервер.

       // До тех пор пока поступает кадр из видеопотока выполняем следующие действия:
        while (cap.grab()) {

            try {
                // Извлекаем кадр из видеопотока.
                cap.read(frame);

                /* Преобразуем изображение в матрицу байтов с целью
                   получить массив байтов (пикселей). */
                Imgcodecs.imencode(".png", frame, buf);
                imageData = buf.toArray();

                // Извлекаем размер изображения.
                int dataLength = imageData.length;

                // Отправляем размер изображения.
                dataOutputStream.writeInt(dataLength);
                /* Отправляем изображение в виде массива байтов (пикселей).
                   "последовательность байтов" */
                dataOutputStream.write(imageData);

Сервер со своей стороны состоит из двух циклов. В первом цикле ожидаем подключения к серверу от клиента. Если подключение установлено, то внутри этого цикла запускается второй, который будет принимать видеопоток от пользователя и выводить его.

   // Создаём серверный сокет, который будет слушать на заданом порту.
        ServerSocket serverSocket = new ServerSocket(1234);

        // В бесконечном цикле ожидаем подключение к серверу.
        while (true) {

            try {
                /* Ожидаем клиента, если придёт запрос от клиента создаём сокет (точку соединения)
                   чтобы подключится. */
                Socket socket = serverSocket.accept();

                /* Создаём объект DataInputStream который связываем с нашим сокетом.
                   Данный объект позволяет принимать примитивные типы данных. */
                DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());

                // В бесконечном цикле принимаем данные от клиента.
                while (true) {

                    // Принимаем размер изображения.
                    int dataLength = dataInputStream.readInt();

                    // Если изображение существует, то
                    if (dataLength > 0) {

                        /* принимаем изображение в виде массива байтов (пикселей).
                           "последовательность байтов" */
                        byte[] imageData = new byte [dataLength];
                        dataInputStream.readFully(imageData,0,dataLength);

                        /* Преобразуем массив пикселей в ImageIcon,
                           изображение которое будет отображатся. */
                        ic = new ImageIcon(imageData);

                        // Привязываем изображение к контейнеру.
                        screen.setIcon(ic);
                        // Привязываем контейнер к окну отображения.
                        window.setContentPane(screen);
                        window.pack();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

Шаг 4: [Детекция объектов " YOLOv4 "]

GitHub

Свёрточная нейронная сеть [скачать]
Чтобы разобраться с детекцией объектов на джава предлагаю вам посмотреть мой видеоурок.

Огромное спасибо за внимание!

Теги:
Хабы:
+3
Комментарии8

Публикации

Изменить настройки темы

Истории

Работа

Java разработчик
342 вакансии

Ближайшие события

PG Bootcamp 2024
Дата16 апреля
Время09:30 – 21:00
Место
МинскОнлайн
EvaConf 2024
Дата16 апреля
Время11:00 – 16:00
Место
МоскваОнлайн
Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн