查看: 684|回复: 0
收起左侧

哪吒系列文章之09——USB摄像头拍照Demo

[复制链接]

  离线 

  • TA的每日心情
    奋斗
    2022-6-21 08:23
  • 签到天数: 2 天

    [LV.1]

    发表于 2022-1-24 21:33:10 | 显示全部楼层 |阅读模式

    有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    本帖最后由 塞巴斯蒂安 于 2022-1-24 21:32 编辑

    本章节将讲解如何D1-H上使用一个USB摄像头拍摄一张照片。

    D1-H哪吒开发板上有一个USB Host接口(即电脑上那种插鼠标键盘的USB口),同时D1-H Tina Linux支持UVC(USB Video Class,USB视频类),这样D1-H就具备了开发和使用USB摄像头的软硬件条件。

    一、前期准备

    01、硬件准备

    • USB免驱摄像头一个,标准USB摄像头均可,淘宝直接搜“USB摄像头”搜出来排名靠前的随便买一个就行,本文中调试用到的是一个海康威视的摄像头,零售价格大概数十元。
    • 哪吒开发板一块

    02、软件准备
    • 在运行本例之前,请确保你的Hello Word 的过程没有出现问题。即交叉编译工具和ADB工具都可正常使用或已经添加进环境变量。详见上一章节:编译第一个程序:Hello Word

    USB Camera demo 代码包,下载地址:D1-H USB camera demo source code
    全志D1 芯片及应用-哪吒系列文章之09——USB摄像头拍照Demorisc-v单片机中文社区(1) D1-H USB camera demo source code.gz (6.36 KB, 下载次数: 0)

    下文将详细介绍demo的源码写的内容。

    二、硬件连接

    主要连接串口调试,USB连接电脑可以用来传输数据和供电,USB摄像头连接到开发板的USB接口。
    全志D1 芯片及应用-哪吒系列文章之09——USB摄像头拍照Demorisc-v单片机中文社区(2)
    连接好的系统就是下图的样子:
    全志D1 芯片及应用-哪吒系列文章之09——USB摄像头拍照Demorisc-v单片机中文社区(3)
    如果此时你的开发板是开机的话,终端会打印USB摄像头连接的Log。如下图:
    全志D1 芯片及应用-哪吒系列文章之09——USB摄像头拍照Demorisc-v单片机中文社区(4)
    从图中可以看到,摄像头为 HIK 720P 的摄像头,同时摄像头挂在 USB1总线、为 input3 设备。

    此时我们查看/dev外设目录,可以发现有/dev/video0/dev/video1设备,video1 为 video0的映射。
    全志D1 芯片及应用-哪吒系列文章之09——USB摄像头拍照Demorisc-v单片机中文社区(5)
    到此,我们的哪吒开发板已经成功连接上了USB摄像头,下一步是写程序来使用它。

    三、程序获取

    在编写程序之前,我们需要了解一下Linux中摄像头的接口标准。在LInux系统中,摄像头之所以能被识别离不开我们的系统对摄像头的驱动支持。

    Video4Linux2(Video for Linux Two, 简称V4L2)是Linux中关于视频设备的驱动框架,为上层访问底层的视频设备提供统一接口。V4L2主要支持三类设备:视频输入输出设备、VBI设备和Radio设备,分别会在/dev目录下产生videoX、vbiX和radioX设备节点,其中X是0,1,2等的数字。如USB摄像头是我们常见的视频输入设备。

    Linux 中强大的第三方库如:FFmpeg和OpenCV对V4L2均支持。

    本例就使用V4L2库完成摄像头对图片的捕捉,并将其保存为一张图片。

    依照Tina SKD开发架构,我们的代码创建在prckage目录下,我们新建文件夹camerademo在文件夹中新建test.c,或将提供资源中的代码包中的test.c拷贝至此处。
    1. cd d1-tina-open/package/test
    2. mkdir camerademo
    3. cd camerdemo
    4. touch test.c
    复制代码

    四、程序编译

    程序编译采用最简单的命令行编译程序:
    1. riscv64-unknown-linux-gnu-gcc test.c -o test
    复制代码
    编译后,我们当前目录就会生成可执行文件test

    五、文件烧录及传输

    我们在windows中使用ADB工具将其送入开发板中:
    1. adb push test ./.
    复制代码
    全志D1 芯片及应用-哪吒系列文章之09——USB摄像头拍照Demorisc-v单片机中文社区(6)

    六、程序运行

    文件传输成功后,我们从开发板中运行。

    运行之前首先要赋予文件可执行权限,然后再运行。
    1. chmod +x test
    2. ./test
    复制代码
    运行截图:
    全志D1 芯片及应用-哪吒系列文章之09——USB摄像头拍照Demorisc-v单片机中文社区(7)
    此时程序会打印保存成功的Log。我们使用Ctrl+C终止程序运行后,可在当前文件夹看到有1.jpeg图片生成,我们将他拿出来查看。

    依然使用ADB
    1. adb pull /root/1.jpeg .
    复制代码
    运行截图:
    全志D1 芯片及应用-哪吒系列文章之09——USB摄像头拍照Demorisc-v单片机中文社区(8)
    打开1.jpeg可以看到刚才拍摄到的图片:
    全志D1 芯片及应用-哪吒系列文章之09——USB摄像头拍照Demorisc-v单片机中文社区(9)
    如果可以成果看到拍摄的图片,那么恭喜你,你已经给D1-H哪吒添上了一双眼睛,以后你就可以用这双眼睛,去探索这个有趣的世界了!

    七、进阶:程序代码注释及讲解

    开头说过我们Linux使用的是V4L2框架获取的摄像头数据。该框架的使用流程如下:
    • 打开设备
    • 初始化设备
    • 注册内存映射I/O
    • 开始捕捉
    • 停止捕捉
    • 关闭设备

    根据如上大纲,我们分布讲解。

    1. 打开设备

    打开设备使用C标准接口open函数,返回文件(设备)描述符。打开文件的方式可以选择可读可写方式(O_RDWR)无阻塞方式(O_NONBLOCK)打开
    1. #define CAM_DEV "/dev/video0"
    2. int cam_fd;
    3. if((cam_fd = open(CAM_DEV,O_RDWR)) == -1)
    4. {
    5.     perror("ERROR opening V4L interface.");
    6.     return -1;
    7. }
    复制代码

    2. 初始化设备

    初始化设备调用init_device函数。

    (1) 调用ioctl函数,对设备的I/O通道进行管理,查询设备能力,并将结果保存在结构体v4l2_capability中,结构体v4l2_capability内容如下:
    1. struct v4l2_capability {
    2.     __u8    driver[16]; // name of the driver module (e.g. "bttv")
    3.     __u8    card[32]; // name of the card (e.g. "Hauppauge WinTV")
    4.     __u8    bus_info[32]; // name of the bus (e.g. "PCI:" + pci_name(pci_dev) )
    5.     __u32   version; // KERNEL_VERSION
    6.     __u32   capabilities; // capabilities of the physical device as a whole
    7.     __u32   device_caps; // capabilities accessed via this particular device (node)
    8.     __u32   reserved[3]; // reserved fields for future extensions
    9. };
    复制代码
    1. if(ioctl(cam_fd,VIDIOC_QUERYCAP,&cam_cap) == -1)
    2.     {
    3.         perror("Error opening device %s: unable to query device.");
    4.         return -1;
    5.     }
    复制代码
    (2) 判断是否视频捕获设备V4L2_CAP_VIDEO_CAPTURE

    1. if((cam_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0)
    2.     {
    3.         perror("ERROR video capture not supported.");
    4.         return -1;
    5.     }
    复制代码
    (3) 调用ioctl函数,设置流数据格式,包括宽、高、像素格式,并将结果保存在结构体v4l2_format中,结构体v4l2_format
    1. struct v4l2_pix_format {
    2.     __u32           width;
    3.     __u32           height;
    4.     __u32           pixelformat;
    5.     __u32           field;      /* enum v4l2_field */
    6.     __u32           bytesperline;   /* for padding, zero if unused */
    7.     __u32           sizeimage;
    8.     __u32           colorspace; /* enum v4l2_colorspace */
    9.     __u32           priv;       /* private data, depends on pixelformat */
    10. };

    11. struct v4l2_format { // stream data format
    12.     __u32    type; // enum v4l2_buf_type; type of the data stream
    13.     union {
    14.         struct v4l2_pix_format      pix;     // V4L2_BUF_TYPE_VIDEO_CAPTURE, definition of an image format
    15.         struct v4l2_pix_format_mplane   pix_mp;  // V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, definition of a multiplanar image format
    16.         struct v4l2_window      win;     // V4L2_BUF_TYPE_VIDEO_OVERLAY, definition of an overlaid image
    17.         struct v4l2_vbi_format      vbi;     // V4L2_BUF_TYPE_VBI_CAPTURE, raw VBI capture or output parameters
    18.         struct v4l2_sliced_vbi_format   sliced;  // V4L2_BUF_TYPE_SLICED_VBI_CAPTURE, sliced VBI capture or output parameters
    19.         __u8    raw_data[200];                   // user-defined, placeholder for future extensions and custom formats
    20.     } fmt;
    21. };
    复制代码
    主要设置长宽及出输出格式。
    1.     struct v4l2_format v4l2_fmt;
    2.     v4l2_fmt.type = V4L2_CAP_VIDEO_CAPTURE;
    3.     v4l2_fmt.fmt.pix.width = WIDTH;
    4.     v4l2_fmt.fmt.pix.height = HEIGHT;
    5.     v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    6.     if (ioctl (cam_fd, VIDIOC_S_FMT, &v4l2_fmt) == -1)
    7.     {   
    8.         perror("ERROR camera VIDIOC_S_FMT Failed.");
    9.         return -1;
    10.     }
    复制代码

    3. 注册内存映射I/O

    因为io采用的是MMAP即内存映射方式,因此调用init_mmap函数:

    (1) 调用ioctl函数,设置内存映射I/O,并将结果保存在结构体v4l2_requestbuffers中,结构体v4l2_requestbuffers内容如下:
    1. enum v4l2_memory {
    2.     V4L2_MEMORY_MMAP             = 1,
    3.     V4L2_MEMORY_USERPTR          = 2,
    4.     V4L2_MEMORY_OVERLAY          = 3,
    5.     V4L2_MEMORY_DMABUF           = 4,
    6. };

    7. struct v4l2_requestbuffers {
    8.     __u32           count;
    9.     __u32           type;       /* enum v4l2_buf_type */
    10.     __u32           memory;     /* enum v4l2_memory */
    11.     __u32           reserved[2];
    12. };
    复制代码
    1. struct v4l2_requestbuffers v4l2_req;
    2.     v4l2_req.count = NB_BUFFER;
    3.     v4l2_req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    4.     v4l2_req.memory = V4L2_MEMORY_MMAP;
    5.     if (ioctl (cam_fd, VIDIOC_REQBUFS, &v4l2_req) == -1)
    6.     {
    7.         perror("ERROR camera VIDIOC_REQBUFS Failed.");
    8.         return -1;
    9.     }
    复制代码
    (2) 调用ioctl函数,查询缓冲区状态,并将结果保存在结构体v4l2_buffer中.
    (3) 调用mmap函数,应用程序通过内存映射将帧缓冲区地址映射到用户空间;通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能。
    1. struct v4l2_buffer v4l2_buf;
    2.     v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    3.     v4l2_buf.memory = V4L2_MEMORY_MMAP;
    4.       for(i = 0; i < NB_BUFFER; i++)
    5.    {
    6.         v4l2_buf.index = i;
    7.         if(ioctl(cam_fd, VIDIOC_QUERYBUF, &v4l2_buf) < 0)
    8.         {
    9.             perror("Unable to query buffer.");
    10.             return -1;
    11.         }

    12.         pic.tmpbuffer[i] = (unsigned char*)mmap(NULL, v4l2_buf.length, PROT_READ, MAP_SHARED, cam_fd, v4l2_buf.m.offset);
    13.         if(pic.tmpbuffer[i] == MAP_FAILED)
    14.         {
    15.              perror("Unable to map buffer.");
    16.              return -1;
    17.         }
    18.         if(ioctl(cam_fd, VIDIOC_QBUF, &v4l2_buf) < 0)
    19.         {
    20.             perror("Unable to queue buffer.");
    21.             return -1;
    22.         }
    23.    }
    复制代码

    4. 开始捕捉


    调用ioctl函数,VIDIOC_QBUF,并将结果保存在结构体v4l2_buffer中;
    1. struct v4l2_buffer buff;
    2.     buff.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    3.     buff.memory = V4L2_MEMORY_MMAP;
    4.     if(ioctl(cam_fd, VIDIOC_DQBUF, &buff) < 0)
    5.     {
    6.         printf("camera VIDIOC_DQBUF Failed.\n");
    7.         usleep(1000*1000);
    8.         return -1;
    9.     }
    复制代码

    5. 停止捕捉

    调用munmap函数,取消映射设备内存;
    1. for(i=0; i<NB_BUFFER; i++)
    2.   munmap(pic[0].tmpbuffer[i],pic[0].tmpbytesused[i]);
    复制代码

    6. 关闭设备

    调用close函数关闭设备。
    1. close(cam_fd);
    复制代码





    上一篇:哪吒系列文章之08——编译第一个程序:Hello Word
    下一篇:哪吒系列文章之10——MIPI屏幕TFT08006支持
    RISCV作者优文
    全球首家只专注于RISC-V单片机行业应用的中文网站
    回复

    使用道具 举报

    高级模式
    B Color Image Link Quote Code Smilies

    本版积分规则

    关闭

    RISC-V单片机中文网上一条 /2 下一条


    版权及免责声明|RISC-V单片机中文网 |网站地图

    GMT+8, 2024-3-28 23:24 , Processed in 0.688465 second(s), 49 queries .

    快速回复 返回顶部 返回列表