基于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的完整实现方法,实测通过,没有任何问题。
posted @ 2015-11-19 13:48  苍月代表我  阅读(13465)  评论(2编辑  收藏  举报