roboforum.ru

Технический форум по робототехнике.

MJPG-Streamer+OpenCV

MJPG-Streamer+OpenCV

Tsi329 » 03 май 2012, 08:29

Долго я обещал связать MJPG-Streamer и OpenCV и вот свершилось.

Код упрощен до безобразия и содержит в себе лишь нужный минимум. Это сделано специально, для того чтобы вы смогли встраивать его в свои приложения не таща за собой куски лишнего кода.

В создании кода мне безгранично помогли:
Andrey Smorodov и его сайт http://www.compvision.ru/forum/index.php
и OverQuantum http://overquantum.livejournal.com/.

Возможно, а как я уже сейчас понимаю, это зависит в основном от камеры, вы столкнетесь с ошибкой, которая изображена во вложении. На своей Logitech C270 у меня было также, а вот уже на Logitech C100 ошибок подобного рода не наблюдалось. Ниже решение этой проблемы. Ответ OverQuantum-а до такой степени профессиональный, что я приведу его полностью.

Это не лишние байты, это padding - дополнение битовой строки до полных байт единицами, которое необходимо перед маркером.
Ошибку вызывает padding из 8 бит, который в общем-то, излишен, стандартом не требуется, но не запрещён явно.
Поэтому обычные просмотрщики не ругаются, а вот (древняя) библиотека libjpeg - ругается.
OpenCV использует эту библиотеку.

Кстати, сообщение неправильное, лишний байт всегда 1 (всегда 2, если считать escaping FF->FF00).

ИМХО, есть 4 способа решить проблему:

1) Исправить код, генерирующий такие jpeg-и.
Насколько я понимаю, их даже не mjpg-streamer делает, а сама камера или её драйвер.

2) Исправить код OpenCV, загружающий jpeg-и.
Насколько я вижу в исходниках последней версии под Unix, нужный файл
\OpenCV-2.3.1a.tar.bz2\OpenCV-2.3.1\3rdparty\libjpeg\jdmarker.c

Функция next_marker (j_decompress_ptr cinfo), код:

if (cinfo->marker->discarded_bytes != 0) {
WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c);
cinfo->marker->discarded_bytes = 0;
}

Я бы закоментил строку WARNMS2(..)

3) Исправлять jpeg-и на лету путём выбрасывания лишнего padding-а
Насколько я вижу, padding ставится всегда, и вот когда он не нужен (битовая строка и так закончилась на границе байт) - ставится полный байт.
Соотв. нужно заменить все последовательности FF 00 FF Di на FF Di (i = 0 ..7)

Однако это может повредить изображение - в случае если битовая строка, кодирующая изображения заканчивалась на несколько бит равных 1 и padding тоже из бит 1, в этом случае мы получим такую же пару FF 00 перед маркером, но это уже будет полностью корректный padding, который нельзя отрезать.
Возникнуть это может довольно редко - в MCU блоке должен быть не нулевой последний 63-й коэффициент, его значение должно заканчиваться на несколько бит 1, они должны залезть через границу байт и попасть аккурат перед reset-маркером.
Тем не менее, исключить это нельзя и нужно потестировать на большом объёме кадров, возникает ли повреждение.

4) Исправлять jpeg-и на лету путём полного перекодирования.
Например тем же libjpeg можно игнорировать ошибку, распаковать изображение из файла в DCT коэффициенты и запаковать обратно в jpeg-файл.


Я проверил второй и третий способы. Как бы не был красив третий, он не даёт, к сожалению, стабильного результата по описанным выше причинам.

Посему просто комментируете WARNMS2(cinfo, JWRN_EXTRANEOUS_DATA, cinfo->marker->discarded_bytes, c);
и пересобираете весь OpenCV. Сделать это не сложно. Хорошо описано здесь http://robocraft.ru/blog/computervision/700.html

Пару слов про сам код. К сожалению, он не выдает стабильное FPS. С чем это связано не знаю.
Было бы здорово, если кто-то переписал его под DirectShow для приема реального потока, а не так как реализовал это я.

Если у вас возникнут вопросы, смело обращайтесь. Я всегда к вашим услугам.

Код: Выделить всёРазвернуть
#pragma comment(lib,"Wininet.lib")

#include "stdafx.h"
#include <cv.h>
#include <highgui.h>
#include <stdlib.h>
#include <stdio.h>
#include <Windows.h>
#include "WinInet.h"
#include <string.h>
#include "cxcore.h"

#define strncasecmp _strnicmp

struct tmjpeg
{
   unsigned char *pbuff;
   DWORD ibuffsize;
   unsigned char *pmess;
   DWORD imesssize;
   IplImage* image;
   int width;
   int height;
   HINTERNET hSession;
   HINTERNET hConnect;
   HINTERNET hRequest;
   char szUrl[MAX_PATH];
};
tmjpeg mjpeg;

static int PeekData(void)
{
   int ilen;
   
   // узнаём размер следующего пакета
   InternetQueryDataAvailable(mjpeg.hRequest,&mjpeg.imesssize,0,0);
   // выходим если размер нулевой
   if(mjpeg.imesssize<=0)
      return -1;

   // зарезервируем массив байт для очередной порции
   mjpeg.pmess=new unsigned char[mjpeg.imesssize];
   if(mjpeg.pmess==NULL)
      return -1;
   // эта переменка будет получать число считанных байт
   DWORD nbr=0;
   // Если считывание удается
   if (!InternetReadFile(mjpeg.hRequest,mjpeg.pmess,mjpeg.imesssize,&nbr))
      return -1;
   //Формируем временный буфер размером старый буфер+ новый пакет
   ilen=mjpeg.ibuffsize+mjpeg.imesssize;
   unsigned char *ps= new unsigned char[ilen];
   if(ps==NULL)
      return -1;
   //Если это не первичный запуск
   if(mjpeg.ibuffsize>0)
   {
      // Копируем старый буфер
      memcpy(ps,mjpeg.pbuff,mjpeg.ibuffsize);
      // Освобождаем память старого буфера
      free(mjpeg.pbuff);
   }
   // Копируем новый пакет
   memcpy(ps+mjpeg.ibuffsize,mjpeg.pmess,mjpeg.imesssize);
   // Освобождаем память пакета
   free(mjpeg.pmess);

   //Переназначаем буфер
   mjpeg.pbuff=ps;
   // Устанавливаем новый размер буфера
   mjpeg.ibuffsize=ilen;

   return mjpeg.imesssize;
}


bool CreateMJPEGCameraCapture(char *szUrl, int width, int heigth)
{
   mjpeg.hSession = InternetOpen(TEXT("MJPG listener"),INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
   mjpeg.width=width;
   mjpeg.height=heigth;
   strcpy(mjpeg.szUrl,szUrl);
   if(mjpeg.hSession)
      return true;
   else
      return false;

}

IplImage* QueryMJPEGFrame(void)
{
   mjpeg.ibuffsize=0;
   mjpeg.imesssize=0;
   mjpeg.pmess=NULL;


   mjpeg.hRequest = InternetOpenUrlA(mjpeg.hSession, (LPCSTR)mjpeg.szUrl, NULL, 0, 0, 0);
   if(mjpeg.hRequest)
   {
      DWORD Size=0;
      // Выясняем по сколько портал нам будет высылать байт в очередной порции
      InternetQueryDataAvailable(mjpeg.hRequest,&Size,0,0);
      
      while(PeekData()>0);
      //printf("Bufflen=%d\n\n",mjpeg.ibuffsize);

      CvMat* mat = cvCreateMatHeader(1,mjpeg.ibuffsize, CV_8UC1 );
      cvCreateData( mat );
      cvSetData(mat,mjpeg.pbuff,mjpeg.ibuffsize);

      // декодируем массив в картинку
      if(mjpeg.image!=NULL)
         cvReleaseImage(& mjpeg.image);

      mjpeg.image = cvDecodeImage(mat);//,CV_LOAD_IMAGE_COLOR);
      
      cvReleaseData(mat);

      if(mjpeg.pbuff)
         free(mjpeg.pbuff);
      mjpeg.pbuff=NULL;
   

      InternetCloseHandle(mjpeg.hRequest);
   } else
      return NULL;


   return mjpeg.image;
}

bool ReleaseMJPEGCapture(void)
{
   if(mjpeg.hSession)
   {
      InternetCloseHandle(mjpeg.hSession);
   }

   if(mjpeg.image!=NULL)
        cvReleaseImage(&mjpeg.image);
   if(mjpeg.pbuff!=NULL)
      free(mjpeg.pbuff);

   return true;
}

int _tmain(int argc, _TCHAR* argv[])
{

   IplImage* frame;

   if(!CreateMJPEGCameraCapture("http://192.168.1.120:8080/?action=snapshot",640,480))
   {
      return 0;
   }
   cvNamedWindow("original",CV_WINDOW_AUTOSIZE);
        while(true)
   {
            // получаем кадр
         frame=QueryMJPEGFrame();
         if(frame==NULL)
         {
            printf("Error: No frame\n");
            break;
         }
            // показываем
            cvShowImage("original",frame);
            char c = cvWaitKey(33);
            if (c == 27)
         { // нажата ESC
                    break;
            }
   }

   
   // удаляем окно
   cvDestroyWindow("original");
   // освобождаем ресурсы
   ReleaseMJPEGCapture();
   return 0;
}

Вложения
int_2.jpg

Re: MJPG-Streamer+OpenCV

elmot » 03 май 2012, 08:33

Было бы нелишним уточнить, что же делает этот код и какая часть OpenCV задействована и зачем.

Re: MJPG-Streamer+OpenCV

Alex21 » 03 май 2012, 12:36

Не без проблем, но удалось запустить программу. Только она выдаёт примерно 1fps, если не меньше. А ещё память куда-то утекает. Собирал в Qt Creator. Проблем с камерой logitech с300 нету.

Re: MJPG-Streamer+OpenCV

Tsi329 » 03 май 2012, 12:42

elmot писал(а):Было бы нелишним уточнить, что же делает этот код и какая часть OpenCV задействована и зачем.

Код просто выводит картинку на экран передаваемую по MJPG-Streamer.

Т.е. стандартный код вывода картинки
Код: Выделить всёРазвернуть
int main(int argc, char* argv[])
{
        IplImage* frame=0;
        CvCapture* capture = cvCreateCameraCapture(CV_CAP_ANY);
        cvNamedWindow("capture", CV_WINDOW_AUTOSIZE);
        while(true){
                frame = cvQueryFrame( capture );
                cvShowImage("capture", frame);
                char c = cvWaitKey(33);
                if (c == 27) break;
        }
        cvReleaseCapture( &capture );
        cvDestroyWindow("capture");
        return 0;
}

заменяем на:
Код: Выделить всёРазвернуть
int _tmain(int argc, _TCHAR* argv[])
{
   IplImage* frame;
   CreateMJPEGCameraCapture("http://192.168.1.120:8080/?action=snapshot",640,480);
   cvNamedWindow("original",CV_WINDOW_AUTOSIZE);
   while(true)
   {
         frame=QueryMJPEGFrame();
         cvShowImage("original",frame);
         char c = cvWaitKey(33);
         if (c == 27) break;
   }
   cvDestroyWindow("original");
   ReleaseMJPEGCapture();
   return 0;
}
Последний раз редактировалось Tsi329 03 май 2012, 19:02, всего редактировалось 1 раз.

Re: MJPG-Streamer+OpenCV

Alex21 » 03 май 2012, 16:16

Alex21 писал(а):Не без проблем, но удалось запустить программу. Только она выдаёт примерно 1fps, если не меньше. А ещё память куда-то утекает. Собирал в Qt Creator. Проблем с камерой logitech с300 нету.

Непонятно из-за чего, но fps пришли в норму. Ошибки как на скриншоте тоже появляются, только у меня они такого вида: "Corrupt JPEG data: premature end of data segment". А куда утекает память, я не разобрался.

Re: MJPG-Streamer+OpenCV

Tsi329 » 03 май 2012, 19:01

Спасибо за feedback.
Alex21 писал(а):Непонятно из-за чего, но fps пришли в норму.

У меня такое то же пару раз было. Причины сего объяснить не могу. Надо попробовать переделать под DirectShow.

Alex21 писал(а):Ошибки как на скриншоте тоже появляются, только у меня они такого вида: "Corrupt JPEG data: premature end of data segment".

Ну тут решение примерно такое же, только комментить другой код:
Код: Выделить всёРазвернуть
      if (! cinfo->entropy->insufficient_data) {
   //WARNMS(cinfo, JWRN_HIT_MARKER);
   cinfo->entropy->insufficient_data = TRUE;
      }

в файле C:\opencv\3rdparty\libjpeg\jdhuff.c
Удивительно, как дыряво написан либо драйвер под камеры для линукса, либо сам MJPEG-Streamer. Я грешу на последний.
Alex21 писал(а):А куда утекает память, я не разобрался.

Как ты видишь, что утекает память? Надо мне еще поколдовать над кодом. Так немного неочевидное перераспределение памяти. Может там где.

Re: MJPG-Streamer+OpenCV

=DeaD= » 03 май 2012, 19:14

Не совсем понял, для использования решения требуется пересборка OpenCV?
А нельзя сделать библиотеку, которая будет внешней к OpenCV, чтобы её кинул в каталог и вперед, без пересборки?

Это было весьма удобно.

Re: MJPG-Streamer+OpenCV

Alex21 » 03 май 2012, 20:57

Tsi329 писал(а):Как ты видишь, что утекает память?

Запускаю программу, открываю диспетчер задач и наблюдаю за процессом роста памяти у процесса.

Re: MJPG-Streamer+OpenCV

=DeaD= » 03 май 2012, 21:22

А случаем перед frame= не надо почистить старый объект frame?

Re: MJPG-Streamer+OpenCV

Tsi329 » 04 май 2012, 09:08

=DeaD=, Возможно, но память всё равно утекает

Re: MJPG-Streamer+OpenCV

Alex21 » 24 июл 2012, 19:26

Удалось наладить обработку видео?

Re: MJPG-Streamer+OpenCV

noonv » 24 июл 2012, 21:44

Последний раз редактировалось noonv 17 дек 2012, 21:14, всего редактировалось 1 раз.

Re: MJPG-Streamer+OpenCV

madrugado » 17 дек 2012, 13:39

Спасибо большое за вашу работу!

Весьма помогла. У меня аналогичная ситуация - MJPG-Streamer+OpenCV, правда OpenCV 2.3.0, но в данном случае это не играет. Использую камеру C920, на ней не замечено проблемы с лишним паддингом.

Re: MJPG-Streamer+OpenCV

vicul » 23 апр 2014, 15:57

ТС. Это же надо так над памятью издеваться. Кто тебя учил выделять память через new, а освобождать ее через free? Вот поэтому мозги у проги текут.
Последний раз редактировалось Myp 23 апр 2014, 23:28, всего редактировалось 1 раз.
Причина: чуточку культурней надо быть.


Rambler\'s Top100 Mail.ru counter