【米尔-瑞萨RZ/G2UL开发板-试用评测】基于USB摄像头拍照
<div class='showpostmsg'><div> </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 "usb_camera.h"</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 ="rk_picture";</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)) < 0)</div>
<div>{</div>
<div>perror("Fail to open");</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,&cap);</div>
<div>if(ret < 0)</div>
<div>{</div>
<div>perror("FAIL to ioctl VIDIOC_QUERYCAP");</div>
<div>exit(EXIT_FAILURE);</div>
<div>}</div>
<div>//判断是否是一个视频捕捉设备</div>
<div>if(!(cap.capabilities & V4L2_BUF_TYPE_VIDEO_CAPTURE))</div>
<div>{</div>
<div>perror("The Current device is not a video capture device\n");</div>
<div>exit(EXIT_FAILURE);</div>
<div>}</div>
<div>//判断是否支持视频流形式</div>
<div>if(!(cap.capabilities & V4L2_CAP_STREAMING))</div>
<div>{</div>
<div>perror("The Current device does not support streaming i/o\n");</div>
<div>exit(EXIT_FAILURE);</div>
<div>}</div>
<div>/*3.设置视频的制式和帧格式,制式包括PAL,NTSC,帧的格式个包括宽度和高度等。*/</div>
<div>memset(&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,&fmt)) == 0)</div>
<div>{</div>
<div>fmt.index ++ ;</div>
<div>#ifdef DEBUG</div>
<div>printf("{pixelformat = %c%c%c%c},description = '%s'\n",</div>
<div>fmt.pixelformat & 0xff,(fmt.pixelformat >> 8)&0xff,</div>
<div>(fmt.pixelformat >> 16) & 0xff,(fmt.pixelformat >> 24)&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,&stream_fmt))</div>
<div>{</div>
<div>perror("Fail to ioctl");</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(&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,&reqbuf))</div>
<div>{</div>
<div>perror("Fail to ioctl 'VIDIOC_REQBUFS'");</div>
<div>exit(EXIT_FAILURE);</div>
<div>}</div>
<div>n_buffer = reqbuf.count;</div>
<div>#ifdef DEBUG</div>
<div>printf("n_buffer = %d\n",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,"Out of memory\n");</div>
<div>exit(EXIT_FAILURE);</div>
<div>}</div>
<div>/*5.将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,</div>
<div>而不必去复制。mmap*/</div>
<div>for(i = 0; i < n_buffer; i ++)</div>
<div>{</div>
<div>struct v4l2_buffer buf;//驱动中的一帧</div>
<div>bzero(&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,&buf)) //映射用户空间</div>
<div>{</div>
<div>perror("Fail to ioctl : VIDIOC_QUERYBUF");</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("Fail to mmap");</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 < n_buffer;i ++)</div>
<div>{</div>
<div>struct v4l2_buffer buf;</div>
<div>bzero(&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,&buf))//申请到的缓冲进入列队</div>
<div>{</div>
<div>perror("Fail to ioctl 'VIDIOC_QBUF'");</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,&type)) //开始捕捉图像数据</div>
<div>{</div>
<div>perror("Fail to ioctl 'VIDIOC_STREAMON'");</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-- > 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(&fds);//将指定的文件描述符集清空</div>
<div>FD_SET(fd,&fds);//在文件描述符集合中增加新的文件描述符</div>
<div>/*Timeout*/</div>
<div>tv.tv_sec = 2;</div>
<div>tv.tv_usec = 0;</div>
<div>r = select(fd + 1,&fds,NULL,NULL,&tv);//判断是否可读(即摄像头是否准备好),tv是定时</div>
<div>if(-1 == r)</div>
<div>{</div>
<div>if(EINTR == errno)</div>
<div>continue;</div>
<div>perror("Fail to select");</div>
<div>exit(EXIT_FAILURE);</div>
<div>}</div>
<div>if(0 == r)</div>
<div>{</div>
<div>fprintf(stderr,"select Timeout\n");</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,"%s%d.jpg",picture_name,num ++);</div>
<div>if((fp = fopen(name,"w")) == NULL)</div>
<div>{</div>
<div>perror("Fail to fopen");</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(&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,&buf))</div>
<div>{</div>
<div>perror("Fail to ioctl 'VIDIOC_DQBUF'");</div>
<div>exit(EXIT_FAILURE);</div>
<div>}</div>
<div>assert(buf.index < n_buffer);</div>
<div>{</div>
<div>#ifdef DEBUG</div>
<div>printf ("buf.index dq is %d,\n",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,&buf))//把数据从缓存中读取出来</div>
<div>{</div>
<div>perror("Fail to ioctl 'VIDIOC_QBUF'");</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,&type))</div>
<div>{</div>
<div>perror("Fail to ioctl 'VIDIOC_STREAMOFF'");</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 < 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("Fail to close fd");</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("Camera get pic success!\n");</div>
<div>}</div>
<div>else</div>
<div>{</div>
<div>printf("Please input video device!\n");</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 <stdio.h></div>
<div>#include <stdlib.h></div>
<div>#include <string.h></div>
<div>#include <unistd.h></div>
<div>#include <pthread.h></div>
<div>#include <assert.h></div>
<div>#include <getopt.h></div>
<div>#include <fcntl.h></div>
<div>#include <errno.h></div>
<div>#include <malloc.h></div>
<div>#include <sys/stat.h></div>
<div>#include <sys/types.h></div>
<div>#include <sys/time.h></div>
<div>#include <sys/mman.h></div>
<div>#include <sys/ioctl.h></div>
<div>#include <asm/types.h></div>
<div>#include <linux/videodev2.h></div>
<div>#define VIDEO_DEV "/dev/video9"//摄像头设备名</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> <p>V4L2使用usb摄像头UVC拍照还是比较清晰的</p>
页:
[1]