По работе я занимаюсь разработкой алгоритмов обработки изображений и в частности алгоритмами автоматического слежения за объектами на видео для специального применения. Недавно понадобилось сделать модель алгоритма, управляемую с удаленного компьютера для отладки логики работы в сложной системе. Раньше такая задача не стояла, т.к. все алгоритмы реализовывались в итоге на 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

Удачи во всех начинаниях!
Поделиться с друзьями
-->

Комментарии (12)


  1. ice2heart
    17.05.2016 15:23
    +5

    Трафика много будет при такой передаче, это как передавать каждый кадр ключевой. Не проще было бы использовать например fragmented mp4?


    1. siemdi
      17.05.2016 15:39

      Присоеденюсь, а почему ты выбрал именно JPEG? Явный костыль…


  1. RPG18
    17.05.2016 15:55

    Удивительно что не был сделан MJPEG over HTTP(Захват видео с сетевых камер, часть 1)


  1. Sirius_Voodoo
    17.05.2016 21:32
    -7

    Ого! Не был полдня и уже столько всего! Спасибо за Ваши комментарии. Учту в дальнейшем. Приятно, что такое обсуждение. Сразу видно, что РУССКИЙ форум — столько льется… )))


    1. batChar
      18.05.2016 10:18
      +5

      Забавно, никакой оскорбительной, нецензурной брани в вашу сторону сказано небыло. Конструктивные вопросы коментаторов вы проигнорировали. На что вы обиделись?

      Как уже заметили, использовать «комбайн» OpenCV для кодирования и декодирования затея не очень разумная. Да и TCP для видеопотока. Ну да ладно, что нового несет в себе ваша сатья?
      Ну и как вишенка сорцы в дропбоксе :)


  1. Shtucer
    17.05.2016 21:37
    +1

    ffmpeg получился?


  1. amelmac
    17.05.2016 21:37
    +1

    unsigned char *buf = new unsigned char[MAX_BUF_SIZE];

    Raw pointer лучше не использовать для обладания памятью (на случай exception'a), лучше было бы так:
    std::unique_ptr buf(new unsigned char[MAX_BUF_SIZE]);


  1. Vik_007
    17.05.2016 21:37

    Чего придрались, он же написал «отработать» технологию, нормально всё, кому надо тот допишет )


  1. batChar
    17.05.2016 21:37
    +1

    Статья шутка? :)


  1. ksergey01
    17.05.2016 21:37
    +4

    >> const int MAX_BUF_SIZE = 2073600;
    Для такого принято использовать size_t

    >>unsigned char *buf = new unsigned char[MAX_BUF_SIZE]; // Буфер для прима сообщений
    >>vector videoBuffer;
    Почему тогда для buf не использовать vector?

    >> 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);
    Зачем лишний раз копировать данные?

    Печально видеть такое в хабе C++


  1. a1ien_n3t
    17.05.2016 23:30
    +1

    Что я толькочто прочел…
    Зачем вам TCP при передачи «видео».
    Почем нельзя было взять ffmpeg или gstreamer и написать в 5 строчек и сжимать вочто угодно.
    Да и вобще можно без единой строки кода эту задачу решить. Темиже ffmpeg и gstremer вам в помошь.
    Картинка с буханка хлеба и троллейбус


    1. Sirius_Voodoo
      18.05.2016 00:33
      +1

      Конечно можно все сделать проще. Это же не боевая программа. Это программа для того, чтобы попробовать как это работает и работает ли вообще (упаковка, пересылка и распаковка). В некоторых задачах может пригодиться кому-нибудь.