• 嵌入式项目:智慧农业1---V4L2编程之USB摄像头采集图像


    目录

    什么是V4L2?

    V4L2编程流程

    1.打开摄像头设备

    2.查询设备的属性或功能

    3.设置合适的采样方式

    4.如果支持STREAM则设置缓冲队列属性

    5.内存映射

    6.开启视频采集

    7.帧缓冲出队、对采集的数据进行处理(保存为图片,或者通过网络协议发送)

    8.停止采集,释放映射,关闭设备


    什么是V4L2

    V4L2,即 Video for linux two ,是 Linux 内核中视频类设备的一套驱动框架,为视频类设备驱动开发和应用层提供了一套统一的接口规范使用 V4L2 设备驱动框架注册的设备会在 Linux 系统/dev/目录下生成对应的设备节点文件,设备节点的名称通常为 videoX(X为0、1、2…)

    V4L2是Linux视频处理模块的最新标准代码,包括对视频输入设备的处理,如高频(即、电视信号输入端子)或摄像头,还包括视频处理输出装置。一般来说,最常见的是使用V4L2来处理相机数据采集的问题。我们通常使用的相机实际上是一个图像传感器,将捕捉到的光线通过视频芯片处理后,编码成JPG/MJPGYUV格式输出。我们可以很容易地通过V4L2与第一台摄像机设备“通信”,如设置或获取它们的工作参数。

    V4L2编程流程

    在内核中,摄像头捕捉到的视频数据,我们可以使用一个队列来存储。我们做的工作大致是这样的:首先配置摄像头的相关参数,可以正常工作,然后申请一个号码的内核视频缓存,送他们到队列,像三个空盘子在传送带上。然后我们还需要三个内核缓存区域通过mmap函数映射到用户空间,
    这样我们就可以操作相机数据在用户层,然后我们可以启动相机开始数据采集,每一帧捕获数据我们可以做一个团队操作,读取数据,然后再阅读内核缓存的数据小组,依次循环。

    V4L2接口编程步骤:

    1. 首先是打开摄像头设备;
    2. 查询设备的属性或功能;
    3. 设置设备的参数,譬如像素格式、 帧大小、 帧率;
    4. 申请帧缓冲、 内存映射(.把内核的缓冲区队列映射到用户空间);
    5. 帧缓冲入队;
    6. 开启视频采集;
    7. 帧缓冲出队、对采集的数据进行处理;
    8. 处理完后,再次将帧缓冲入队,往复;
    9. 结束采集,释放映射,关闭设备。

    1.打开摄像头设备

    视频类设备对应的设备节点为/dev/videoX(X为0、1、2…)可以使用命令 ls /dev/video* 查看视频类设备对应的设备节点,在ARM板上插上USB摄像头之后可以看到多了一个设备节点。

    1. //1. 打开摄像头
    2. int fd = open("/dev/video0", O_RDWR);
    3. if(-1 == fd)
    4. {
    5. perror("open");//打开失败
    6. return -1;
    7. }

    2.查询设备的属性或功能

    1.提取摄像头的能力,查看设备是否为视频采集设备以及支持那些功能

    1. #if 0
    2. /***结构体里是摄像头支持的一些功能****/
    3. struct v4l2_capability {
    4. __u8 driver[16]; /* i.e. "bttv" */
    5. __u8 card[32]; /* i.e. "Hauppauge WinTV" */
    6. __u8 bus_info[32]; /* "PCI:" + pci_name(pci_dev) */
    7. __u32 version; /* should use KERNEL_VERSION() */
    8. __u32 capabilities; /* Device capabilities */
    9. __u32 reserved[4];
    10. };
    11. #endif
    12. struct v4l2_capability cap = {0};
    13. //2. 提取摄像头的能力(linux v4l2规范)
    14. int ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
    15. if (-1 == ret)
    16. {
    17. perror("ioctl");
    18. return -1;
    19. }
    20. printf("driver name: %s device name: %s device location: %s\n", cap.driver, cap.card, cap.bus_info);
    21. printf("version: %u.%u.%u\n", (cap.version >> 16) & 0xff, (cap.version >> 8) & 0xff, cap.version & 0xff);
    22. if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
    23. printf("v4l2 dev support capture\n");
    24. if(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)
    25. printf("v4l2 dev support output\n");
    26. if(cap.capabilities & V4L2_CAP_VIDEO_OVERLAY)
    27. printf("v4l2 dev support overlay\n");
    28. if(cap.capabilities & V4L2_CAP_STREAMING)
    29. printf("v4l2 dev support streaming\n");
    30. if(cap.capabilities & V4L2_CAP_READWRITE)
    31. printf("v4l2 dev support read write\n");

    2.查看摄像头所支持的所有像素格式,先看一下v4l2_fmtdesc结构体里面包含了那些成员

    1. struct v4l2_fmtdesc {
    2. __u32 index; /* index 就是一个编号 */
    3. __u32 type; /* enum v4l2_buf_type */
    4. __u32 flags;
    5. __u8 description[32]; /* description 字段是一个简单地描述性字符串 */
    6. __u32 pixelformat; /* pixelformat 字段则是对应的像素格式编号 */
    7. __u32 reserved[4];
    8. };

    代码查看设备支持那些格式如下:

    1. struct v4l2_fmtdesc fmtdesc;
    2. fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    3. fmtdesc.index = 0;
    4. //支持哪些图片格式
    5. while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
    6. {
    7. printf("fmt:%s\n", fmtdesc.description);
    8. fmtdesc.index++;
    9. }

    3.查看摄像头所支持的分辨率,先看一下v4l2_frmsizeenum结构体

    1. struct v4l2_frmsizeenum {
    2. __u32 index; /* Frame size number */
    3. __u32 pixel_format; /* 像素格式 */
    4. __u32 type; /* type */
    5. union { /* Frame size */
    6. struct v4l2_frmsize_discrete discrete;
    7. struct v4l2_frmsize_stepwise stepwise;
    8. };
    9. __u32 reserved[2]; /* Reserved space for future use */
    10. };
    11. struct v4l2_frmsize_discrete {
    12. __u32 width; /* Frame width [pixel] */
    13. __u32 height; /* Frame height [pixel] */
    14. };

    我们要枚举出摄像头 MJPEG 像素格式所支持的所有分辨率:

    1. struct v4l2_frmsizeenum frmsize;
    2. frmsize.index = 0;
    3. frmsize.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    4. printf("MJPEG格式支持所有分辨率如下:\n");
    5. frmsize.pixel_format = V4L2_PIX_FMT_MJPEG;
    6. while(ioctl(fd,VIDIOC_ENUM_FRAMESIZES,&frmsize) == 0){
    7. printf("frame_size<%d*%d>\n",frmsize.discrete.width,frmsize.discrete.height);
    8. frmsize.index++;
    9. }

    4.查看摄像头所支持的视频采集帧率,先看一下v4l2_frmivalenum结构体

    1. struct v4l2_frmivalenum {
    2. __u32 index; /* Frame format index */
    3. __u32 pixel_format;/* Pixel format */
    4. __u32 width; /* Frame width */
    5. __u32 height; /* Frame height */
    6. __u32 type; /* type */
    7. union { /* Frame interval */
    8. struct v4l2_fract discrete;
    9. struct v4l2_frmival_stepwise stepwise;
    10. };
    11. __u32 reserved[2]; /* Reserved space for future use */
    12. };
    13. struct v4l2_fract {
    14. __u32 numerator; //分子
    15. __u32 denominator; //分母
    16. };

    比如我们要枚举出摄像头 MJPEG 格式下640*480分辨率所支持的帧数:

    1. struct v4l2_frmivalenum frmival;
    2. frmival.index = 0;
    3. frmival.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    4. frmival.pixel_format = V4L2_PIX_FMT_MJPEG;
    5. frmival.width = 640;
    6. frmival.height = 480;
    7. while(ioctl(fd,VIDIOC_ENUM_FRAMEINTERVALS,&frmival) == 0){
    8. printf("frame_interval under frame_size <%d*%d> support %dfps\n",frmival.width,frmival.height,frmival.discrete.denominator / frmival.discrete.numerator);
    9. frmival.index++;
    10. }

    3.设置合适的采样方式

    首先要定义结构体v4l2_format来保存采集格式信息,使用VIDIOC_S_FMT指令设置格式,最后用VIDIOC_G_FMT指令查看相关参数是否生效,我们先来看看结构体v4l2_format

    1. struct v4l2_format {
    2. __u32 type;
    3. union {
    4. struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
    5. struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
    6. struct v4l2_window win; /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
    7. struct v4l2_vbi_format vbi; /* V4L2_BUF_TYPE_VBI_CAPTURE */
    8. struct v4l2_sliced_vbi_format sliced; /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
    9. struct v4l2_sdr_format sdr; /* V4L2_BUF_TYPE_SDR_CAPTURE */
    10. struct v4l2_meta_format meta; /* V4L2_BUF_TYPE_META_CAPTURE */
    11. __u8 raw_data[200]; /* user-defined */
    12. } fmt;
    13. };

    代码实现:

    1. //3. 设置合适的采样方式
    2. struct v4l2_format v4l2_fmt;
    3. memset(&v4l2_fmt, 0, sizeof(struct v4l2_format));
    4. v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    5. v4l2_fmt.fmt.pix.width = W; //宽度
    6. v4l2_fmt.fmt.pix.height = H; //高度
    7. v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG; //像素格式M-JPEG
    8. v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY;
    9. // 检查设置参数是否生效
    10. if (ioctl(fd, VIDIOC_S_FMT, &v4l2_fmt) < 0)
    11. {
    12. printf("ERR(%s):VIDIOC_S_FMT failed\n", __func__);
    13. return -1;
    14. }

    4.如果支持STREAM则设置缓冲队列属性

    申请帧缓冲顾名思义就是申请用于存储一帧图像数据的缓冲区, 使 VIDIOC_REQBUFS 指令可申请帧缓冲其中struct v4l2_requestbuffers 结构体描述了申请帧缓冲的信息。我们先看看结构体v4l2_buffer

    1. struct v4l2_buffer {
    2. __u32 index; //缓冲区编号
    3. __u32 type;
    4. __u32 bytesused; //图像大小
    5. __u32 flags;
    6. __u32 field;
    7. struct timeval timestamp;
    8. struct v4l2_timecode timecode;
    9. __u32 sequence; /* memory location */
    10. __u32 memory;
    11. union {
    12. __u32 offset;
    13. unsigned long userptr;
    14. struct v4l2_plane *planes;
    15. __s32 fd;
    16. } m;
    17. __u32 length;
    18. __u32 reserved2;
    19. union {
    20. __s32 request_fd;
    21. __u32 reserved;
    22. };
    23. };

    代码实现:

    1. void* addr[4]; //缓存地址, 假设定为系统缓存4帧
    2. unsigned int size[4];
    3. struct v4l2_requestbuffers req;
    4. req.count = 4; //缓存数量
    5. req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    6. req.memory = V4L2_MEMORY_MMAP;
    7. if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
    8. {
    9. printf("ERR(%s):VIDIOC_REQBUFS failed\n", __func__);
    10. return -1;
    11. }

    5.内存映射

    把内核的缓冲区队列映射到用户空间

    1. int i = 0;
    2. for(;i<4; i++)
    3. {
    4. struct v4l2_buffer v4l2_buffer;
    5. memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
    6. v4l2_buffer.index = i; //想要查询的缓存
    7. v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    8. v4l2_buffer.memory = V4L2_MEMORY_MMAP;
    9. /* 查询缓存信息 */
    10. int ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2_buffer);
    11. if(ret < 0)
    12. {
    13. printf("Unable to query buffer.\n");
    14. return -1;
    15. }
    16. /* 得到图像缓冲位置 */
    17. addr[i] = mmap(NULL /* start anywhere */ ,
    18. v4l2_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
    19. fd, v4l2_buffer.m.offset);
    20. size[i]=v4l2_buffer.length;
    21. if (ioctl(fd, VIDIOC_QBUF, &v4l2_buffer) < 0)
    22. {
    23. printf("ERR(%s):VIDIOC_QBUF failed\n", __func__);
    24. return -1;
    25. }
    26. }

    6.开启视频采集

    1. //5. 开启采样流
    2. enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    3. if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
    4. {
    5. printf("ERR(%s):VIDIOC_STREAMON failed\n", __func__);
    6. return -1;
    7. }

    7.帧缓冲出队、对采集的数据进行处理(保存为图片,或者通过网络协议发送)

    这里我们使用了IO多路复用,可以同时对多个设备进行操作。

    1. //6. 等待采样成功并提取图像(select)
    2. struct pollfd poll_fds[1];
    3. poll_fds[0].fd = fd;
    4. poll_fds[0].events = POLLIN; //关注可读
    5. poll(poll_fds, 1, 10000);//等待有图像准备好
    6. //6.2 提取采样内容
    7. struct v4l2_buffer buffer;
    8. buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    9. buffer.memory = V4L2_MEMORY_MMAP;
    10. if (ioctl(fd, VIDIOC_DQBUF, &buffer) < 0)
    11. {
    12. printf("ERR(%s):VIDIOC_DQBUF failed, dropped frame\n", __func__);
    13. return -1;
    14. }
    15. //buffer.index: 成功拍摄出画面的下标
    16. //buffer.bytesused: 成功拍下来图片的大小
    17. //addr[buffer.index]: 图片存储的内存位置
    18. printf("ready: %u %u\n", buffer.index, buffer.bytesused);
    19. //假如实现视频监控: 你应该将内存图片通过网络发走!(这里使用了UDP协议)
    20. #if 1
    21. int ret=sendto(Socket_fd, addr[buffer.index], buffer.bytesused, 0, (struct sockaddr *)&Phone_ipaddr, sizeof(Phone_ipaddr));
    22. if(ret<0)
    23. {
    24. perror("sendto");
    25. }
    26. printf("%d\n",ret);
    27. #endif
    28. //假设实现照相机: 你应该将内存图片保存到文件中!
    29. #if 0
    30. int fdjpg = open("1.jpg", O_CREAT|O_WRONLY, 0666);
    31. if(-1 == fdjpg)
    32. {
    33. perror("open 1.jpg");
    34. }
    35. else
    36. {
    37. //内存图片保存到文件中
    38. write(fdjpg, addr[buffer.index], buffer.bytesused);
    39. }
    40. close(fdjpg);
    41. break;
    42. #endif
    43. //6.3 标识图像已经取走,读取数据并处理完之后要再次入队
    44. if (ioctl(fd, VIDIOC_QBUF, &buffer) < 0)
    45. {
    46. printf("ERR(%s):VIDIOC_QBUF failed\n", __func__);//入队失败
    47. return -1;
    48. }
    49. }

    8.停止采集,释放映射,关闭设备

    1. if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
    2. {
    3. printf("ERR(%s):VIDIOC_STREAMOFF failed\n", __func__);
    4. return -1;
    5. }
    6. // 释放映射
    7. for(int i=0;i<4;i++){
    8. munmap(add[i],size[i]);
    9. }
    10. close(fd);

  • 相关阅读:
    [CC2642R1][VSCODE+Embedded IDE+Cortex-Debug] TI CC2642R1 快速搭建VsCode开发环境
    新手杠杆炒股需要什么条件?股市杠杆如何操作的具体流程?
    一文理解Hadoop分布式存储和计算框架入门基础
    四、分类算法 - 随机森林
    数据结构每日亿题(五)
    Redis 之高可用与持久化
    区块链9999999666666
    elementui 表单规则
    Excel数据处理:动态数据分析报表、单元格数字格式、使用排序工具
    java 歌词解析 源代码, 在windows10下调试运行成功。
  • 原文地址:https://blog.csdn.net/m0_70983574/article/details/126714272