ALPHA/Mini I.MX6U 开发板配套支持多种不同的摄像头,包括正点原子的 ov5640(500W像素)、 ov2640(200W 像素)以及 ov7725(不带 FIFO、30W 像素)这三款摄像头,在开发板出厂系统上,可以使用这些摄像头;当然,除此之外我们还可以使用 USB 摄像头,直接将 USB 摄像头插入到开发板上的 USB接口即可!本章我们就来学习 Linux 下的摄像头应用编程。
本章将会讨论如下主题内容。
⚫ V4L2 简介;
⚫ V4L2 设备应用编程介绍;
⚫ 摄像头应用编程实战;
V4L2****简介
大家可以看到我们本章的标题叫做"V4L2 摄像头应用编程",那什么是 V4L2 呢?对 Linux 下摄像头 驱动程序开发有过了解的读者,应该知道这是什么意思。
V4L2 是 Video for linux two 的简称,是 Linux 内核中视频类设备的一套驱动框架,为视频类设备驱动开发和应用层提供了一套统一的接口规范,那什么是视频类设备呢?一个非常典型的视频类设备就是视频采集设备,譬如各种摄像头;当然还包括其它类型视频类设备,这里就不再给介绍了。
使用 V4L2 设备驱动框架注册的设备会在 Linux 系统/dev/目录下生成对应的设备节点文件,设备节点的名称通常为 videoX(X 标准一个数字编号,0、1、2、3......),每一个 videoX 设备文件就代表一个视频类设备。应用程序通过对 videoX 设备文件进行 I/O 操作来配置、使用设备类设备,下小节将向大家详细介绍!
V4L2****摄像头应用程序
V4L2 设备驱动框架向应用层提供了一套统一、标准的接口规范,应用程序按照该接口规范来进行应用编程,从而使用摄像头。对于摄像头设备来说,其编程模式如下所示:
-
首先是打开摄像头设备;
-
查询设备的属性或功能;
-
设置设备的参数,譬如像素格式、帧大小、帧率;
-
申请帧缓冲、内存映射;
-
帧缓冲入队;
-
开启视频采集;
-
帧缓冲出队、对采集的数据进行处理;
-
处理完后,再次将帧缓冲入队,往复;
-
结束采集。
流程图如下所示:
从流程图中可以看到,几乎对摄像头的所有操作都是通过 ioctl()来完成,搭配不同的 V4L2 指令(request参数)请求不同的操作,这些指令定义在头文件 linux/videodev2.h 中,在摄像头应用程序代码中,需要包含头文件 linux/videodev2.h,该头文件中申明了很多与摄像头应用编程相关的数据结构以及宏定义,大家可以打开这个头文件看看。
在 videodev2.h 头文件中,定义了很多 ioctl()的指令,以宏定义的形式提供(VIDIOC_XXX),如下所示:
每一个 不 同 的 指 令 宏 就 表 示 向 设 备 请 求 不 同 的 操 作 , 从 上 面 可 以 看 到 , 每 一 个 宏 后 面(_IOWR/_IOR/_IOW)还携带了一个 struct 数据结构体,譬如 struct v4l2_capability、struct v4l2_fmtdesc,这就是调用 ioctl()时需要传入的第三个参数的类型;调用 ioctl()前,定义一个该类型变量,调用 ioctl()时、将变量的指针作为 ioctl()的第三个参数传入,譬如:
在实际的应用编程中,并不是所有的指令都会用到,针对视频采集类设备,以下笔者列出了一些常用的指令:
打开摄像头
视频类设备对应的设备节点为/dev/videoX,X 为数字编号,通常从 0 开始;摄像头应用编程的第一步便是打开设备,调用 open 打开,得到文件描述符 fd,如下所示:
打开设备文件时,需要使用 O_RDWR 指定读权限和写权限。
查询设备的属性**/能力/**功能
打开设备之后,接着需要查询设备的属性,确定该设备是否是一个视频采集类设备、以及其它一些属性,怎么查询呢?自然是通过 ioctl()函数来实现,ioctl()对于设备文件来说是一个非常重要的系统调用,凡是涉及到配置设备、获取设备配置等操作都会使用 ioctl 来完成,在前面章节内容中我们就已经见识过了;
但对于普通文件来说,ioctl()几乎没什么用。
查询设备的属性,使用的指令为 VIDIOC_QUERYCAP,如下所示:
ioctl(int fd, VIDIOC_QUERYCAP, struct v4l2_capability *cap);
此时通过 ioctl()将获取到一个 struct v4l2_capability 类型数据,struct v4l2_capability 数据结构描述了设备的一些属性,结构体定义如下所示:
我们重点关注的是 capabilities 字段,该字段描述了设备拥有的能力,该字段的值如下(可以是以下任意一个值或多个值的位或关系):
这些宏都是在videodev2.h头文件中所定义的,大家可以自己去看。对于摄像头设备来说,它的capabilities字段必须包含 V4L2_CAP_VIDEO_CAPTURE,表示它支持视频采集功能。所以我们可以通过判断 capabilities字段是否包含 V4L2_CAP_VIDEO_CAPTURE、来确定它是否是一个摄像头设备,譬如:
设置帧格式、帧率
一个摄像头通常会支持多种不同的像素格式,譬如 RGB、YUYV 以及压缩格式 MJPEG 等,并且还支持多种不同的视频采集分辨率,譬如 640*480、320*240、1280*720 等,除此之外,同一分辨率可能还支持多种不同的视频采集帧率(15fps、30fps)。所以,通常在进行视频采集之前、需要在应用程序中去设置这些参数。
**a)****枚举出摄像头支持的所有像素格式:**VIDIOC_ENUM_FMT
要设置像素格式,首先得知道该设备支持哪些像素格式,如何得知呢?使用 VIDIOC_ENUM_FMT 指令:
ioctl(int fd, VIDIOC_ENUM_FMT, struct v4l2_fmtdesc *fmtdesc);
使用 VIDIOC_ENUM_FMT 可以枚举出设备所支持的所有像素格式,调用 ioctl()需要传入一个 struct v4l2_fmtdesc *指针,ioctl()会将获取到的数据写入到 fmtdesc 指针所指向的对象中。struct v4l2_fmtdesc 结构体描述了像素格式相关的信息,我们来看看 struct v4l2_fmtdesc 结构体的定义:
index 表示编号,在枚举之前,需将其设置为 0,然后每次 ioctl()调用之后将其值加 1。一次 ioctl()调用只能得到一种像素格式的信息,如果设备支持多种像素格式,则需要循环调用多次,通过 index 来控制,index 从 0 开始,调用一次 ioctl()之后加 1,直到 ioctl()调用失败,表示已经将所有像素格式都枚举出来了;
所以 index 就是一个编号,获取 index 编号指定的像素格式。
description 字段是一个简单地描述性字符串,简单描述 pixelformat 像素格式。
pixelformat 字段则是对应的像素格式编号,这是一个无符号 32 位数据,每一种像素格式都会使用一个u32 类型数据来表示,如下所示:
以上列举出来的只是其中一部分,篇幅有限、不能将所有的像素格式都列举出来,大家可以自己查看videodev2.h 头文件。可以看到后面有一个 v4l2_fourcc 宏,其实就是通过这个宏以及对应的参数合成的一个u32 类型数据。
type 字段指定类型,表示我们要获取设备的哪种功能对应的像素格式,因为有些设备它可能即支持视频采集功能、又支持视频输出等其它的功能;type 字段可取值如下:
type 字 段 需 要 在 调 用 ioctl() 之 前 设 置 它 的 值 , 对 于 摄 像 头 , 需 要 将 type 字 段 设 置 为 V4L2_BUF_TYPE_VIDEO_CAPTURE,指定我们将要获取的是视频采集的像素格式。
使用示例如下所示:
**b)****枚举摄像头所支持的所有视频采集分辨率:**VIDIOC_ENUM_FRAMESIZES
使用 VIDIOC_ENUM_FRAMESIZES 指令可以枚举出设备所支持的所有视频采集分辨率,用法如下所示:
ioctl(int fd, VIDIOC_ENUM_FRAMESIZES, struct v4l2_frmsizeenum *frmsize);
调用 ioctl()需要传入一个 struct v4l2_frmsizeenum *指针,ioctl()会将获取到的数据写入到 frmsize 指针所指向的对象中。struct v4l2_frmsizeenum 结构体描述了视频帧大小相关的信息,我们来看看 struct v4l2_frmsizeenum 结构体的定义:
index 字段与 struct v4l2_fmtdesc 结构体的 index 字段意义相同,一个摄像头通常支持多种不同的视频采集分辨率,一次 ioctl()调用只能得到一种视频帧大小信息,如果设备支持多种视频帧大小,则需要循环调用多次,通过 index 来控制。
pixel_format 字段指定像素格式,而 type 字段与 struct v4l2_fmtdesc 结构体的 type 字段意义相同;在调用 ioctl()之前,需要先设置 type 字段与 pixel_format 字段,确定我们将要枚举的是:设备的哪种功能、哪种像素格式支持的视频帧大小。
可以看到 struct v4l2_frmsizeenum 结构体中有一个 union 共用体, type= V4L2_BUF_TYPE_VIDEO_CAPTURE 情况下,discrete 生效,这是一个 struct v4l2_frmsize_discrete 类型变量,描述了视频帧大小信息(包括视频帧的宽度和高度),也就是视频采集分辨率大小。
譬如我们要枚举出摄像头 RGB565 像素格式所支持的所有视频帧大小:
**c)****枚举摄像头所支持的所有视频采集帧率:**VIDIOC_ENUM_FRAMEINTERVALS
同一种视频帧大小,摄像头可能会支持多种不同的视频采集帧率,譬如常见的 15fps、30fps、45fps 以及 60fps 等;使用 VIDIOC_ENUM_FRAMEINTERVALS 指令可以枚举出设备所支持的所有帧率,使用方式如下:
ioctl(int fd, VIDIOC_ENUM_FRAMEINTERVALS, struct v4l2_frmivalenum *frmival);
调用 ioctl()需要传入一个 struct v4l2_frmivalenum *指针,ioctl()会将获取到的数据写入到 frmival 指针所指 向 的 对象 中 。 struct v4l2_frmivalenum 结构 体描 述 了 视频 帧 率相 关的 信 息 ,我 们 来看看 struct v4l2_frmivalenum 结构体的定义:
index、type 字段与 struct v4l2_frmsizeenum 结构体的 index、type 字段意义相同。
width、height 字段用于指定视频帧大小,pixel_format 字段指定像素格式。
以上这些字段都是需要在调用 ioctl()之前设置它的值。
可以看到 struct v4l2_frmivalenum 结构体也有一个 union 共用体,当 type= V4L2_BUF_TYPE_VIDEO_CAPTURE 时,discrete 生效,这是一个 struct v4l2_fract 类型变量,描述了视频帧率信息(一秒钟采集图像的次数);struct v4l2_fract 结构体中,numerator 表示分子、denominator 表示分母,使用 numerator / denominator 来表示图像采集的周期(采集一幅图像需要多少秒),所以视频帧率便等于 denominator / numerator。
使用示例,譬如,我们要枚举出 RGB565 像素格式下 640*480 帧大小所支持的所有视频采集帧率:
**d)****查看或设置当前的格式:VIDIOC_G_FMT、**VIDIOC_S_FMT
前面介绍的指令只是枚举设备支持的像素格式、视频帧大小以及视频采集帧率等这些信息,将下来我们将介绍如何设置这些参数。
首先可以使用 VIDIOC_G_FMT 指令查看设备当前的格式,用法如下所示
int ioctl(int fd, VIDIOC_G_FMT, struct v4l2_format *fmt);
调用 ioctl()需要传入一个 struct v4l2_format *指针,ioctl()会将获取到的数据写入到 fmt 指针所指向的对象中,struct v4l2_format 结构体描述了格式相关的信息。
使用 VIDIOC_S_FMT 指令设置设备的格式,用法如下所示:
int ioctl(int fd, VIDIOC_S_FMT, struct v4l2_format *fmt);
ioctl()会使用 fmt 所指对象的数据去设置设备的格式。我们来看看 v4l2_format 结构体的定义:
type 字段依然与前面介绍的结构体中的 type 字段意义相同,不管是获取格式、还是设置格式都需要在调用 ioctl()函数之前设置它的值。
接下来是一个 union 共用体,当 type 被设置为 V4L2_BUF_TYPE_VIDEO_CAPTURE 时,pix 变量生效,它是一个 struct v4l2_pix_format 类型变量,记录了视频帧格式相关的信息,如下所示:
colorspace 字段描述的是一个颜色空间,可取值如下:
使用 VIDIOC_S_FMT 指令设置格式时,通常不需要用户指定 colorspace,底层驱动会根据像素格式 pixelformat 来确定对应的 colorspace。
例子:获取当前的格式、并设置格式
使用指令 VIDIOC_S_FMT 设置格式时,实际设置的参数并不一定等于我们指定的参数,譬如上面我们指定视频帧宽度为 800、高度为 480,但这个摄像头不一定支持这种视频帧大小,或者摄像头不支持V4L2_PIX_FMT_RGB565 这种像素格式;通常在这种情况下,底层驱动程序并不会按照我们指定的参数进行设置,它会对这些参数进行修改,譬如,如果摄像头不支持 800*480,那么底层驱动可能会将其修改为640*480(假设摄像头支持这种分辨率);所以,当 ioctl()调用返回后,我们还需要检查返回的 struct v4l2_format类型变量,以确定我们指定的参数是否已经生效:
**e)****设置或获取当前的流类型相关参数:VIDIOC_G_PARM、**VIDIOC_S_PARM
使用 VIDIOC_G_PARM 指令可以获取设备的流类型相关参数(Stream type-dependent parameters),使用方式如下:
ioctl(int fd, VIDIOC_G_PARM, struct v4l2_streamparm *streamparm);
调用 ioctl()需要传入一个 struct v4l2_streamparm *指针,ioctl()会将获取到的数据写入到 streamparm 指针所指向的对象中,struct v4l2_streamparm 结构体描述了流类型相关的信息,具体的内容等会在介绍。
使用 VIDIOC_S_PARM 指令设置设备的流类型相关参数,用法如下所示:
ioctl(int fd, VIDIOC_S_PARM, struct v4l2_streamparm *streamparm);
ioctl()会使用 streamparm 所指对象的数据去设置设备的流类型相关参数。我们来看看 struct v4l2_streamparm 结构体的定义:
type 字段与前面一样,不再介绍,在调用 ioctl()之前需先设置它的值。
当 type= V4L2_BUF_TYPE_VIDEO_CAPTURE 时,union 共用体中 capture 变量生效,它是一个 struct v4l2_captureparm 类型变量,struct v4l2_captureparm 结构体描述了摄像头采集相关的一些参数,譬如视频采集帧率,上面已经给出了该结构体的定义。
struct v4l2_captureparm 结构体中,capability 字段表示设备支持的模式有哪些,可取值如下(以下任意一个或多个的位或关系):
capturemode 则表示当前的模式,与 capability 字段的取值相同。
timeperframe 字段是一个 struct v4l2_fract 结构体类型变量,描述了设备视频采集的周期,前面已经给大家介绍过。使用 VIDIOC_S_PARM 可以设置视频采集的周期,也就是视频采集帧率,但是很多设备并不支持应用层设置 timeperframe 字段,只有当 capability 字段包含 V4L2_CAP_TIMEPERFRAME 时才表示设备支持 timeperframe 字段,这样应用层才可以去设置设备的视频采集帧率。
所以,在设置之前,先通过 VIDIOC_G_PARM 命令获取到设备的流类型相关参数,判断 capability 字段是否包含 V4L2_CAP_TIMEPERFRAME,如下所示:
申请帧缓冲、内存映射
读取摄像头数据的方式有两种,一种是 read 方式,也就是直接通过 read()系统调用读取摄像头采集到的数据;另一种则是 streaming 方式;25.2.2 小节中介绍了使用 VIDIOC_QUERYCAP 指令查询设备的属性、得到一个 struct v4l2_capability 类型数据,其中 capabilities 字段记录了设备拥有的能力,当该字段包含 V4L2_CAP_READWRITE 时,表示设备支持 read I/O 方式读取数据;当该字段包含 V4L2_CAP_STREAMING 时,表示设备支持 streaming I/O 方式;事实上,绝大部分设备都支持 streaming I/O 方式读取数据,使用 streaming I/O 方式,我们需要向设备申请帧缓冲,并将帧缓冲映射到应用程序进程地址空间中。
当完成对设备的配置之后,接下来就可以去申请帧缓冲了,帧缓冲顾名思义就是用于存储一帧图像数据的缓冲区,使用 VIDIOC_REQBUFS 指令可申请帧缓冲,使用方式如下所示:
ioctl(int fd, VIDIOC_REQBUFS, struct v4l2_requestbuffers *reqbuf);
调用 ioctl()需要传入一个 struct v4l2_requestbuffers *指针,struct v4l2_requestbuffers 结构体描述了申请帧缓冲的信息,ioctl()会根据 reqbuf 所指对象填充的信息进行申请。我们来看看 struct v4l2_requestbuffers 结构体的定义:
type 字段与前面所提及到的 type 字段意义相同,不再介绍,在调用 ioctl()之前需先设置它的值。
count 字段用于指定申请帧缓冲的数量。
memory 字段可取值如下:
通常将 memory 设置为 V4L2_MEMORY_MMAP 即可!使用示例如下:
streaming I/O 方式会在内核空间中维护一个帧缓冲队列,驱动程序会将从摄像头读取的一帧数据写入到队列中的一个帧缓冲,接着将下一帧数据写入到队列中的下一个帧缓冲;当应用程序需要读取一帧数据时,需要从队列中取出一个装满一帧数据的帧缓冲,这个取出过程就叫做出队;当应用程序处理完这一帧数据后,需要再把这个帧缓冲加入到内核的帧缓冲队列中,这个过程叫做入队!这个很容易理解,现实当中都有很多这样的例子,这里就不再举例了。
所以由此可知,读取图像数据的过程其实就是一个不断地出队列和入队列的过程,如下图所示
将帧缓冲映射到进程地址空间
使用 VIDIOC_REQBUFS 指令申请帧缓冲,该缓冲区实质上是由内核所维护的,应用程序不能直接读取该缓冲区的数据,我们需要将其映射到用户空间中,这样,应用程序读取映射区的数据实际上就是读取内核维护的帧缓冲中的数据。
在映射之前,需要查询帧缓冲的信息,譬如帧缓冲的长度、偏移量等信息,使用VIDIOC_QUERYBUF指令查询,使用方式如下所示:
ioctl(int fd, VIDIOC_QUERYBUF, struct v4l2_buffer *buf);
调用 ioctl()需要传入一个 struct v4l2_buffer *指针,struct v4l2_buffer 结构体描述了帧缓冲的信息,ioctl()会将获取到的数据写入到 buf 指针所指的对象中。我们来看看 struct v4l2_buffer 结构体的定义:
index 字段表示一个编号,申请的多个帧缓冲、每一个帧缓冲都有一个编号,从 0 开始。一次 ioctl()调用只能获取指定编号对应的帧缓冲的信息,所以要获取多个帧缓冲的信息,需要重复调用多次,每调用一次ioctl()、index 加 1,指向下一个帧缓冲。
type 字段与前面所提及到的 type 字段意义相同,不再介绍,在调用 ioctl()之前需先设置它的值。
memory 字段与 struct v4l2_requestbuffers 结构体的 memory 字段意义相同,需要在调用 ioctl()之前设置它的值。
length 字段表示帧缓冲的长度,而共同体中的 offset 则表示帧缓冲的偏移量,如何理解这个偏移量?因为应用程序通过 VIDIOC_REQBUFS 指令申请帧缓冲时,内核会向操作系统申请一块内存空间作为帧缓冲区,这块内存空间的大小就等于申请的帧缓冲数量 * 每一个帧缓冲的大小,每一个帧缓冲对应到这一块内存空间的某一段,所以它们都有一个地址偏移量。
帧缓冲的数量不要太多了,尤其是在一些内存比较吃紧的嵌入式系统中,帧缓冲的数量太多,势必会占用太多的系统内存。
使用示例,申请帧缓冲后、调用 mmap()将帧缓冲映射到用户地址空间:
在上述的示例中,我们会将三个帧缓冲映射到用户空间,并将每一个帧缓冲对应的映射区的起始地址保存在 frm_base 数组中,后面读取摄像头采集的数据时,直接读取映射区即可。
入队
使用 VIDIOC_QBUF 指令将帧缓冲放入到内核的帧缓冲队列中,使用方式如下:
ioctl(int fd, VIDIOC_QBUF, struct v4l2_buffer *buf);
调用 ioctl()之前,需要设置 struct v4l2_buffer 类型对象的 memory、type 字段,使用示例如下所示:将三个帧缓冲放入内核的帧缓冲队列(入队操作)中:
开启视频采集
将三个帧缓冲放入到队列中之后,接着便可以打开摄像头、开启图像采集了,使用VIDIOC_DQBUF 指令开启视频采集,使用方式如下所示:
读取数据、对数据进行处理
开启视频采集之后,接着便可以去读取数据了,前面我们已经说过,直接读取每一个帧缓冲的在用户空间的映射区即可读取到摄像头采集的每一帧图像数据。在读取数据之前,需要将帧缓冲从内核的帧缓冲队列中取出,这个操作叫做帧缓冲出队(有入队自然就有出队),前面已经给大家详细地介绍了这些理论知识。
使用 VIDIOC_DQBUF 指令执行出队操作,使用方式如下:
ioctl(int fd, VIDIOC_DQBUF, struct v4l2_buffer *buf);
帧缓冲出队之后,接下来便可读取数据了,然后对数据进行处理,譬如将摄像头采集的图像显示到 LCD屏上;数据处理完成之后,再将帧缓冲入队,将队列中的下一个帧缓冲出队,然后读取数据、处理,这样往复操作。
使用示例如下:
结束视频采集
如果要结束视频采集,使用 VIDIOC_STREAMOFF 指令,用法前面已经介绍了。使用示例如下所示:
V4L2****摄像头应用编程实战
通过前面的介绍,我们已经知道如何对摄像头进行应用编程了,摄像头的应用编程其实并不难,基本都是按照那样的一套流程下来即可:打开设备、查询设备、设置格式、申请帧缓冲、内存映射、入队、开启视频采集、出队、对采集到的数据进行处理,虽然步骤很多,但是这些操作步骤都是容易理解的,他并没有让你感觉到很难理解这个步骤,每一个步骤基本都是通过 ioctl()来实现,搭配不同请求指令。
本小节我们来编写摄像头应用程序,笔者希望大家能够自己去独立完成,通过前面的介绍,相信大家是能够独立完成的,可以适当地参考下面笔者提供的示例代码:
本例程源码对应的路径为: 开 发 板 光 盘 ->11 、 Linux C 应 用 编 程 例 程 源 码 ->25_v4l2_camera->v4l2_camera.c。
上述示例代码中,会将摄像头采集到的图像数据显示到开发板 LCD 屏上,我们将摄像头的像素格式设置为 RGB565,因为这样比较好处理。其它的代码就不给大家介绍了,没什么可说的,代码中的注释信息已经描述得很清楚了,这要是讲视频还可以给扯一扯,文本形式的话,有些东西不是那么好描述!
开发板出厂系统支持正点原子的 ov5640、ov7725(无 FIFO)以及 ov2640 这几款摄像头,这几款摄像头都支持 RGB565 像素格式;当然除此之外,还可以板子上使用 UVC USB 摄像头,如果大家身边有这种摄像头,也可以进行测试,但是这种 USB 摄像头通常不支持 RGB565 格式,而更多是 YUYV 格式,上述代码并不支持 YUYV 格式的处理,需要大家进行修改,你得将采集到的 YUYV 数据转为 RGB565 数据,才能在LCD 上显示采集到的图像。
接下来编译示例代码:
将编译得到的可执行文件拷贝到开发板 Linux 系统的用户家目录下:
首先在测试之前,我们的开发板上得插上一个摄像头,这里需要注意一下,前面我们提到开发板出厂系统支持ov5640、ov7725以及ov2640,这三款摄像头,但是不能同时生效,出厂系统默认配置使能的是ov5640,如果要使用 ov7725 或 ov2640,则需修改设备树,具体如何修改请大家参考"开发板光盘资料 A-基础资料/【正点原子】I.MX6U 用户快速体验 V1.7.3.pdf"文档中的 3.16 小节。
这里笔者以 ov2640 摄像头为例,笔者的测试板上已经连接了 ov2640 摄像头,如下所示:
其它摄像头的安装方式也是如此,头部朝外,注意一定是在启动之前就安装好了、而不是开发板启动之后再安装,切记!如果是 USB 摄像头,则可在开发板运行状态下,直接将 USB 摄像头插入到开发板上的USB HOST 接口即可。
接着运行测试程序,我们需要传入一个参数,该参数表示摄像头的对应的设备节点:
程序运行之后,此时开发板 LCD 屏上将会显示摄像头所采集到的图像,如下所示:
请大家忽略手机拍摄的问题!原本运行程序之后,终端会打印出摄像头所支持的像素格式、描述信息以及摄像头所支持的采集分辨率、帧率等信息,但是从图 25.3.4 中打印信息可知,程序运行之后只打印了像素格式以及描述信息,并没有打印分辨率和帧率等信息,为什么呢?当然这个不是我们的程序有问题,而是摄像头的驱动功能不够完善,底层驱动并没有去实现这些相关的功能,这里给大家简单地提一下,免得大家以为程序有问题!这里笔者换了一个 USB 摄像头,给大家看下它的打印信息,如下所示:
从上图可以看到,程序打印了摄像头所支持的所有采集分辨率大小以及帧率。
好了,本章的内容到此结束了,到此为止,我们已经学习了很多硬件外设的应用编程知识了,大家要学会活学活用,把这些东西用起来,尝试着做一个综合类的好玩的小项目,提高自己的应用编程能力,笔者觉得这是非常重要,你不要跟着笔者的教程一个一个章节往下走,你得停下来思考、多动动手、在教程的基础上多往外扩展,这样你才能进步!大家加油!