Bruceou 发表于 2024-3-2 16:38

【米尔-瑞萨RZ/G2UL开发板-试用评测】基于USB摄像头拍照

<div class='showpostmsg'><div>&nbsp;</div>

<div><strong>开发</strong><strong>环境:</strong></div>

<div>主机:Ubuntu 20.04</div>

<div>开发板:MYD-YG2UL开发板</div>

<div>Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版。V4L2是linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛的应用。本文将基于V4L2使用usb摄像头(UVC)拍照。</div>

<h1>1 查看内核对USB摄像头</h1>

<div>当插入UVC摄像头就会以下打印信息。</div>

<div></div>

<div>在dev目录下也会有相应的设备。</div>

<div></div>

<div>如果插入多个摄像头,设备名后缀数字依次增加,如: video1 video2 video3。</div>

<div>摄像头识别检测和格式支持查询</div>

<div># v4l2-ctl --list-devices</div>

<div></div>

<div>格式支持查询:</div>

<div># v4l2-ctl --list-formats-ext -d /dev/video0</div>

<div></div>

<h1>2 V4L2拍照应用实现</h1>

<h2>2.1 V4L2拍照原理</h2>

<div>在Linux下,所有外设都被看成一种特殊的文件,也就是一切皆文件,Linux中所有的外设均可像访问普通文件一样对其进行读写操作。</div>

<div>V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。</div>

<div>在Linux中V4L2拍照的调用过程如下图所示。</div>

<div></div>

<div>V4L2支持两种方式来采集图像:<strong>内存映射方式</strong><strong>(</strong><strong>mmap</strong><strong>)</strong><strong>和直接读取方式</strong><strong>(read)</strong>。前者一般用于连续视频数据的采集,后者常用于静态图片数据的采集。</div>

<div>主要分为五个步骤:</div>

<div>首先,打开设备文件,参数初始化,通过V4L2接口设置图像的采集窗口、采集的点阵大小和格式。</div>

<div>其次,申请若干图像采集的帧缓冲区,便于应用程序读取/处理视频数据。</div>

<div>第三,将申请到的帧缓冲区在数据采集输入队列排队,并启动图片采集。</div>

<div>第四,驱动开始图像数据的采集,应用程序从数据采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入数据采集输入队列,循环往复采集连续的数据;</div>

<div>第五,停止数据采集。</div>

<div>完整代码如下:</div>

<div>【usb_camera.c】</div>

<div>/**</div>

<div>******************************************************************************</div>

<div>* @File usb_camera.c</div>

<div>* @author BruceOu</div>

<div>* @version V1.0</div>

<div>* @date 2024-03-01</div>

<div>* @blog <a href="https://blog.bruceou.cn" target="_blank">https://blog.bruceou.cn</a>/</div>

<div>* @official Accounts 嵌入式实验楼</div>

<div>* @brief USB CAMERA</div>

<div>******************************************************************************</div>

<div>*/</div>

<div>/**Includes*********************************************************************/</div>

<div>#include &quot;usb_camera.h&quot;</div>

<div>#define DEBUG</div>

<div>/**【全局变量声明】*************************************************************/</div>

<div>buffer *user_buf = NULL;</div>

<div>static unsigned int n_buffer = 0;</div>

<div>static unsigned long file_length;</div>

<div>char picture_name =&quot;rk_picture&quot;;</div>

<div>int num = 0;</div>

<div>/**</div>

<div>* @brief 打开摄像头设备函数</div>

<div>* @param None</div>

<div>* @retval fd 摄像头设备</div>

<div>*/</div>

<div>int open_camer_device(char * videoDev)</div>

<div>{</div>

<div>int fd;</div>

<div>/*1.打开设备文件。*/</div>

<div>if((fd = open(videoDev,O_RDWR | O_NONBLOCK)) &lt; 0)</div>

<div>{</div>

<div>perror(&quot;Fail to open&quot;);</div>

<div>pthread_exit(NULL);</div>

<div>}</div>

<div>return fd;</div>

<div>}</div>

<div>/**</div>

<div>* @brief 初始化视频设备函数</div>

<div>* @param fd 摄像头设备</div>

<div>* @retval</div>

<div>*/</div>

<div>int init_camer_device(int fd)</div>

<div>{</div>

<div>struct v4l2_fmtdesc fmt;</div>

<div>struct v4l2_capability cap;</div>

<div>struct v4l2_format stream_fmt;</div>

<div>int ret;</div>

<div>/*2.取得设备的capability,查询视频设备驱动的功能</div>

<div>比如是否具有视频输入,或者音频输入输出等。VIDIOC_QUERYCAP,struct v4l2_capability*/</div>

<div>ret = ioctl(fd,VIDIOC_QUERYCAP,&amp;cap);</div>

<div>if(ret &lt; 0)</div>

<div>{</div>

<div>perror(&quot;FAIL to ioctl VIDIOC_QUERYCAP&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>//判断是否是一个视频捕捉设备</div>

<div>if(!(cap.capabilities &amp; V4L2_BUF_TYPE_VIDEO_CAPTURE))</div>

<div>{</div>

<div>perror(&quot;The Current device is not a video capture device\n&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>//判断是否支持视频流形式</div>

<div>if(!(cap.capabilities &amp; V4L2_CAP_STREAMING))</div>

<div>{</div>

<div>perror(&quot;The Current device does not support streaming i/o\n&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>/*3.设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式个包括宽度和高度等。*/</div>

<div>memset(&amp;fmt,0,sizeof(fmt));</div>

<div>fmt.index = 0;</div>

<div>fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;</div>

<div>while((ret = ioctl(fd,VIDIOC_ENUM_FMT,&amp;fmt)) == 0)</div>

<div>{</div>

<div>fmt.index ++ ;</div>

<div>#ifdef DEBUG</div>

<div>printf(&quot;{pixelformat = %c%c%c%c},description = &#39;%s&#39;\n&quot;,</div>

<div>fmt.pixelformat &amp; 0xff,(fmt.pixelformat &gt;&gt; 8)&amp;0xff,</div>

<div>(fmt.pixelformat &gt;&gt; 16) &amp; 0xff,(fmt.pixelformat &gt;&gt; 24)&amp;0xff,</div>

<div>fmt.description);</div>

<div>#endif</div>

<div>}</div>

<div>//设置摄像头采集数据格式,如设置采集数据的</div>

<div>//长,宽,图像格式(JPEG,YUYV,MJPEG等格式)</div>

<div>stream_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE</div>

<div>stream_fmt.fmt.pix.width = 680;//宽,必须是16的倍数</div>

<div>stream_fmt.fmt.pix.height = 480;//高,必须是16的倍数</div>

<div>stream_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//视频数据存储类型//V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YVU420;//V4L2_PIX_FMT_YUYV;</div>

<div>stream_fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;</div>

<div>//设置当前驱动的频捕获格式</div>

<div>if(-1 == ioctl(fd,VIDIOC_S_FMT,&amp;stream_fmt))</div>

<div>{</div>

<div>perror(&quot;Fail to ioctl&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>//计算图片大小</div>

<div>file_length = stream_fmt.fmt.pix.bytesperline * stream_fmt.fmt.pix.height;</div>

<div>//初始化视频采集方式(mmap)</div>

<div>init_mmap(fd);</div>

<div>return 0;</div>

<div>}</div>

<div>/**</div>

<div>* @brief 初始化视频采集方式(mmap)</div>

<div>* @param fd 摄像头设备</div>

<div>* @retval</div>

<div>*/</div>

<div>int init_mmap(int fd)</div>

<div>{</div>

<div>int i = 0;</div>

<div>struct v4l2_requestbuffers reqbuf;</div>

<div>/*4.向驱动申请帧缓冲,一般不超过5个。struct v4l2_requestbuffers*/</div>

<div>bzero(&amp;reqbuf,sizeof(reqbuf));</div>

<div>reqbuf.count = 4;//缓存数量,也就是说在缓存队列里保持多少张照片</div>

<div>reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;</div>

<div>reqbuf.memory = V4L2_MEMORY_MMAP;//或V4L2_MEMORY_USERPTR</div>

<div>//申请视频缓冲区(这个缓冲区位于内核空间,需要通过mmap映射)</div>

<div>//这一步操作可能会修改reqbuf.count的值,修改为实际成功申请缓冲区个数</div>

<div>if(-1 == ioctl(fd,VIDIOC_REQBUFS,&amp;reqbuf))</div>

<div>{</div>

<div>perror(&quot;Fail to ioctl &#39;VIDIOC_REQBUFS&#39;&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>n_buffer = reqbuf.count;</div>

<div>#ifdef DEBUG</div>

<div>printf(&quot;n_buffer = %d\n&quot;,n_buffer);</div>

<div>#endif</div>

<div>user_buf = calloc(reqbuf.count,sizeof(*user_buf));//内存中建立对应空间</div>

<div>if(user_buf == NULL)</div>

<div>{</div>

<div>fprintf(stderr,&quot;Out of memory\n&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>/*5.将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,</div>

<div>而不必去复制。mmap*/</div>

<div>for(i = 0; i &lt; n_buffer; i ++)</div>

<div>{</div>

<div>struct v4l2_buffer buf;//驱动中的一帧</div>

<div>bzero(&amp;buf,sizeof(buf));</div>

<div>buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;</div>

<div>buf.memory = V4L2_MEMORY_MMAP;</div>

<div>buf.index = i;</div>

<div>//查询申请到内核缓冲区的信息</div>

<div>if(-1 == ioctl(fd,VIDIOC_QUERYBUF,&amp;buf)) //映射用户空间</div>

<div>{</div>

<div>perror(&quot;Fail to ioctl : VIDIOC_QUERYBUF&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>user_buf.length = buf.length;</div>

<div>user_buf.start =</div>

<div>mmap(</div>

<div>NULL,/*start anywhere*/</div>

<div>buf.length,</div>

<div>PROT_READ | PROT_WRITE,</div>

<div>MAP_SHARED,</div>

<div>fd,buf.m.offset//通过mmap建立映射关系,返回映射区的起始地址</div>

<div>);</div>

<div>if(MAP_FAILED == user_buf.start)</div>

<div>{</div>

<div>perror(&quot;Fail to mmap&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>}</div>

<div>return 0;</div>

<div>}</div>

<div>int start_capturing(int fd)</div>

<div>{</div>

<div>unsigned int i;</div>

<div>enum v4l2_buf_type type;</div>

<div>/*6.将申请到的帧缓冲全部入队列,以便存放采集到的数据.VIDIOC_QBUF,struct v4l2_buffer*/</div>

<div>for(i = 0;i &lt; n_buffer;i ++)</div>

<div>{</div>

<div>struct v4l2_buffer buf;</div>

<div>bzero(&amp;buf,sizeof(buf));</div>

<div>buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;</div>

<div>buf.memory = V4L2_MEMORY_MMAP;</div>

<div>buf.index = i;</div>

<div>//把数据从缓存中读取出来</div>

<div>if(-1 == ioctl(fd,VIDIOC_QBUF,&amp;buf))//申请到的缓冲进入列队</div>

<div>{</div>

<div>perror(&quot;Fail to ioctl &#39;VIDIOC_QBUF&#39;&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>}</div>

<div>type = V4L2_BUF_TYPE_VIDEO_CAPTURE;</div>

<div>/*7.开始视频的采集。VIDIOC_STREAMON*/</div>

<div>if(-1 == ioctl(fd,VIDIOC_STREAMON,&amp;type)) //开始捕捉图像数据</div>

<div>{</div>

<div>perror(&quot;Fail to ioctl &#39;VIDIOC_STREAMON&#39;&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>return 0;</div>

<div>}</div>

<div>int mainloop(int fd)</div>

<div>{</div>

<div>int count = 2;</div>

<div>/*8.循环采集图片。*/</div>

<div>while(count-- &gt; 0)</div>

<div>{</div>

<div>for(;;)</div>

<div>{</div>

<div>fd_set fds;</div>

<div>struct timeval tv;</div>

<div>int r;</div>

<div>FD_ZERO(&amp;fds);//将指定的文件描述符集清空</div>

<div>FD_SET(fd,&amp;fds);//在文件描述符集合中增加新的文件描述符</div>

<div>/*Timeout*/</div>

<div>tv.tv_sec = 2;</div>

<div>tv.tv_usec = 0;</div>

<div>r = select(fd + 1,&amp;fds,NULL,NULL,&amp;tv);//判断是否可读(即摄像头是否准备好),tv是定时</div>

<div>if(-1 == r)</div>

<div>{</div>

<div>if(EINTR == errno)</div>

<div>continue;</div>

<div>perror(&quot;Fail to select&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>if(0 == r)</div>

<div>{</div>

<div>fprintf(stderr,&quot;select Timeout\n&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>if(read_frame(fd))//如果可读,执行read_frame ()函数,并跳出循环</div>

<div>break;</div>

<div>}</div>

<div>}</div>

<div>return 0;</div>

<div>}</div>

<div>//将采集好的数据放到文件中</div>

<div>int process_image(void *addr,int length)</div>

<div>{</div>

<div>FILE *fp;</div>

<div>char name;</div>

<div>sprintf(name,&quot;%s%d.jpg&quot;,picture_name,num ++);</div>

<div>if((fp = fopen(name,&quot;w&quot;)) == NULL)</div>

<div>{</div>

<div>perror(&quot;Fail to fopen&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>fwrite(addr,length,1,fp);</div>

<div>usleep(500);</div>

<div>fclose(fp);</div>

<div>return 0;</div>

<div>}</div>

<div>int read_frame(int fd)</div>

<div>{</div>

<div>struct v4l2_buffer buf;</div>

<div>unsigned int i;</div>

<div>bzero(&amp;buf,sizeof(buf));</div>

<div>buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;</div>

<div>buf.memory = V4L2_MEMORY_MMAP;</div>

<div>/*9.出队列以取得已采集数据的帧缓冲,取得原始采集数据。VIDIOC_DQBUF*/</div>

<div>if(-1 == ioctl(fd,VIDIOC_DQBUF,&amp;buf))</div>

<div>{</div>

<div>perror(&quot;Fail to ioctl &#39;VIDIOC_DQBUF&#39;&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>assert(buf.index &lt; n_buffer);</div>

<div>{</div>

<div>#ifdef DEBUG</div>

<div>printf (&quot;buf.index dq is %d,\n&quot;,buf.index);</div>

<div>#endif</div>

<div>}</div>

<div>//读取进程空间的数据到一个文件中</div>

<div>process_image(user_buf.start,user_buf.length);</div>

<div>/*10.将缓冲重新入队列尾,这样可以循环采集。VIDIOC_QBUF*/</div>

<div>if(-1 == ioctl(fd,VIDIOC_QBUF,&amp;buf))//把数据从缓存中读取出来</div>

<div>{</div>

<div>perror(&quot;Fail to ioctl &#39;VIDIOC_QBUF&#39;&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>return 1;</div>

<div>}</div>

<div>void stop_capturing(int fd)</div>

<div>{</div>

<div>enum v4l2_buf_type type;</div>

<div>/*11.停止视频的采集。VIDIOC_STREAMOFF*/</div>

<div>type = V4L2_BUF_TYPE_VIDEO_CAPTURE;</div>

<div>if(-1 == ioctl(fd,VIDIOC_STREAMOFF,&amp;type))</div>

<div>{</div>

<div>perror(&quot;Fail to ioctl &#39;VIDIOC_STREAMOFF&#39;&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>return;</div>

<div>}</div>

<div>void uninit_camer_device()</div>

<div>{</div>

<div>unsigned int i;</div>

<div>for(i = 0;i &lt; n_buffer;i ++)</div>

<div>{</div>

<div>if(-1 == munmap(user_buf.start,user_buf.length))</div>

<div>{</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>}</div>

<div>free(user_buf);</div>

<div>return;</div>

<div>}</div>

<div>void close_camer_device(int fd)</div>

<div>{</div>

<div>if(-1 == close(fd))</div>

<div>{</div>

<div>perror(&quot;Fail to close fd&quot;);</div>

<div>exit(EXIT_FAILURE);</div>

<div>}</div>

<div>return;</div>

<div>}</div>

<div>/**</div>

<div>* @brief 摄像头拍照函数</div>

<div>* @param void</div>

<div>* @retval Nono</div>

<div>*/</div>

<div>int main(int argc, char* argv[])</div>

<div>{</div>

<div>int camera_fd;</div>

<div>if(argc == 2 )</div>

<div>{</div>

<div>camera_fd = open_camer_device(argv);</div>

<div>init_camer_device(camera_fd);</div>

<div>start_capturing(camera_fd);</div>

<div>num = 0;</div>

<div>mainloop(camera_fd);</div>

<div>stop_capturing(camera_fd);</div>

<div>uninit_camer_device(camera_fd);</div>

<div>close_camer_device(camera_fd);</div>

<div>printf(&quot;Camera get pic success!\n&quot;);</div>

<div>}</div>

<div>else</div>

<div>{</div>

<div>printf(&quot;Please input video device!\n&quot;);</div>

<div>}</div>

<div>return 0;</div>

<div>}</div>

<div>【usb_camera.h】</div>

<div>#ifndef _USB_CAMERA_H_</div>

<div>#define _USB_CAMERA_H_</div>

<div>#include &lt;stdio.h&gt;</div>

<div>#include &lt;stdlib.h&gt;</div>

<div>#include &lt;string.h&gt;</div>

<div>#include &lt;unistd.h&gt;</div>

<div>#include &lt;pthread.h&gt;</div>

<div>#include &lt;assert.h&gt;</div>

<div>#include &lt;getopt.h&gt;</div>

<div>#include &lt;fcntl.h&gt;</div>

<div>#include &lt;errno.h&gt;</div>

<div>#include &lt;malloc.h&gt;</div>

<div>#include &lt;sys/stat.h&gt;</div>

<div>#include &lt;sys/types.h&gt;</div>

<div>#include &lt;sys/time.h&gt;</div>

<div>#include &lt;sys/mman.h&gt;</div>

<div>#include &lt;sys/ioctl.h&gt;</div>

<div>#include &lt;asm/types.h&gt;</div>

<div>#include &lt;linux/videodev2.h&gt;</div>

<div>#define VIDEO_DEV &quot;/dev/video9&quot;//摄像头设备名</div>

<div>typedef struct _buffer</div>

<div>{</div>

<div>void *start;</div>

<div>size_t length;</div>

<div>}buffer;</div>

<div>int open_camer_device(char * videoDev);</div>

<div>int init_mmap(int fd);</div>

<div>int init_camer_device(int fd);</div>

<div>int start_capturing(int fd);</div>

<div>int process_image(void *addr,int length);</div>

<div>int read_frame(int fd);</div>

<div>int mainloop(int fd);</div>

<div>void stop_capturing(int fd);</div>

<div>void uninit_camer_device();</div>

<div>void close_camer_device(int fd);</div>

<div>void camera_get_image(void);</div>

<div>#endif</div>

<div>【Makefile】</div>

<div>all: usb_camera</div>

<div>usb_camera:usb_camera.c</div>

<div>$(CC) -o usb_camera usb_camera.c</div>

<div>clean:</div>

<div>@rm -vf usb_camera *.o *~</div>

<h2>2.2 编译测试</h2>

<div>接下来就是编译下载测试了。</div>

<div><strong>1</strong><strong>.</strong><strong>编译</strong></div>

<div>设置环境变量</div>

<div>$source /opt/poky/3.1.21/environment-setup-aarch64-poky-linux</div>

<div>然后使用make编译。</div>

<div></div>

<div><strong>2</strong><strong>.</strong><strong>测试</strong></div>

<div>下载到开发板中:</div>

<div>#tftp -g -l usb_camera -r usb_camera 192.168.101.10</div>

<div>【注】-l后的文件是下载后文件名,可以自定义;-r后的是服务器的文件名。</div>

<div>接下来在MYD-YG2UL中运行拍照程序。</div>

<div></div>

<div>笔者一次拍两张,当然也可以连续拍很多,在代码中可以修改。最后将照片传到主机查看。</div>

<div>我们在Windows中查看拍的照片。</div>

<div></div>

<div>照片大小在代码中可以调整,可以通过参数传进去。</div>
</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                                                                                (function($){
                                                        var postHeight = getTextHeight(400);
                                                        $(".showpostmsg").html($(".showpostmsg").html());
                                                        $(".showpostmsg").after(loginstr);
                                                        $(".showpostmsg").css({height:postHeight,overflow:"hidden"});
                                                })(jQuery);
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>

Jacktang 发表于 2024-3-2 20:41

<p>V4L2使用usb摄像头UVC拍照还是比较清晰的</p>
页: [1]
查看完整版本: 【米尔-瑞萨RZ/G2UL开发板-试用评测】基于USB摄像头拍照