USB摄像头采集.docx
- 文档编号:12890065
- 上传时间:2023-04-22
- 格式:DOCX
- 页数:17
- 大小:20.81KB
USB摄像头采集.docx
《USB摄像头采集.docx》由会员分享,可在线阅读,更多相关《USB摄像头采集.docx(17页珍藏版)》请在冰豆网上搜索。
USB摄像头采集
USB摄像头图像采集程序分析
本文是在Linux操作系统下实现对USB摄像头的图像采集与显示的。
由于操作系统已经有了USB摄像头的驱动,因此摄像头可以直接使用。
USB摄像头的数据采集和显示分为三个步骤:
USB摄像头采集数据;将采集的数据进行解码转换成RGB格式;利用Framebuffer将RGB数据显示在LCD上。
USB摄像头图像采集属于V4L2编程,可以参考VideoforLinuxTwoAPISpecification这个文档。
我的USB摄像头采集的数据格式是Jpeg图片,像素是320*240,下一步工作是将JPEG图片转换成RGB格式。
对Framebuffer进行操作便可以显示RGB图像,即可显示摄像头采集的图像了。
多祯图片连续显示便可显示连续的画面。
下面我将从后到前的顺序依次介绍这三个过程。
首先是对Framebuffer的编程实现对RGB图像的显示。
Framebuffer在硬件上对应的就是手持设备的LCD,在PC机上就是显示其了。
在软件上就称为Framebuffer了,在Linux系统中,一个设备相当于一个文件,对文件的操作相当于对设备进行操作了,显示器对应的设备文件就是/dev/fb0。
进行如下操作:
le/dev,查看设备文件,看是否有fb0,如果有这个设备就可以进行下面的编程了。
如果没有,需要修改一个文件,/boot/grub/menu.lst,在我们使用的那个系统增加如下参数,rgb=0x317,设置为1024*76816位色显示,然后重启便可以看到fb0了。
第一步是对fb0的初始化,读取fb0相关参数并得到内存映射地址。
intinit_fb(void)
{
//intfb;
structfb_var_screeninfofb_var;
//1.openframebuffer
fb=open("/dev/fb0",O_RDWR);
if(fb<0)
{
printf("open/dev/fb0error!
\n");
return-1;
}
//2.getfbinformation
ioctl(fb,FBIOGET_VSCREENINFO,&fb_var);
w=fb_var.xres;
h=fb_var.yres;
bpp=fb_var.bits_per_pixel;
printf("screeninformation:
%d*%d,bpp:
%d\n",w,h,bpp);
//3.getframebufferaddress
fbmem=mmap(0,w*h*bpp/8,PROT_WRITE|PROT_READ,MAP_SHARED,fb,0);
return0;
}
之前定义了全局变量,
staticintfb=-1;//fb0文件描述符
staticintw,h,bpp;//显示器长度,宽度,每像素多少位
staticshort*fbmem;
这里主要有一个函数mmap(0,w*h*bpp/8,PROT_WRITE|PROT_READ,MAP_SHARED,fb,0);
把Framebuffer映射到内存空间,长度为w*b*bpp/8个字节。
然后通过对这段内存进行读取和修改,相当于对Framebuffer(硬件上就是LCD)的读取和修改。
下面是对Framebuffer的操作了,也就是对LCD进行操作了。
voidfb_point(unsignedshort*fbmem,intx,inty,
intw,shortcolor)
{
fbmem[y*w+x]=color;
}
这是一个画点的函数,(x,y)代表LCD的横坐标、竖坐标,确定某一个具体的位置,color为颜色值。
在这里我采用的是RGB565编码,通过三元色确定一个像素的颜色。
R(red)占5位,G(green)占6位,B(blue)占5位,正好16位,两个字节,因此fbmem为short类型。
voiddisplay_image(unsignedshort*fbmem,intw,
intimgw,intimgh,unsignedshort*imgbuf)
{
inti,j;
shortcolor;
unsignedshort*imagebuf=(unsignedshort*)imgbuf;
for(j=0;j { for(i=0;i { color=*imagebuf; fb_point(fbmem,10+i,10+j,w,color); imagebuf++; } } } 这是一个显示图片的函数,参数fbmem代表mmap映射frambuffer的首地址,intw代表屏的宽度,intimgw,imgh代表图片的长和宽,imgbuf为一张图片的RGB565数据。 通过此函数可以将一副图片显示在LCD上。 现在唯一的问题是图片格式一般很少是RGB格式的,所以还需做的一个工作就是将其他格式的数据转换为RGB格式。 下面介绍将.jpeg图片转换为RGB565的方法。 主要使用了两个函数: u_char*decode_jpeg(char*filename,short*widthPtr,short*heightPtr); unsignedchar*buf24_to16(unsignedchar*buf,intw,inth); 首先是将.jpeg文件转换为rgb-8-8-8格式,返回一个队列,然后将这个队列的数据转换为rgb565格式,这两个函数会用到jpeg库的相关函数。 这个图片转换的程序是亚嵌(www.akaedu.org)何老师传给我的。 如果需要的话可以发邮件给我,834152646@。 至此将一张.jpeg的图片显示在Lcd的工作全部完成了。 下面是最开始的一步,也是要介绍的最后一个步骤了,从摄像头获取一张Jpeg图片,也就是V4L2编程了。 主要参考《VideoforLinuxTwoAPISpecification》和capture.c。 由于这个相对复杂些再加上自己水平有限,我也是基于capture.c做了部分修改,所以会有些理解上可能会有些差错,请指正: 834152646@ 先介绍几个重要的结构体: structv4l2_buffer,structbuffer structv4l2_buffer在videodev2.h可以查看的到,structbuffer是自己定义的: structv4l2_buffer{ __u32index; enumv4l2_buf_typetype; __u32bytesused; __u32flags; enumv4l2_fieldfield; structtimevaltimestamp; structv4l2_timecodetimecode; __u32sequence; /*memorylocation*/ enumv4l2_memorymemory; union{ __u32offset; unsignedlonguserptr; }m; __u32length; __u32input; __u32reserved; }; structbuffer { void*start; size_tlength; }; 首先从main函数开始看,下面是简化的一部分: intmain(intargc,char**argv) { dev_name="/dev/video"; open_device(); init_device(); start_capturing(); mainloop(); stop_capturing(); uninit_device(); close_device(); exit(EXIT_SUCCESS); return0; } 第一步open_device()函数: staticvoidopen_device(void) { structstatst; if(-1==stat(dev_name,&st)){ fprintf(stderr,"Cannotidentify'%s': %d,%s\n", dev_name,errno,strerror(errno)); exit(EXIT_FAILURE); } if(! S_ISCHR(st.st_mode)){ fprintf(stderr,"%sisnodevice\n",dev_name); exit(EXIT_FAILURE); } fd=open(dev_name,O_RDWR/*required*/|O_NONBLOCK,0); if(-1==fd){ fprintf(stderr,"Cannotopen'%s': %d,%s\n", dev_name,errno,strerror(errno)); exit(EXIT_FAILURE); } } stat(dev_name,&st)使用这个函数通过设备文件名dev_name获取文件的相关属性,其中包括文件权限、文件类型、文件所有者等等一系列信息。 其中比较重要的有mode_tst_mode;记录文件权限和文件类型信息。 更详细的介绍可参考: 首先判断dev_name是不是一个字符设备,是字符设备则打开这个设备文件,即打开摄像头/dev/video3,(在我的omap3530开发板上插上摄像头之后多了这个设备,所以对应video3)open设备文件后返回一个设备文件描述符fd,在以后中程序中要经常用到这个fd。 第二步是init_device(),下面是这个函数: staticvoidinit_device(void) { structv4l2_capabilitycap; structv4l2_cropcapcropcap; structv4l2_cropcrop; structv4l2_formatfmt; unsignedintmin; if(-1==xioctl(fd,VIDIOC_QUERYCAP,&cap)){ if(EINVAL==errno){ fprintf(stderr,"%sisnoV4L2device\n", dev_name); exit(EXIT_FAILURE); }else{ errno_exit("VIDIOC_QUERYCAP"); } } if(! (cap.capabilities&V4L2_CAP_VIDEO_CAPTURE)){ fprintf(stderr,"%sisnovideocapturedevice\n", dev_name); exit(EXIT_FAILURE); } switch(io){ caseIO_METHOD_READ: if(! (cap.capabilities&V4L2_CAP_READWRITE)){ fprintf(stderr,"%sdoesnotsupportreadi/o\n", dev_name); exit(EXIT_FAILURE); } break; caseIO_METHOD_MMAP: caseIO_METHOD_USERPTR: if(! (cap.capabilities&V4L2_CAP_STREAMING)){ fprintf(stderr,"%sdoesnotsupportstreamingi/o\n", dev_name); exit(EXIT_FAILURE); } break; } /*Selectvideoinput,videostandardandtunehere.*/ CLEAR(cropcap); cropcap.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; if(0==xioctl(fd,VIDIOC_CROPCAP,&cropcap)){ crop.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; crop.c=cropcap.defrect;/*resettodefault*/ if(-1==xioctl(fd,VIDIOC_S_CROP,&crop)){ switch(errno){ caseEINVAL: /*Croppingnotsupported.*/ break; default: /*Errorsignored.*/ break; } } }else{ /*Errorsignored.*/ } CLEAR(fmt); fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width=320; fmt.fmt.pix.height=240; fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_MJPEG; fmt.fmt.pix.field=V4L2_FIELD_INTERLACED; if(-1==xioctl(fd,VIDIOC_S_FMT,&fmt)) errno_exit("VIDIOC_S_FMT"); /*NoteVIDIOC_S_FMTmaychangewidthandheight.*/ /*Buggydriverparanoia.*/ min=fmt.fmt.pix.width*2; if(fmt.fmt.pix.bytesperline fmt.fmt.pix.bytesperline=min; min=fmt.fmt.pix.bytesperline*fmt.fmt.pix.height; if(fmt.fmt.pix.sizeimage fmt.fmt.pix.sizeimage=min; switch(io){ caseIO_METHOD_READ: init_read(fmt.fmt.pix.sizeimage); break; caseIO_METHOD_MMAP: init_mmap(); break; caseIO_METHOD_USERPTR: init_userp(fmt.fmt.pix.sizeimage); break; } } xioctl(fd,VIDIOC_QUERYCAP,&cap),第一个ioctl函数用来确认这个设备是否和内核驱动相兼容,如果不兼容则返回EINVAL,并且获取这个摄像头的相关性能参数。 if(! (cap.capabilities&V4L2_CAP_VIDEO_CAPTURE)),获取摄像头参数后判断这个设备是否是videocapturedevice。 一个switch(io)语句,在这里我们使用mmap,if(! (cap.capabilities&V4L2_CAP_STREAMING))判断这个设备是否支持视频流方式。 if(0==xioctl(fd,VIDIOC_CROPCAP,&cropcap)),通过这个ioctl获取裁剪相关限制。 首先设置v4l2_buf_type,然后驱动会补充这个结构体的其他属性,这时可以设置其他参数,再利用if(-1==xioctl(fd,VIDIOC_S_CROP,&crop)),重新设置。 接着设置一帧图片的相关参数,v4l2_format相关参数设置好之后调用if(-1==xioctl(fd,VIDIOC_S_FMT,&fmt))便可设置图片属性。 这个函数最后的一步就是init_mmap();之前的操作都是获取摄像头的相关信息和对摄像头的设置。 下面将看看init_mmap()这个函数: staticvoidinit_mmap(void) { structv4l2_requestbuffersreq; CLEAR(req); req.count=4; req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory=V4L2_MEMORY_MMAP; if(-1==xioctl(fd,VIDIOC_REQBUFS,&req)){ if(EINVAL==errno){ fprintf(stderr,"%sdoesnotsupport" "memorymapping\n",dev_name); exit(EXIT_FAILURE); }else{ errno_exit("VIDIOC_REQBUFS"); } } if(req.count<2){ fprintf(stderr,"Insufficientbuffermemoryon%s\n", dev_name); exit(EXIT_FAILURE); } buffers=calloc(req.count,sizeof(*buffers)); if(! buffers){ fprintf(stderr,"Outofmemory\n"); exit(EXIT_FAILURE); } for(n_buffers=0;n_buffers structv4l2_bufferbuf; CLEAR(buf); buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory=V4L2_MEMORY_MMAP; buf.index=n_buffers; if(-1==xioctl(fd,VIDIOC_QUERYBUF,&buf)) errno_exit("VIDIOC_QUERYBUF"); buffers[n_buffers].length=buf.length; buffers[n_buffers].start= mmap(NULL/*startanywhere*/, buf.length, PROT_READ|PROT_WRITE/*required*/, MAP_SHARED/*recommended*/, fd,buf.m.offset); if(MAP_FAILED==buffers[n_buffers].start) errno_exit("mmap"); } } 首先if(-1==xioctl(fd,VIDIOC_REQBUFS,&req)),先设置req的count,type,memory,memory必须为V4L2_MEMORY_MMAP,count为希望申请到的buffer个数,当这个ioctl被调用时,驱动尝试分配count个buffers,并且把实际分配的个数存储到count中。 手册中有这么一句话: Memorymappedbuffersarelocatedindevicememoryandmustbeallocatedwiththisioctlbeforetheycanbemappedintoapplication’saddressspace。 if(-1==xioctl(fd,VIDIOC_QUERYBUF,&buf)),获取structv4l2_buffer的相关信息。 buffers[n_buffers].length=buf.length; buffers[n_buffers].start=mmap(buf.length,PROT_READ|PROT_WRITE, MAP_SHARED,fd,buf.m.offset); 内存映射到摄像头的devicememory,映射到内存,这样用户空间就可以读取buffer了。 至此整个init_device()完成了,主要做两件事,一是对摄像头参数的读取和设置,二是init_mmap(),即将v4l2_buffer映射到用户空间以便读取。 第三步是start_capturing(); staticvoidstart_capturing(void) { unsignedinti; enumv4l2_buf_typetype; switch(io){ caseIO_METHOD_READ: /*Nothingtodo.*/ break; caseIO_METHOD_MMAP: for(i=0;i structv4l2_bufferbuf; CLEAR(buf); buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory=V4L2_MEMORY_MMAP; buf.index=i; if(-1==xioctl(fd,VIDIOC_QBUF,&buf)) errno_exit("VIDIOC_QBUF"); } type=V4L2_BUF_TYPE_VIDEO_CAPTURE; if(-1==xioctl(fd,VIDIOC_STREAMON,&type)) errno_exit("VIDIOC_STREAMON"); break; } 这就两个ioctl,一个是VIDIOC_QBUF,v4l2_buf入列,另外一个是VIDIOC_STREAMON作用是开始captuer。 第四
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- USB 摄像头 采集