Сжатие и передача потокового видео по TCP с помощью OpenCV

в 11:11, , рубрики: c++, image processing, opencv, обработка изображений, С++, Сетевые технологии

По работе я занимаюсь разработкой алгоритмов обработки изображений и в частности алгоритмами автоматического слежения за объектами на видео для специального применения. Недавно понадобилось сделать модель алгоритма, управляемую с удаленного компьютера для отладки логики работы в сложной системе. Раньше такая задача не стояла, т.к. все алгоритмы реализовывались в итоге на FPGA. Давно работаю с OpenCV и, потерев руки, подошел к написанию программы. Но энтузиазм быстро погас, когда столкнулся непосредственно с передачей видео по сети.

Задача заключалась в следующем:

1. Написать программу сервер, которая загружает видео из файла, сжимает в JPEG и передает по протоколу TCP программе клиенту.
2. Написать программу клиент, которая принимает видео по TCP, декодирует и отображает.

Описанные выше задачи являются элементарными и служат для того, чтобы «отработать» технологию. Кажется, что эта тема уже описана давно, но после некоторого времени в поиске готового ответа (куска кода) я понял, что не все очевидно. Поэтому здесь я привожу свой опыт в этом вопросе. Возможно кому-то мой опыт окажется полезным.

Программу разрабатывал в среде Visual Studio 2015 с использование библиотеки OpenCV версии 3.1. Опущу этапы создания проекта, подключения библиотек и написание кода, отвечающего за сетевое взаимодействие. В конце статьи дам ссылку на исходные коды проекта с полными коментариями в коде для быстрого понимания. Сосредоточимся на главной проблеме: как получить видео из файла, сжать его в JPEG с нужной степенью компрессии и передать по сети с дальнейшим декодированием и отображением на приемной стороне. Ниже небольшой кусок кода, показывающий как сжать кадр видео и передать его по сети (с учетом того, что соединение с приемной стороной установлено).

// Объявляем переменные
Mat srcMat;                                                                 // Данные исходного изображения
vector<uchar> imgBuf;                                                // Буфер для сжатого изображения
vector<int> quality_params = vector<int>(2);              // Вектор параметров качества сжатия
quality_params[0] = CV_IMWRITE_JPEG_QUALITY; // Кодек JPEG
quality_params[1] = 20;                                               // По умолчанию качество сжатия (95) 0-100

// Захватываем кадр видео
cd.frame = cvQueryFrame(cd.videocap);

// Получаем изображение в вектор
srcMat = cv::cvarrToMat(cd.frame);

// Кодируем изображение кодеком JPEG
imencode(".jpg", srcMat, imgBuf, quality_params);
// Отправляем данные
send(clientSocket, (const char*)&imgBuf[0], static_cast<int>(imgBuf.size()), 0);

Теперь опишем как распаковать изображение на приемной стороне. Ниже фрагмент кода программы клиента.

// Объявляем переменные
int iResult;                                                                                // Переменная на результат операций
const int MAX_BUF_SIZE = 2073600;                                     // Произвольно максимальный размер приемного буфера
unsigned char *buf = new unsigned char[MAX_BUF_SIZE];   // Буфер для прима сообщений
vector<uchar> videoBuffer;                                                     // Буфер данных изображения
Mat jpegimage;                                                                       // Вектор данных изображения
IplImage img;                                                                          // Изображение для вывода

// Ожидаем прихода данных
iResult = recv(connectSocket, (char *)&buf[0], MAX_BUF_SIZE, 0);
if (iResult > 0) {
        // Если пришли данные изображения, копируем их
        videoBuffer.resize(iResult);
        memcpy((char*)(&videoBuffer[0]), buf, iResult);
        // Декодируем данные
        jpegimage = imdecode(Mat(videoBuffer), CV_LOAD_IMAGE_COLOR);
        img = jpegimage;
        // Выводим изображение
        cvShowImage("Recieved Video", &img);
        // Ожидаем отклика управления (произвольно 5 ms)
        cvWaitKey(5);
}//if (iResult > 0)...

Описанные операции производятся для каждого кадра видео. Надеюсь этот небольшой пример поможет решить возникающие трудности сжатия и передачи потокового видео через TCP используя библиотеку OpenCV. Ниже по ссылке скриншот работы клиента и сервера.
www.dropbox.com/s/ikisl8rjxxd5d0f/%D0%91%D0%B5%D0%B7%D1%8B%D0%BC%D1%8F%D0%BD%D0%BD%D1%8B%D0%B9.png
Ввиду того, что коэффициент сжатия стоит 20 (высокое сжатие) на приемной стороне можно заметить значительные искажения картинки.

Исходные коды программ сервера и клиента с подробными комментариями вы можете посмотреть по ссылкам:

www.dropbox.com/s/3ucjsdes7khcr24/Server.cpp?dl=0
www.dropbox.com/s/14mat8bhlonz392/Client.cpp?dl=0

Удачи во всех начинаниях!

Автор: Sirius_Voodoo

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js