全志科技
直播中

corkia

5年用户 657经验值
擅长:嵌入式技术
私信 关注
[经验]

全志D1-H开发板USB摄像头拍照Demo

USB摄像头拍照Demo

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

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

前期准备

硬件准备

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

软件准备

  • 在运行本例之前,请确保你的Hello Word 的过程没有出现问题。即交叉编译工具和ADB工具都可正常使用或已经添加进环境变量。
  • 下文将详细介绍demo的源码写的内容。

硬件连接

主要连接串口调试,USB连接电脑可以用来传输数据和供电,USB摄像头连接到开发板的USB接口。

在这里插入图片描述

连接好的系统就是下图的样子:

在这里插入图片描述

如果此时你的开发板是开机的话,终端会打印USB摄像头连接的Log。如下图:

在这里插入图片描述

从图中可以看到,摄像头为 HIK 720P 的摄像头,同时摄像头挂在 USB1总线、为 input3 设备。

此时我们查看/dev 外设目录,可以发现有/dev/video0 /dev/video1设备,video1 为 video0的映射。

在这里插入图片描述

到此,我们的哪吒开发板已经成功连接上了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拷贝至此处。

cd d1-tina-open/package/test
mkdir camerademo
cd camerdemo
touch test.c

程序编译

程序编译采用最简单的命令行编译程序:

riscv64-unknown-linux-gnu-gcc test.c -o test

编译后,我们当前目录就会生成可执行文件test

文件烧录及传输

我们在windows中使用ADB工具将其送入开发板中:

adb push test ./.

在这里插入图片描述

程序运行

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

运行之前首先要赋予文件可执行权限,然后再运行。

chmod +x test
./test

运行截图:

在这里插入图片描述

此时程序会打印保存成功的Log。我们使用Ctrl+C终止程序运行后,可在当前文件夹看到有1.jpeg图片生成,我们将他拿出来查看。

依然使用ADB

adb pull /root/1.jpeg .

运行截图:

在这里插入图片描述

打开1.jpeg可以看到刚才拍摄到的图片:

在这里插入图片描述

如果可以成果看到拍摄的图片,那么恭喜你,你已经给D1-H哪吒添上了一双眼睛,以后你就可以用这双眼睛,去探索这个有趣的世界了!

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

开头说过我们Linux使用的是V4L2框架获取的摄像头数据。该框架的使用流程如下:

  1. 打开设备
  2. 初始化设备
  3. 注册内存映射I/O
  4. 开始捕捉
  5. 停止捕捉
  6. 关闭设备

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

1. 打开设备

打开设备使用C标准接口open函数,返回文件(设备)描述符。打开文件的方式可以选择**可读可写方式(O_RDWR)无阻塞方式(O_NONBLOCK)**打开

#define CAM_DEV "/dev/video0"
int cam_fd;
if((cam_fd = open(CAM_DEV,O_RDWR)) == -1)
{
    perror("ERROR opening V4L interface.");
    return -1;
}

2. 初始化设备

初始化设备调用init_device函数。

(1) 调用ioctl函数,对设备的I/O通道进行管理,查询设备能力,并将结果保存在结构体v4l2_capability中,结构体v4l2_capability内容如下:

struct v4l2_capability {
	__u8	driver[16]; // name of the driver module (e.g. "bttv")
	__u8	card[32]; // name of the card (e.g. "Hauppauge WinTV")
	__u8	bus_info[32]; // name of the bus (e.g. "PCI:" + pci_name(pci_dev) )
	__u32   version; // KERNEL_VERSION
	__u32	capabilities; // capabilities of the physical device as a whole
	__u32	device_caps; // capabilities accessed via this particular device (node)
	__u32	reserved[3]; // reserved fields for future extensions
};
if(ioctl(cam_fd,VIDIOC_QUERYCAP,&cam_cap) == -1)
    {
        perror("Error opening device %s: unable to query device.");
        return -1;
    }

(2) 判断是否视频捕获设备V4L2_CAP_VIDEO_CAPTURE

if((cam_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == 0) 
    {
        perror("ERROR video capture not supported.");
        return -1;
    }

(3) 调用ioctl函数,设置流数据格式,包括宽、高、像素格式,并将结果保存在结构体v4l2_format中,结构体v4l2_format

struct v4l2_pix_format {
	__u32         	width;
	__u32			height;
	__u32			pixelformat;
	__u32			field;		/* enum v4l2_field */
	__u32           bytesperline;	/* for padding, zero if unused */
	__u32          	sizeimage;
	__u32			colorspace;	/* enum v4l2_colorspace */
	__u32			priv;		/* private data, depends on pixelformat */
};
 
struct v4l2_format { // stream data format
	__u32	 type; // enum v4l2_buf_type; type of the data stream
	union {
		struct v4l2_pix_format		pix;     // V4L2_BUF_TYPE_VIDEO_CAPTURE, definition of an image format
		struct v4l2_pix_format_mplane	pix_mp;  // V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, definition of a multiplanar image format
		struct v4l2_window		win;     // V4L2_BUF_TYPE_VIDEO_OVERLAY, definition of an overlaid image
		struct v4l2_vbi_format		vbi;     // V4L2_BUF_TYPE_VBI_CAPTURE, raw VBI capture or output parameters
		struct v4l2_sliced_vbi_format	sliced;  // V4L2_BUF_TYPE_SLICED_VBI_CAPTURE, sliced VBI capture or output parameters
		__u8	raw_data[200];                   // user-defined, placeholder for future extensions and custom formats
	} fmt;
};

主要设置长宽及出输出格式。

struct v4l2_format v4l2_fmt;
    v4l2_fmt.type = V4L2_CAP_VIDEO_CAPTURE;
    v4l2_fmt.fmt.pix.width = WIDTH;
    v4l2_fmt.fmt.pix.height = HEIGHT;
    v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
    if (ioctl (cam_fd, VIDIOC_S_FMT, &v4l2_fmt) == -1) 
    {   
        perror("ERROR camera VIDIOC_S_FMT Failed.");
        return -1;
    }

3. 注册内存映射I/O

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

(1) 调用ioctl函数,设置内存映射I/O,并将结果保存在结构体v4l2_requestbuffers中,结构体v4l2_requestbuffers内容如下:

enum v4l2_memory {
	V4L2_MEMORY_MMAP             = 1,
	V4L2_MEMORY_USERPTR          = 2,
	V4L2_MEMORY_OVERLAY          = 3,
	V4L2_MEMORY_DMABUF           = 4,
};
 
struct v4l2_requestbuffers {
	__u32			count;
	__u32			type;		/* enum v4l2_buf_type */
	__u32			memory;		/* enum v4l2_memory */
	__u32			reserved[2];
};
struct v4l2_requestbuffers v4l2_req;
    v4l2_req.count = NB_BUFFER;
    v4l2_req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_req.memory = V4L2_MEMORY_MMAP;
    if (ioctl (cam_fd, VIDIOC_REQBUFS, &v4l2_req) == -1) 
    {
        perror("ERROR camera VIDIOC_REQBUFS Failed.");
        return -1;
    }

(2) 调用ioctl函数,查询缓冲区状态,并将结果保存在结构体v4l2_buffer中.

(3) 调用mmap函数,应用程序通过内存映射将帧缓冲区地址映射到用户空间;通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能。

struct v4l2_buffer v4l2_buf;
    v4l2_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buf.memory = V4L2_MEMORY_MMAP;
      for(i = 0; i < NB_BUFFER; i++) 
   {
        v4l2_buf.index = i;
        if(ioctl(cam_fd, VIDIOC_QUERYBUF, &v4l2_buf) < 0)
        {
            perror("Unable to query buffer.");
            return -1;
        }
 
        pic.tmpbuffer[i] = (unsigned char*)mmap(NULL, v4l2_buf.length, PROT_READ, MAP_SHARED, cam_fd, v4l2_buf.m.offset);
        if(pic.tmpbuffer[i] == MAP_FAILED)
        {
             perror("Unable to map buffer.");
             return -1;
        }
        if(ioctl(cam_fd, VIDIOC_QBUF, &v4l2_buf) < 0)
        {
            perror("Unable to queue buffer.");
            return -1;
        }
   }

4. 开始捕捉

调用ioctl函数,VIDIOC_QBUF,并将结果保存在结构体v4l2_buffer中;

struct v4l2_buffer buff;
    buff.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buff.memory = V4L2_MEMORY_MMAP;
    if(ioctl(cam_fd, VIDIOC_DQBUF, &buff) < 0)
    {
        printf("camera VIDIOC_DQBUF Failed.\n");
        usleep(1000*1000);
        return -1;
    }

5. 停止捕捉

调用munmap函数,取消映射设备内存;

for(i=0; i<NB_BUFFER; i++)
  munmap(pic[0].tmpbuffer[i],pic[0].tmpbytesused[i]);

6. 关闭设备

调用close函数关闭设备。

close(cam_fd);

更多回帖

发帖
×
20
完善资料,
赚取积分