转载基于ZedBoard的Webcam设计一USB摄像头V4L2接口的图片采集.docx
- 文档编号:6036842
- 上传时间:2023-01-03
- 格式:DOCX
- 页数:34
- 大小:28.09KB
转载基于ZedBoard的Webcam设计一USB摄像头V4L2接口的图片采集.docx
《转载基于ZedBoard的Webcam设计一USB摄像头V4L2接口的图片采集.docx》由会员分享,可在线阅读,更多相关《转载基于ZedBoard的Webcam设计一USB摄像头V4L2接口的图片采集.docx(34页珍藏版)》请在冰豆网上搜索。
转载基于ZedBoard的Webcam设计一USB摄像头V4L2接口的图片采集
(转载)基于ZedBoard的Webcam设计
(一):
USB摄像头(V4L2接口)的图片采集
一直想把USB摄像头接到Zedboard上,搭建一个简易视频监控系统方便后续做视频处理。
Xilinx官方给出了一个Webcam摄像头监控的例子,跑的是linaro,不知道是我的SD卡问题还是摄像头的问题,播放视频的时候总是会很卡,而且突然系统就死掉了。
还是很喜欢自己动手,能学到新东西。
Digilent官方给的OOB设计,那个精简的linux足够做简单的linux开发了,而且OOB设计中USB驱动和V4L(VideoforLinux)都提供好了。
这几天找了一些的V4L的资料,完成了摄像头的单帧图片采集,接下来要做的是QT界面显示和视频流的显示了,最终的计划是完成视频采集、编码、存储和以太网传输。
希望能有时间和精力完成这么多。
先把这几天的做的东西整理出来和大家分享。
为了方便大家对程序的理解,先简单介绍一下linux下的V4L2的一些知识,然后再详细介绍V4L2编程。
更多更新请关注我的博客:
@超群天晴
相关阅读:
(原创)基于ZedBoard的Webcam设计
(一):
USB摄像头(V4L2接口)的图片采集
(原创)基于ZedBoard的Webcam设计
(二):
USB摄像头图片采集+QT显示
(原创)基于ZedBoard的Webcam设计(三):
视频的采集和动态显示
(原创)基于ZedBoard的Webcam设计(四):
MJPG编码和AVI封装
(原创)基于ZedBoard的Webcam设计(五):
x264编码在zedboard上的实现(软编码)
硬件平台:
DigilentZedBoard+USB摄像头
开发环境:
WindowsXP32bit+Wmare8.0+Ubuntu10.04+arm-linux-xilinx-gnueabi交叉编译环境
Zedboardlinux:
DigilentOOBDesign
一、一些知识
1、V4L和V4L2。
V4L是Linux环境下开发视频采集设备驱动程序的一套规范(API),它为驱动程序的编写提供统一的接口,并将所有的视频采集设备的驱动程序都纳入其的管理之中。
V4L不仅给驱动程序编写者带来极大的方便,同时也方便了应用程序的编写和移植。
V4L2是V4L的升级版,由于我们使用的OOB是3.3的内核,不再支持V4L,因而编程不再考虑V4L的api和参数定义。
2、YUYV与RGB24
RGB是一种颜色的表示法,计算机中一般采用24位来存储,每个颜色占8位。
YUV也是一种颜色空间,为什么要出现YUV,主要有两个原因,一个是为了让彩色信号兼容黑白电视机,另外一个原因是为了减少传输的带宽。
YUV中,Y表示亮度,U和V表示色度,总之它是将RGB信号进行了一种处理,根据人对亮度更敏感些,增加亮度的信号,减少颜色的信号,以这样“欺骗”人的眼睛的手段来节省空间。
YUV到RGB颜色空间转换关系是:
R=Y+1.042*(V-128);
G=Y-0.34414*(U-128)-0.71414*(V-128);
B=Y+1.772*(U-128);
YUV的格式也很多,不过常见的就是422、420等。
YUYV就是422形式,简单来说就是,两个像素点P1、P2本应该有Y1、U1、V1和Y2、U2、V2这六个分量,但是实际只保留Y1、U1、Y2、V2。
图1YUYV像素
二、应用程序设计
先定义一些宏和结构体,方便后续编程
1#defineTRUE1
2#defineFALSE0
3
4#defineFILE_VIDEO"/dev/video0"
5#defineBMP"/usr/image_bmp.bmp"
6#defineYUV"/usr/image_yuv.yuv"
7
8#defineIMAGEWIDTH640
9#defineIMAGEHEIGHT480
10
11staticintfd;
12staticstructv4l2_capabilitycap;
13structv4l2_fmtdescfmtdesc;
14structv4l2_formatfmt,fmtack;
15structv4l2_streamparmsetfps;
16structv4l2_requestbuffersreq;
17structv4l2_bufferbuf;
18enumv4l2_buf_typetype;
19unsignedcharframe_buffer[IMAGEWIDTH*IMAGEHEIGHT*3];
其中
#defineFILE_VIDEO"/dev/video0"
是要访问的摄像头设备,默人都是/dev/video0
#defineBMP"/usr/image_bmp.bmp"
#defineYUV"/usr/image_yuv.yuv"
是采集后存储的图片,为了方便测试,这里将直接获取的yuv格式数据也保存成文件,可以通过yuvviewer等查看器查看。
staticintfd;
staticstructv4l2_capabilitycap;
structv4l2_fmtdescfmtdesc;
structv4l2_formatfmt,fmtack;
structv4l2_streamparmsetfps;
structv4l2_requestbuffersreq;
structv4l2_bufferbuf;
enumv4l2_buf_typetype;
这些结构体的定义都可以从/usr/include/linux/videodev2.h中找到定义,具体含义在后续编程会做相应解释。
#defineIMAGEWIDTH640
#defineIMAGEHEIGHT480
为采集图像的大小。
定义一个frame_buffer,用来缓存RGB颜色数据
unsignedcharframe_buffer[IMAGEWIDTH*IMAGEHEIGHT*3]
这些宏和定义结束后,就可以开始编程配置摄像头并采集图像了。
一般来说V4L2采集视频数据分为五个步骤:
首先,打开视频设备文件,进行视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;其次,申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;第三,将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;第四,驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;第五,停止视频采集。
在本次设计中,定义了三个函数实现对摄像头的配置和采集。
intinit_v4l2(void);
intv4l2_grab(void);
intclose_v4l2(void);
同时由于采集到的图像数据是YUYV格式,需要进行颜色空间转换,定义了转换函数。
intyuyv_2_rgb888(void);
下面就详细介绍这几个函数的实现。
1、初始化V4l2
(1)打开视频。
linux对摄像头的访问和普通设备一样,使用open函数就可以,返回值是设备的id。
1if((fd=open(FILE_VIDEO,O_RDWR))==-1)
2{
3printf("ErroropeningV4Linterface\n");
4return(FALSE);
5}
(2)读video_capability中信息。
通过调用IOCTL函数和接口命令VIDIOC_QUERYCAP查询摄像头的信息,结构体v4l2_capability中有包括驱动名称driver、card、bus_info、version以及属性capabilities。
这里我们需要检查一下是否是为视频采集设备V4L2_CAP_VIDEO_CAPTURE以及是否支持流IO操作V4L2_CAP_STREAMING。
1if(ioctl(fd,VIDIOC_QUERYCAP,&cap)==-1)
2{
3printf("Erroropeningdevice%s:
unabletoquerydevice.\n",FILE_VIDEO);
4return(FALSE);
5}
6else
7{
8printf("driver:
\t\t%s\n",cap.driver);
9printf("card:
\t\t%s\n",cap.card);
10printf("bus_info:
\t%s\n",cap.bus_info);
11printf("version:
\t%d\n",cap.version);
12printf("capabilities:
\t%x\n",cap.capabilities);
13
14if((cap.capabilities&V4L2_CAP_VIDEO_CAPTURE)==V4L2_CAP_VIDEO_CAPTURE)
15{
16printf("Device%s:
supportscapture.\n",FILE_VIDEO);
17}
18
19if((cap.capabilities&V4L2_CAP_STREAMING)==V4L2_CAP_STREAMING)
20{
21printf("Device%s:
supportsstreaming.\n",FILE_VIDEO);
22}
23}
(3)列举摄像头所支持像素格式。
使用命令VIDIOC_ENUM_FMT,获取到的信息通过结构体v4l2_fmtdesc查询。
这步很关键,不同的摄像头可能支持的格式不一样,V4L2可以支持的格式很多,/usr/include/linux/videodev2.h文件中可以看到。
1fmtdesc.index=0;
2fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
3printf("Supportformat:
\n");
4while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!
=-1)
5{
6printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
7fmtdesc.index++;
8}
(4)设置像素格式。
一般的USB摄像头都会支持YUYV,有些还支持其他的格式。
通过前一步对摄像头所支持像素格式查询,下面需要对格式进行设置。
命令为VIDIOC_S_FMT,通过结构体v4l2_format把图像的像素格式设置为V4L2_PIX_FMT_YUYV,高度和宽度设置为IMAGEHEIGHT和IMAGEWIDTH。
一般情况下一个摄像头所支持的格式是不可以随便更改的,我尝试把把一个只支持YUYV和MJPEG的摄像头格式改为RGB24或者JPEG,都没有成功。
1fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
2fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;
3fmt.fmt.pix.height=IMAGEHEIGHT;
4fmt.fmt.pix.width=IMAGEWIDTH;
5fmt.fmt.pix.field=V4L2_FIELD_INTERLACED;
6
7if(ioctl(fd,VIDIOC_S_FMT,&fmt)==-1)
8{
9printf("Unabletosetformat\n");
10returnFALSE;
11}
为了确保设置的格式作用到摄像头上,再通过命令VIDIOC_G_FMT将摄像头设置读取回来。
1if(ioctl(fd,VIDIOC_G_FMT,&fmt)==-1)
2{
3printf("Unabletogetformat\n");
4returnFALSE;
5}
6{
7printf("fmt.type:
\t\t%d\n",fmt.type);
8printf("pix.pixelformat:
\t%c%c%c%c\n",fmt.fmt.pix.pixelformat&0xFF,(fmt.fmt.pix.pixelformat>>8)&0xFF,(fmt.fmt.pix.pixelformat>>16)&0xFF,(fmt.fmt.pix.pixelformat>>24)&0xFF);
9printf("pix.height:
\t\t%d\n",fmt.fmt.pix.height);
10printf("pix.width:
\t\t%d\n",fmt.fmt.pix.width);
11printf("pix.field:
\t\t%d\n",fmt.fmt.pix.field);
12}
完整的初始化代码如下:
ViewCode
2、图像采集
(1)申请缓存区。
使用参数VIDIOC_REQBUFS和结构体v4l2_requestbuffers。
v4l2_requestbuffers结构中定义了缓存的数量,系统会据此申请对应数量的视频缓存。
1req.count=4;
2req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
3req.memory=V4L2_MEMORY_MMAP;
4if(ioctl(fd,VIDIOC_REQBUFS,&req)==-1)
5{
6printf("requestforbufferserror\n");
7
8}
(2)获取每个缓存的信息,并mmap到用户空间。
定义结构体
structbuffer
{
void*start;
unsignedintlength;
}*buffers;
来存储mmap后的地址信息。
需要说明的是由于mmap函数定义时返回的地址是个void*,因而这里面的start也是个void*。
实际地址在运行的时候会自动分配。
1for(n_buffers=0;n_buffers<req.count;n_buffers++)
2{
3buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
4buf.memory=V4L2_MEMORY_MMAP;
5buf.index=n_buffers;
6//querybuffers
7if(ioctl(fd,VIDIOC_QUERYBUF,&buf)==-1)
8{
9printf("querybuffererror\n");
10return(FALSE);
11}
12
13buffers[n_buffers].length=buf.length;
14//map
15buffers[n_buffers].start=mmap(NULL,buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,buf.m.offset);
16if(buffers[n_buffers].start==MAP_FAILED)
17{
18printf("buffermaperror\n");
19return(FALSE);
20}
21}
(3)之后就可以开始采集视频了。
使用命令VIDIOC_STREAMON。
1type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
2ioctl(fd,VIDIOC_STREAMON,&type);
(4)取出缓存中已经采样的缓存。
使用命令VIDIOC_DQBUF。
视频数据存放的位置是buffers[n_buffers].start的地址处。
1ioctl(fd,VIDIOC_DQBUF,&buf);
完整的采集代码:
ViewCode
3、YUYV转RGB24
由于摄像头采集的数据格式为YUYV,为了方便后续设计,需要转变为RGB24,并将转换完成的数据存储到frame_buffer中。
值得一提的是,由于定义的时候buffers[index].start是个void*,没有办法进行+1这样的操作,需要强制转换为
char*pointer
pointer=buffers[0].start
由于后续RGB的数据要存储到BMP中,而BMP文件中颜色数据是“倒序”,即从下到上,从左到右,因而在向frame_buffer写数据时是从最后一行最左测开始写,每写满一行行数减一。
ViewCode
4、停止采集和关闭设备
使用命令VIDIOC_STREAMOFF停止视频采集,并关闭设备。
1intclose_v4l2(void)
2{
3ioctl(fd,VIDIOC_STREAMOFF,&buf_type);
4if(fd!
=-1)
5{
6close(fd);
7return(TRUE);
8}
9return(FALSE);
10}
5、主函数
需要把我们采集到图像数据存储成图片,为了方便调试,先将原始的数据存储为yuv格式文件,再将转换成RGB后的数据存储为BMP。
定义BMP头结构体
1typedefstructtagBITMAPFILEHEADER{
2WORDbfType;//theflagofbmp,valueis"BM"
3DWORDbfSize;//sizeBMPfile,unitisbytes
4DWORDbfReserved;//0
5DWORDbfOffBits;//mustbe54
6
7}BITMAPFILEHEADER;
8
9
10typedefstructtagBITMAPINFOHEADER{
11DWORDbiSize;//mustbe0x28
12DWORDbiWidth;//
13DWORDbiHeight;//
14WORDbiPlanes;//mustbe1
15WORDbiBitCount;//
16DWORDbiCompression;//
17DWORDbiSizeImage;//
18DWORDbiXPelsPerMeter;//
19DWORDbiYPelsPerMeter;//
20DWORDbiClrUsed;//
21DWORDbiClrImportant;//
22}BITMAPINFOHEADER;
完整的主函数
//@超群天晴
//
intmain(void)
{
FILE*fp1,*fp2;
BITMAPFILEHEADERbf;
BITMAPINFOHEADERbi;
fp1=fopen(BMP,"wb");
if(!
fp1)
{
printf("open"BMP"error\n");
return(FALSE);
}
fp2=fopen(YUV,"wb");
if(!
fp2)
{
printf("open"YUV"error\n");
return(FALSE);
}
if(init_v4l2()==FALSE)
{
return(FALSE);
}
//SetBITMAPINFOHEADER
bi.biSize=40;
bi.biWidth=IMAGEWIDTH;
bi.biHeight=IMAGEHEIGHT;
bi.biPlanes=1;
bi.biBitCount=24;
bi.biCompression=0;
bi.biSizeImage=IMAGEWIDTH*IMAGEHEIGHT*3;
bi.biXPelsPerMeter=0;
bi.biYPelsPerMeter=0;
bi.biClrUsed=0;
bi.biClrImportant=0;
//SetBITMAPFILEHEADER
bf.bfType=0x4d42;
bf.bfSize=54+bi.biSizeImage;
bf.bfReserved=0;
bf.bfOffBits=54;
v4l2_grab();
fwrite(buffers[0].start,640*480*2,1,fp2);
printf("save"YUV"OK\n");
yuyv_2_rgb888();
fwrite(&bf,14,1,fp1);
fwrite(&bi,40,1,fp1);
fwrite(frame_buffer,bi.biSizeImage,1,fp1);
printf("save"BMP"OK\n");
fclose(fp1);
fclose(fp2);
close_v4l2();
return(TRUE);
}
三、PC测试
程序编写完后,可以先在PC上做测试(实际整个调试过程都是在PC上,直道最后PC上能实现功能再挪到ZedBoard上的)。
PC上测试的结果
在/usr目录下可以查看到采集到的图片
四、Zedboard测试
PC上测试OK后,可以“挪”到ZedBoard上了。
使用arm-xilinx-linux交叉编译环境对源文件进行交叉编译,将生成的可执行文件拷贝到ZedBoard上运行即可。
使用命令
arm-xilinx-linux-gnueabi-gccv4l2grab.c-ozed-camera
对程序进行编译,编译通过后将生成的可执行文件zed-camera拷贝到到ZedBoard上,并将USB摄像头连接到ZedBoard上,通过命令
ls/dev
查看dev目录下的是否有video0设备。
如果有,可以运行可执行文件了。
在运行前我比较习惯获得可执行文件的权限,使用命令
chmod
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 转载 基于 ZedBoard Webcam 设计 USB 摄像头 V4L2 接口 图片 采集
![提示](https://static.bdocx.com/images/bang_tan.gif)