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