当前位置: 首页 > news >正文

Linux-V4L2摄像头应用编程

ALPHA/Mini I.MX6U 开发板配套支持多种不同的摄像头,包括正点原子的 ov5640500W像素)、 ov2640(200W 像素)以及 ov7725(不带 FIFO30W 像素)这三款摄像头,在开发板出厂系统上,可以使用这些摄像头;当然,除此之外我们还可以使用 USB 摄像头,直接将 USB 摄像头插入到开发板上的 USB接口即可!本章我们就来学习 Linux 下的摄像头应用编程。

本章将会讨论如下主题内容。

V4L2 简介;

V4L2 设备应用编程介绍;

摄像头应用编程实战;


V4L2 简介

大家可以看到我们本章的标题叫做“V4L2 摄像头应用编程”,那什么是 V4L2 呢?对 Linux 下摄像头 驱动程序开发有过了解的读者,应该知道这是什么意思。

V4L2 Video for linux two 的简称,是 Linux 内核中视频类设备的一套驱动框架,为视频类设备驱动开发和应用层提供了一套统一的接口规范,那什么是视频类设备呢?一个非常典型的视频类设备就是视频采集设备,譬如各种摄像头;当然还包括其它类型视频类设备,这里就不再给介绍了。

使用 V4L2 设备驱动框架注册的设备会在 Linux 系统/dev/目录下生成对应的设备节点文件,设备节点的名称通常为 videoXX 标准一个数字编号,0123……),每一个 videoX 设备文件就代表一个视频类设备。应用程序通过对 videoX 设备文件进行 I/O 操作来配置、使用设备类设备,下小节将向大家详细介绍!


V4L2 摄像头应用程序

V4L2 设备驱动框架向应用层提供了一套统一、标准的接口规范,应用程序按照该接口规范来进行应用编程,从而使用摄像头。对于摄像头设备来说,其编程模式如下所示:

1. 首先是打开摄像头设备;

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

3. 设置设备的参数,譬如像素格式、帧大小、帧率;

4. 申请帧缓冲、内存映射;

5. 帧缓冲入队;

6. 开启视频采集;

7. 帧缓冲出队、对采集的数据进行处理;

8. 处理完后,再次将帧缓冲入队,往复;

9. 结束采集。

流程图如下所示:

从流程图中可以看到,几乎对摄像头的所有操作都是通过 ioctl()来完成,搭配不同的 V4L2 指令(request参数)请求不同的操作,这些指令定义在头文件 linux/videodev2.h 中,在摄像头应用程序代码中,需要包含头文件 linux/videodev2.h,该头文件中申明了很多与摄像头应用编程相关的数据结构以及宏定义,大家可以打开这个头文件看看。

videodev2.h 头文件中,定义了很多 ioctl()的指令,以宏定义的形式提供(VIDIOC_XXX),如下所示:

每一个 不 同 的 指 令 宏 就 表 示 向 设 备 请 求 不 同 的 操 作 , 从 上 面 可 以 看 到 , 每 一 个 宏 后 面(_IOWR/_IOR/_IOW)还携带了一个 struct 数据结构体,譬如 struct v4l2_capabilitystruct v4l2_fmtdesc,这就是调用 ioctl()时需要传入的第三个参数的类型;调用 ioctl()前,定义一个该类型变量,调用 ioctl()时、将变量的指针作为 ioctl()的第三个参数传入,譬如:

在实际的应用编程中,并不是所有的指令都会用到,针对视频采集类设备,以下笔者列出了一些常用的指令:


打开摄像头

视频类设备对应的设备节点为/dev/videoXX 为数字编号,通常从 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、来确定它是否是一个摄像头设备,譬如:


设置帧格式、帧率

一个摄像头通常会支持多种不同的像素格式,譬如 RGBYUYV 以及压缩格式 MJPEG 等,并且还支持多种不同的视频采集分辨率,譬如 640*480320*2401280*720 等,除此之外,同一分辨率可能还支持多种不同的视频采集帧率(15fps30fps)。所以,通常在进行视频采集之前、需要在应用程序中去设置这些参数。

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

同一种视频帧大小,摄像头可能会支持多种不同的视频采集帧率,譬如常见的 15fps30fps45fps 以及 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 结构体的 indextype 字段意义相同。

widthheight 字段用于指定视频帧大小,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_FMTVIDIOC_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_PARMVIDIOC_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 类型对象的 memorytype 字段,使用示例如下所示:将三个帧缓冲放入内核的帧缓冲队列(入队操作)中:


开启视频采集

将三个帧缓冲放入到队列中之后,接着便可以打开摄像头、开启图像采集了,使用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,因为这样比较好处理。其它的代码就不给大家介绍了,没什么可说的,代码中的注释信息已经描述得很清楚了,这要是讲视频还可以给扯一扯,文本形式的话,有些东西不是那么好描述!

开发板出厂系统支持正点原子的 ov5640ov7725(无 FIFO)以及 ov2640 这几款摄像头,这几款摄像头都支持 RGB565 像素格式;当然除此之外,还可以板子上使用 UVC USB 摄像头,如果大家身边有这种摄像头,也可以进行测试,但是这种 USB 摄像头通常不支持 RGB565 格式,而更多是 YUYV 格式,上述代码并不支持 YUYV 格式的处理,需要大家进行修改,你得将采集到的 YUYV 数据转为 RGB565 数据,才能在LCD 上显示采集到的图像。

接下来编译示例代码:

将编译得到的可执行文件拷贝到开发板 Linux 系统的用户家目录下:

首先在测试之前,我们的开发板上得插上一个摄像头,这里需要注意一下,前面我们提到开发板出厂系统支持ov5640ov7725以及ov2640,这三款摄像头,但是不能同时生效,出厂系统默认配置使能的是ov5640,如果要使用 ov7725 ov2640,则需修改设备树,具体如何修改请大家参考“开发板光盘资料 A-基础资料/【正点原子】I.MX6U 用户快速体验 V1.7.3.pdf”文档中的 3.16 小节。

这里笔者以 ov2640 摄像头为例,笔者的测试板上已经连接了 ov2640 摄像头,如下所示:

其它摄像头的安装方式也是如此,头部朝外,注意一定是在启动之前就安装好了、而不是开发板启动之后再安装,切记!如果是 USB 摄像头,则可在开发板运行状态下,直接将 USB 摄像头插入到开发板上的USB HOST 接口即可。

接着运行测试程序,我们需要传入一个参数,该参数表示摄像头的对应的设备节点:

程序运行之后,此时开发板 LCD 屏上将会显示摄像头所采集到的图像,如下所示:

请大家忽略手机拍摄的问题!原本运行程序之后,终端会打印出摄像头所支持的像素格式、描述信息以及摄像头所支持的采集分辨率、帧率等信息,但是从图 25.3.4 中打印信息可知,程序运行之后只打印了像素格式以及描述信息,并没有打印分辨率和帧率等信息,为什么呢?当然这个不是我们的程序有问题,而是摄像头的驱动功能不够完善,底层驱动并没有去实现这些相关的功能,这里给大家简单地提一下,免得大家以为程序有问题!这里笔者换了一个 USB 摄像头,给大家看下它的打印信息,如下所示:

从上图可以看到,程序打印了摄像头所支持的所有采集分辨率大小以及帧率。

好了,本章的内容到此结束了,到此为止,我们已经学习了很多硬件外设的应用编程知识了,大家要学会活学活用,把这些东西用起来,尝试着做一个综合类的好玩的小项目,提高自己的应用编程能力,笔者觉得这是非常重要,你不要跟着笔者的教程一个一个章节往下走,你得停下来思考、多动动手、在教程的基础上多往外扩展,这样你才能进步!大家加油!

相关文章:

Linux-V4L2摄像头应用编程

ALPHA/Mini I.MX6U 开发板配套支持多种不同的摄像头,包括正点原子的 ov5640(500W像素)、 ov2640(200W 像素)以及 ov7725(不带 FIFO、30W 像素)这三款摄像头,在开发板出厂系统上&…...

掌握谈判技巧,达成双赢协议

在当今竞争激烈且合作频繁的社会环境中,谈判成为了我们解决分歧、谋求共同发展的重要手段。无论是商业合作、职场交流,还是国际事务协商,掌握谈判技巧以达成双赢协议都具有极其关键的意义。它不仅能够让各方在利益分配上找到平衡点&#xff0…...

Mysql - 存储引擎

一 MYSQL体系结构简介 MYSQL的体系结构可以分为四个层级,从上往下依次为: 1. 连接层: 最上层为客户端以及一些连接服务,包含连接操作,例如JAVA想要与MYSQL建立连接就需要用到JDBC,PHP语言与Python也可以连接到MYSQL&am…...

借助 CC-Link IE FB 转 Profinet 网关实现西门子 PLC 接入三菱 PLC 系统的解决策略

我们公司自动化生产线上,原有的控制系统采用三菱 PLC 通过 CC-Link IEFB 网络进行通信和控制。后来随着企业生产规模的扩大和对自动化系统集成度要求的提高,需要将部分设备与新引入的西门子 PLC 控制系统相连接,而西门子 PLC 使用 ProfiNet 协…...

未完成_RFdiffusion应用案例_从头设计pMHC的结合剂

目录 1. 论文导读1)摘要2)设计流程3)设计流程的验证 2. 实战 1. 论文导读 Liu, Bingxu, et al. “Design of high specificity binders for peptide-MHC-I complexes.” bioRxiv (2024): 2024-11. 1)摘要 MHC-I 将胞内抗原肽递呈…...

python使用h5py保存数据

python使用h5py保存使用 1、导入库 pip install h5py 2、简介 HDF5(Hierarchical Data Format version 5)是一种可用于存储大量科学数据的文件格式。 支持层次化的数据组织,可以将数据分为多个组(group)和数据集(dataset)。 支持多种数据类型,包括整数、浮点数、字…...

ubuntu24.04利用selenium控制浏览器的方法

ubuntu24.04利用selenium控制浏览器的方法 1.安装selenium sudo apt install python3-selenium2.下载chromedriver 下载地址1 下载地址2 在上面两个地址中挑选自己的版本号 下载chromedriver的时候一定要跟自己浏览器的版本相对应,笔者采用的是版本 131.0.6778.1…...

Thonny IDE + MicroPython + ESP32 + 0.96寸OLED(IIC) 显示任意字符

四针脚0.96英寸OLED显示屏模块的具体参数如下表所示。 参数名称 参数特性 分辨率 128x64像素 通信方式 IIC 驱动芯片 SSD1306 屏幕颜色 白色、蓝色或黄蓝双色 元件: 四针脚0.96英寸OLED显示屏模块 ESP32 DEVKIT_C开发板 杜邦线USB Type-C 接线&#xf…...

centos7 扩容

首先关闭虚拟机设置一下磁盘 lsblk查看磁盘 空间 可以看到想要扩容的是sda2 只有19.7G sudo fdisk -l查看sda2 的起始扇区 sudo fdisk /dev/sda更改磁盘依次输入: sudo pvresize /dev/sda2sudo xfs_growfs /...

FreeRTOS实现UART通信

串口通信 速战速决形式,大家走一遍就通; 本次实验验证: 配置文件 4、打开CubeMX 5、选择芯片型号,然后点击开始项目 6、配置时钟 配置烧录引脚,与FreeRTOS系统时钟 选择FreeRTOS 这里已经默认有一个任务&…...

【从CURD到全栈成长】Java后端如何提升自己?

全文目录: 开篇语前言 🌟📜 目录1. 为什么CRUD不等于后端开发? 🤔2. 技能进阶:从CRUD走向设计与架构 🛠️2.1 了解MVC与三层架构2.2 理解设计模式2.3 学习微服务架构 3. 深挖Java核心&#xff1…...

动态计算加载图片

学习啦 别名路径:①npm install path --save-dev②配置 // vite.config,js import { defineConfig } from vite import vue from vitejs/plugin-vueimport { viteStaticCopy } from vite-plugin-static-copy import path from path export default defineConfig({re…...

利用R包QstFstComp包进行Qst-Fst分析

1.Qst-Fst分析 安装和加载QstFstComp包 首先,你需要安装devtools包,如果尚未安装,可以使用以下命令安装: install.packages("devtools") 2. 然后,使用devtools安装QstFstComp包:R library(de…...

ASP.NET Core8.0学习笔记(二十五)——EF Core Include导航数据加载之预加载与过滤

一、导航属性数据加载 1.在EF Core中可以使用导航属性来加载相关实体。 2.加载实体的三种方式: (1)预先加载:直接在查询主体时就把对应的依赖实体查出来(作为初始查询的一部分) (2)显式加载:使用代码指示稍后显式的从…...

ubuntu常用的设置

目录 1.设置vim的tab键为4个空格2.设置git提交时自动将Tab转换为空格 1.设置vim的tab键为4个空格 set tabstop4 set shiftwidth4 set expandtab tabstop设置Tab键为4个字符宽,shiftwidth设置自动缩进时的宽度也为4,expandtab表示将Tab转换为等量的空格。…...

基于框架的逻辑回归:原理、实现与应用

目录 ​编辑 逻辑回归原理 损失函数与优化 正则化 基于框架的实现 1. 数据预处理 2. 模型初始化与训练 3. 模型评估与调优 4. 特征缩放 逻辑回归的应用 信用评分 医疗诊断 垃圾邮件识别 推荐系统 结论 在机器学习领域,逻辑回归是一种基础且强大的分类…...

Charts 教程:创建交互式图表的基础

ECharts 是一个开源的、基于 JavaScript 的数据可视化库,它可以帮助你快速创建交互式的图表。无论是简单的柱状图、折线图,还是复杂的地图和关系图,ECharts 都能够轻松应对。本文将带你了解如何在你的网页中使用 ECharts 创建图表&#xff0c…...

VTK知识学习(20)- 数据的存储与表达

1、数据的存储 1)、vtkDataArray VTK中的内存分配采用连续内存,可以快速地创建、删除和遍历,称之为数据数组(DataArray),用类 vtkDataArray 实现。数组数据的访问是基于索引的,从零开始计数。 以 vtkFloatArray 类来说明如何在 …...

springboot网站开发-使用redis作为定时器控制手机号每日注册次数

springboot网站开发-使用redis作为定时器控制手机号每日注册次数!为了避免,某些手机号,频繁的申请注册,开启了redis数据库配置的定时器模式。下面是设计代码的案例展示。 1: package com.blog.utils;import org.slf4…...

IntelliJ+SpringBoot项目实战(28)--整合Beetl模板框架

在前面的文章里介绍过freemarker,thymeleaf模板引擎,本文介绍另一个性能超高的模板引擎---Beetl,据说此模板引擎的性能远超Freemarker。官网的说法是,Beetl 远超过主流java模板引擎性能(引擎性能5-6倍于FreeMarker,2倍…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解

本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...

Element Plus 表单(el-form)中关于正整数输入的校验规则

目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入&#xff08;联动&#xff09;2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...

Python Einops库:深度学习中的张量操作革命

Einops&#xff08;爱因斯坦操作库&#xff09;就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库&#xff0c;用类似自然语言的表达式替代了晦涩的API调用&#xff0c;彻底改变了深度学习工程…...

LabVIEW双光子成像系统技术

双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制&#xff0c;展现出显著的技术优势&#xff1a; 深层组织穿透能力&#xff1a;适用于活体组织深度成像 高分辨率观测性能&#xff1a;满足微观结构的精细研究需求 低光毒性特点&#xff1a;减少对样本的损伤…...

人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent

安全大模型训练计划&#xff1a;基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标&#xff1a;为安全大模型创建高质量、去偏、符合伦理的训练数据集&#xff0c;涵盖安全相关任务&#xff08;如有害内容检测、隐私保护、道德推理等&#xff09;。 1.1 数据收集 描…...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...

c# 局部函数 定义、功能与示例

C# 局部函数&#xff1a;定义、功能与示例 1. 定义与功能 局部函数&#xff08;Local Function&#xff09;是嵌套在另一个方法内部的私有方法&#xff0c;仅在包含它的方法内可见。 • 作用&#xff1a;封装仅用于当前方法的逻辑&#xff0c;避免污染类作用域&#xff0c;提升…...