【Webcam设计】利用底层V4L2+OPENCV进行图像处理以及移植策略
1、背景利用OPENCV的CvCapture *cvCaptureFromCAM( int index )来实现,屏蔽掉V4L2底层的繁琐操作,使用opencv调用相机,发现不同设备上采集的图像有很大的区别,如果保持移植的一致性,应该需要考虑v4l2配置相机。经过对这些库的了解,才发现,最为关键的几个库为ffmpeg以及libv4l,libavcodec。特别是libv4l是直接用来捕获摄像头的库
https://www.cnblogs.com/lifan3a/articles/4977421.html
1、背景
利用OPENCV的CvCapture *cvCaptureFromCAM( int index )来实现,屏蔽掉V4L2底层的繁琐操作,使用opencv调用相机,发现不同设备上采集的图像有很大的区别,如果保持移植的一致性,应该需要考虑v4l2配置相机。
经过对这些库的了解,才发现,最为关键的几个库为ffmpeg以及libv4l,libavcodec。特别是libv4l是直接用来捕获摄像头的库,才能够将cvCaptureFromCAM于真正的设备连接,从而获取视频。
其中,libv4l.so,没有ARM的版本,如果能够在ARM平台下找到libv4l.so的库文件,选上with v4l,我在交叉编译器的目录下没ARM版本的libv4l.so的库
2、过程
通过V4L2,从摄像头的像素格式,视频流格式,包括视频的输出格式等作设置和定制,包括视频帧显示的大小等都可以进行修改。
底层的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
至少我觉得很好用,这种视频开发思路,虽然有点费周折,但是不用移植任何第三方库
CV下的cvCaptureFromCAM ,但是V4L2的底层函数,则可以完全地按照摄像头的像素来设置窗口大小
3、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中,怎么办呢,两种思路:
- 如果是彩色图像,则很简单一句话:
IplImage *img = cvLoadImage("lena.jpg", 1);
QImage qImage(img->imageData, img->width, img->height, img->widthStep, QImage::Format_RGB888);
- 如果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)的方式。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐

所有评论(0)