基於V4L2+OPENCV的攝像頭采集與圖像處理方案


本帖內容摘要:將開源進行到底——如何在ZED內利用底層V4L2+OPENCV進行圖像處理以及移植策略。

百轉千回,終於到了最后的總結時間,每一個帖子都是幾天幾周反復探索的結果,將一些教訓和彎路都分享一下,能給利用zed開發攝像頭和視頻的同學一點指導。

1. ZED開發攝像頭的幾種思路以及選擇。。。

想在ZED上開發攝像頭,其實和其它ARM系列上開發攝像頭,是異曲同工的,都有兩種基本思路:

第一,利用OPENCV的CvCapture*cvCaptureFromCAM( int index )來實現,屏蔽掉V4L2底層的繁瑣操作。

第二,通過V4L2,從攝像頭的像素格式,視頻流格式,包括視頻的輸出格式等作設置和定制,包括視頻幀顯示的大小等都可以進行修改。

那么很顯然,包括我在內,大多數新手一開始肯定希望如何利用第一種方案,簡單嘛,首先在PC下的虛擬機UBuntu 12.04內實現攝像頭讀取,然后存儲為一幅照片,驗證上面的函數是否可行。一開始必然是不行的,那么通過下面的辦法,安裝一些第三方庫,便可以讓程序走通,但是。。。一會兒就知道,這條路是死路

apt-get install ffmpeg libavcodec-dev libavcodec52 libavformat52 libavformat-dev

apt-get install libgstreamer0.10-0-dbg libgstreamer0.10-0 libgstreamer0.10-dev 

 apt-get install libxine1-ffmpeg libxine-dev libxine1-bin 

 apt-get install libunicap2 libunicap2-dev 

 apt-get install libdc1394-22-dev libdc1394-22 libdc1394-utils 

 apt-get install swig 

 apt-get install libv4l-0 libv4l-dev 

 apt-get install python-numpy 

 apt-get install libpython2.6 python-dev python2.6-dev #You must install this for python support

裝好之后重新編譯攝像頭程序,還是不行。
需要重新編譯Opencv於是cmake,make ,sudo make install這樣以后,攝像頭就順利打開了。

然而,問題是,本機上可以了,那么這些庫里哪些是真正讓攝像頭打開的呢,經過對這些庫的了解,才發現,最為關鍵的幾個庫為ffmpeg以及libv4l,libavcodec。特別是libv4l是直接用來捕獲攝像頭的庫,只有有了這些,在opencv重新編譯后,才能夠將cvCaptureFromCAM於真正的設備連接,從而獲取視頻。

本機PC上確實可以,但是我們最終目的是在嵌入式ARM內實現,這又該怎么辦,雖然論壇里也有一些帖子中說到關於v4l2/v4l的配置問題,經過很多帖子的閱讀,而且經過很多實驗,發現很不容易,最大的問題就是libv4l.so,沒有ARM的版本,如果能夠在ARM平台下找到libv4l.so的庫文件,選上with v4l,在編譯OPENCV時就是走不通,因為首先在交叉編譯器的目錄下就得有這個ARM版本的libv4l.so的庫啊,但是實際是沒有,而且網上也沒有人提供這項資源。

所以,這種方案走不通,pass了。

那么,如何在ZED內打開攝像頭呢,OK,底層的V4L2真的那么恐怖嗎,其實非也,底層V4L2非常容易理解,而且也不難,看我細細道來。

V4L是Linux環境下開發視頻采集設備驅動程序的一套規范(API),它為驅動程序的編寫提供統一的接口,並將所有的視頻采集設備的驅動程序都納入其的管理之中。V4L不僅給驅動程序編寫者帶來極大的方便,同時也方便了應用程序的編寫和移植。V4L2是V4L的升級版,我們使用的OOB是3.3的內核,不再支持V4L,所以是以v4l2作為底層的攝像頭視頻開發。

video4linux2(V4L2)是Linux內核中關於視頻設備的內核驅動,它為Linux中視頻設備訪問提供了通用接口,在Linux系統中,V4L2驅動的Video設備節點路徑通常/dev/video/中的videoX。

V4L2的主要作用使程序有發現設備和操作設備的能力.它主要是用一系列的回調函數來實現這些功能,像設置攝像頭的頻率、幀頻、視頻壓縮格式和圖像參數等等。此框架只能運行在Linux操作系統中,而且是針對uvc的免驅usb設備的編程框架。

V4L2打開視頻的流程可以用以下的軟件框圖表示:

         就分這么幾步,具體的操作,我也不敢妄自吹噓,確實也是學超群天晴的博客而來。    http://www.cnblogs.com/surpassal/archive/2012/12/22/zed_webcam_lab2.html

         至少我覺得很好用,這種視頻開發思路,雖然有點費周折,但是不用移植任何第三方庫,就可以在ZED內打開攝像頭,而且最為關鍵的,CV下的cvCaptureFromCAM,在ZED內最大是640*480,但是V4L2的底層函數,則可以完全地按照攝像頭的像素來設置窗口大小,像我的Logitech C270,像素300萬,我甚至可以放大到1920*1080來觀察攝像頭視頻,這就是V4L2的強大之處。具體的開發過程大家可以參照上面的鏈接,還是大神寫的好啊,慚愧。

2. V4L2+OPENCV,如何在V4L2讀取攝像頭視頻的基礎上,利用OPENCV進行處理。。。

         OpenCV的移植並不復雜,按照教材上一步步來,基本的函數都可以直接拿來使用,當然前提是指定Opencv庫文件的路徑,然而,Opencv進行處理,是基於IplImage數據類型的,IplImage是CV內的struct類型的圖像變量。

        而V4L2是通過malloc申請動態內存,並將圖像連續存放在uchar *的指針所指向的內存內部的,如何轉換呢?

        由於我這里很早就開始用QT進行程序開發,因此,我就毫不猶豫的,想到用QT來做中介,

        沒錯,具體怎么實現呢,OK,uchar *的變量,可以通過下列函數,直接轉化為QImage,沒有任何問題

        QImage frame;

         frame->loadFromData((uchar *)pp,window_width * window_hight * 3 * sizeof(char));

         這就將轉換為了QImage的圖像對象,可以任意在QT內貼圖啊,存儲啊等等。

         那么然后,QImage如何和IplImage轉換呢,最基本的轉換函數為,當然這是網上大家通用的一個:

void labeltest::cvxCopyQImage(const QImage *qImage, IplImage *pIplImage) //QImage to Iplimage

{

   int x, y;

   for(x = 0; x < pIplImage->width; ++x)

   {

      for(y = 0; y < pIplImage->height; ++y)

      {

         QRgb rgb = qImage->pixel(x, y);

         cvSet2D(pIplImage, y, x, CV_RGB(qRed(rgb), qGreen(rgb), qBlue(rgb)));

      }

   }

}

        Ok,在經過OPENCV一陣處理后,特別是很多CV函數,都需要首先對圖像進行灰度化,即BGR2GRAY,之后圖像的通道數都變為1,圖像的數據量也發生變化,處理后的數據想要顯示在QT中,怎么辦呢,兩種思路:

        1. 如果是彩色圖像,則很簡單一句話:

        IplImage *img = cvLoadImage("lena.jpg", 1);

   QImage qImage(img->imageData, img->width, img->height, img->widthStep, QImage::Format_RGB888);

        2.  如果img是灰度圖像,你會發現上面的辦法。。。不行,哪怕你更改最后的Format格式依然不行,我找到一個函數,很牛逼,估計是國外的有人去編寫的,可以將任意格式的IplImage轉換為QImage的RGB32:
void labeltest::cvxCopyQImage(const QImage *qImage, IplImage *pIplImage) //Iplimage to QImage
{
   int x, y;
   for(x = 0; x < pIplImage->width; ++x)
   {
      for(y = 0; y < pIplImage->height; ++y)
      {
         QRgb rgb = qImage->pixel(x, y);
         cvSet2D(pIplImage, y, x, CV_RGB(qRed(rgb), qGreen(rgb), qBlue(rgb)));
      }
 
   }
 
}
 
void labeltest::Ipl2QImageRGB32(IplImage* iplImage,QImage* qImage){
     unsigned char* ptrQImage=qImage->bits();
     switch(iplImage->depth){
           case IPL_DEPTH_8U:
                if(iplImage->nChannels==1){
                    for(int row=0;row<iplImage->height;row++){
                        unsigned char* ptr=(unsigned char*)(iplImage->imageData+row*iplImage->widthStep);
                        for(int col=0;col<iplImage->width;col++){
                            *(ptrQImage)=*(ptr+col);
                            *(ptrQImage+1)=*(ptr+col);
                            *(ptrQImage+2)=*(ptr+col);
                            *(ptrQImage+3)=0;
                            ptrQImage+=4;
                        }
                    }
                }
                else if(iplImage->nChannels==3){
                    for(int row=0;row<iplImage->height;row++){
                        unsigned char* ptr=(unsigned char*)(iplImage->imageData+row*iplImage->widthStep);
                        for(int col=0;col<iplImage->width;col++){
                            *(ptrQImage)=*(ptr+col*3);
                            *(ptrQImage+1)=*(ptr+col*3+1);
                            *(ptrQImage+2)=*(ptr+col*3+2);
                            *(ptrQImage+3)=0;
                            ptrQImage+=4;
                        }
                    }
                }
                break;
           case IPL_DEPTH_32F:
                if(iplImage->nChannels==1){
                    for(int row=0;row<iplImage->height;row++){
                        float* ptr=(float*)(iplImage->imageData+row*iplImage->widthStep);
                        for(int col=0;col<iplImage->width;col++){
                            *(ptrQImage)=(unsigned char)(*(ptr+col)*255.0);
                            *(ptrQImage+1)=(unsigned char)(*(ptr+col)*255.0);
                            *(ptrQImage+2)=(unsigned char)(*(ptr+col)*255.0);
                            *(ptrQImage+3)=0;
                            ptrQImage+=4;
                        }
                    }
                }
                else if(iplImage->nChannels==3){
                    for(int row=0;row<iplImage->height;row++){
                        float* ptr=(float*)(iplImage->imageData+row*iplImage->widthStep);
                        for(int col=0;col<iplImage->width;col++){
                            *(ptrQImage)=(unsigned char)(*(ptr+col*3)*255.0);
                            *(ptrQImage+1)=(unsigned char)(*(ptr+col*3+1)*255.0);
                            *(ptrQImage+2)=(unsigned char)(*(ptr+col*3+2)*255.0);
                            *(ptrQImage+3)=0;
                            ptrQImage+=4;
                        }
                    }
                }
                break;
           case IPL_DEPTH_64F:
                if(iplImage->nChannels==1){
                    for(int row=0;row<iplImage->height;row++){
                        double* ptr=(double*)(iplImage->imageData+row*iplImage->widthStep);
                        for(int col=0;col<iplImage->width;col++){
                            *(ptrQImage)=(unsigned char)(*(ptr+col)*255.0);
                            *(ptrQImage+1)=(unsigned char)(*(ptr+col)*255.0);
                            *(ptrQImage+2)=(unsigned char)(*(ptr+col)*255.0);
                            *(ptrQImage+3)=0;
                            ptrQImage+=4;
                        }
                    }
                }
                else if(iplImage->nChannels==3){
                    for(int row=0;row<iplImage->height;row++){
                        double* ptr=(double*)(iplImage->imageData+row*iplImage->widthStep);
                        for(int col=0;col<iplImage->width;col++){
                            *(ptrQImage)=(unsigned char)(*(ptr+col*3)*255.0);
                            *(ptrQImage+1)=(unsigned char)(*(ptr+col*3+1)*255.0);
                            *(ptrQImage+2)=(unsigned char)(*(ptr+col*3+2)*255.0);
                            *(ptrQImage+3)=0;
                            ptrQImage+=4;
                        }
                    }
                }
                break;
           default:
                printf("The type of the IplImage should be IPL_DEPTH_8U,IPL_DEPTH_32F or IPL_DEPTH_64F");
     }
}
 
如此,所有經過OPENCV處理后的IplImage都能同意轉換為RGB32格式的QImage顯示出來,實際測試非常好用,為了證實,將我們課題的一個QT界面來演示下,右邊就是一模一樣大小的經過Canny邊緣化處理后的圖像的顯示。
采用的就是uchar *-> QImage  QImage->IplImage  IplImage->QImage(RGB32)的方式。
                                   
 
這個帖子跟大家講述了ZED板子通過V4L2+OPENCV的完整實現方法,實測通過,沒有任何問題。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM