Linux下摄像头应用编程

Linux下摄像头应用编程

  V4L2是Video for linux2的简称,为linux中关于视频设备的内核驱动。在Linux中,视频设备是设备文件,可以像访问普通文件一样对其进行读写,摄像头在/dev/video*下,如果只有一个视频设备,通常为/dev/video0。
  v4L2是针对uvc免驱usb设备的编程框架 ,主要用于采集usb摄像头等。

1.摄像头框架编程步骤

  (1)打开摄像头设备(/dev/video0 、/dev/video1 )。
  (2)设置图像格式:VIDIOC_S_FMT(视频捕获格式、图像颜色数据格式、图像宽和高)。
  (3)申请缓冲区:VIDIOC_REQBUFS(缓冲区数量、缓冲映射方式、视频捕获格式)。
  (4)将缓冲区映射到进程空间:VIDIOC_QUERYBUF(要映射的缓冲区下标、缓冲映射方式、视频捕获格式)。
  (5)将缓冲区添加到队列中:VIDIOC_QBUF(映射的缓冲区下标、缓冲映射方式、视频捕获格式)。
  (6)开启摄像头采集:VIDIOC_STREAMON (视频捕获格式)。
  (7)从采集队列中取出图像数据VIDIOC_DQBUF,进行图像渲染。

2.V4L2头文件信息

  V4L2是Linux下标准视频驱动框架,相关头文件信息在include/linux/videodev2.h中。
  V4L2 驱动对用户空间提供字符设备,主设备号为 81,对于视频设备,其次设备号为 0-63。除此之外,次设备号为 64-127 的 Radio 收音机设备,次设备号为 192-223 的是 Teletext 广播设备,次设备号为 224-255 的是 VBI视频消影设备。
  V4L2 驱动的 Video 设备在用户空间通过各种 ioctl 调用进行控制,并且可以使用 mmap 进行内存映射。

2.1 常用ioctl参数

设置视频捕获格式
#define VIDIOC_S_FMT _IOWR(‘V’, 5, struct v4l2_format)
向内核申请缓冲区
#define VIDIOC_REQBUFS _IOWR(‘V’, 8, struct v4l2_requestbuffers)
将缓冲区映射到进程空间
#define VIDIOC_QUERYBUF _IOWR(‘V’, 9, struct v4l2_buffer)
将缓冲区添加到采集队列
#define VIDIOC_QBUF _IOWR(‘V’, 15, struct v4l2_buffer)
从队列中获取图像数据
#define VIDIOC_DQBUF _IOWR(‘V’, 17, struct v4l2_buffer)
开启摄像头图像采集
#define VIDIOC_STREAMON _IOW(‘V’, 18, int)

2.2 核心结构体信息

  • struct v4l2_format

struct v4l2_format {
__u32 type;/* 类型V4L2_BUF_TYPE_VIDEO_CAPTURE*/
union {
struct v4l2_pix_format pix; /* V4L2_BUF_TYPE_VIDEO_CAPTURE视频捕获格式*/
struct v4l2_pix_format_mplane pix_mp; /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE /
struct v4l2_window win; /
V4L2_BUF_TYPE_VIDEO_OVERLAY /
struct v4l2_vbi_format vbi; /
V4L2_BUF_TYPE_VBI_CAPTURE /
struct v4l2_sliced_vbi_format sliced; /
V4L2_BUF_TYPE_SLICED_VBI_CAPTURE /
__u8 raw_data[200]; /
user-defined */
} fmt;
};

  • struct v4l2_pix_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_requestbuffers

//内存映射缓冲区
struct v4l2_requestbuffers {
__u32 count; //申请缓冲区个数
__u32 type; /* enum v4l2_buf_type 视频类型 /
__u32 memory; /
enum v4l2_memory 映射方式*/
__u32 reserved[2];
};

  • struct v4l2_buffer

//视频缓冲区信息
struct v4l2_buffer {
__u32 index;/数组下标/
__u32 type;/视频捕获格式/
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory;/映射格式/
union {
__u32 offset;/偏移量/
unsigned long userptr;
struct v4l2_plane *planes;
int fd;
} m;
__u32 length;/映射缓冲区大小/
__u32 input;
__u32 reserved;
};

2.3 图像颜色编码格式

  关于YUV格式图像参考:https://blog.csdn.net/sway913/article/details/120602052

  • YUV
      YUV,是一种颜色编码方法。常使用在各个视频处理组件中。 YUV在对照片或视频编码时,考虑到人类的感知能力,允许降低色度的带宽。
      YUV是编译true-color颜色空间(color space)的种类,Y’UV, YUV, YCbCr,YPbPr等专有名词都可以称为YUV,彼此有重叠。“Y”表示明亮度(Luminance或Luma),也就是灰阶值,“U”和“V”表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。
      YUV格式有两大类:planar(平面)和packed(交错)。
      对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
      对于packed的YUV格式,每个像素点的Y,U,V是连续交错存储的。
      YUV的主要优势在于可以兼容之前的黑白电视,单独只有Y数据就可以显示完整的黑白图像,UV是后期加入的色彩参数。
  • YUYV格式:YUV422
      YUV 4:2:2采样,表示在每4个像素中,Y采集4份,U采集2份,V采集2份。每两个 Y 分量共享一组 UV 分量。单个像素占用空间为:1byte(Y)+1/2byte(U)+1/2byte(V)=2字节;一帧图像占用的空间为:width * height * 2

    在这里插入图片描述

  • YUV420
      YUV420 每四个Y分量公用一个UV分量,并不是没有V分量,而是UV分量交替采样,所以每个像素点占用1.5个字节空间。根据planar(平面)和packed(交错)方式存储有4种存储方式。下面举其中一种示例说明:
       每4个Y共用一组UV分量,单个像素占用空间为:1byte(Y)+1/4byte(U)+1/4byte(V)=1.5字节;一帧图像占用的空间为:width * height * 3/2 byte。

    在这里插入图片描述

  • RGB
      RGB色彩模式是工业界的一种颜色标准,是通过对红®、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。
       常用的RGB格式:
  • RGB565:5位红色+6位绿色+5位蓝色=16位颜色值
  • RGB888:8位红色+8位绿色+8位蓝色=24真彩色
  • RGB32:RGB32是在RGB基础上附加上Alpha(透明度)通道,RGB个占8位,剩余8位用作Alpha通道。

在这里插入图片描述

3.摄像头编程示例

3.1 摄像头初始化

  打开摄像头设备,设置视频捕获格式,设置图像格式为YUYV422。

/*摄像头初始化*/
int Camera_Init(void)
{
	int i=0;
	/*1.打开摄像头设备*/
	int fd=open(VIDEO_DEV,2);
	if(fd<0)return -1;//摄像头打开失败
	/*2.设置摄像头捕获格式*/
	struct v4l2_format v4l2fmt;
	memset(&v4l2fmt,0,sizeof(v4l2fmt));//初始化结构体
	v4l2fmt.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
	v4l2fmt.fmt.pix.width=1920;//图像宽度
	v4l2fmt.fmt.pix.height=1080;//图像高度
	v4l2fmt.fmt.pix.pixelformat=V4L2_PIX_FMT_YUYV;//YUYV颜色编码格式
	if(ioctl(fd,VIDIOC_S_FMT,&v4l2fmt))return -2;//设置格式失败
	printf("采集图像大小:%d*%d\n",v4l2fmt.fmt.pix.width,v4l2fmt.fmt.pix.height);
	imag_w=v4l2fmt.fmt.pix.width;
	imag_h=v4l2fmt.fmt.pix.height;
	/*3.申请缓冲区*/
	struct v4l2_requestbuffers v4l2reqbuf;
    memset(&v4l2reqbuf,0,sizeof(v4l2reqbuf));//初始化结构体
	v4l2reqbuf.count=4;//申请的缓冲区个数
	v4l2reqbuf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
	v4l2reqbuf.memory=V4L2_MEMORY_MMAP;//内存映射
	if(ioctl(fd,VIDIOC_REQBUFS,&v4l2reqbuf))return -3;//申请缓冲区失败
	printf("申请的缓冲区个数:%d\n",v4l2reqbuf.count);
	/*4.将缓冲区映射到进程空间*/
	struct v4l2_buffer v4l2buf;
	memset(&v4l2buf,0,sizeof(v4l2buf));//初始化结构体
	v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
	v4l2buf.memory=V4L2_MEMORY_MMAP;//内存映射
	for(i=0;i<v4l2reqbuf.count;i++)
	{
		v4l2buf.index=i;/*缓冲区下标*/
		if(ioctl(fd,VIDIOC_QUERYBUF,&v4l2buf))return -4;//申请缓冲区失败
		video_buff[i]=mmap(NULL,v4l2buf.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,v4l2buf.m.offset);
		printf("video_buff[%d]=%p\n",i,video_buff[i]);
	}
	/*5.将映射的空间添加到采集队列*/
	memset(&v4l2buf,0,sizeof(v4l2buf));//初始化结构体
	v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
	v4l2buf.memory=V4L2_MEMORY_MMAP;//内存映射
	for(i=0;i<v4l2reqbuf.count;i++)
	{
		v4l2buf.index=i;/*缓冲区下标*/
		if(ioctl(fd,VIDIOC_QBUF,&v4l2buf))return -5;//添加到采集队列失败
	}
	/*6.开启摄像头*/
	int type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
	if(ioctl(fd,VIDIOC_STREAMON,&type))return -6;//启动摄像头失败
	return fd;//成功返回摄像头文件描述符
}

3.2 采集图像数据

  循环采集图像数据,将一帧图像数据保存为BMP图片。
   由于摄像头图像颜色格式为YUYV格式,BMP图片颜色格式为RGB,因此需要将YUYV转RGB再写入到文件中。

  • YUYV转换RGB函数
/*YUYV转RGB888*/
void yuv_to_rgb(unsigned char *yuv_buffer,unsigned char *rgb_buffer,int iWidth,int iHeight)
{
	int x;
	int z=0;
	unsigned char *ptr = rgb_buffer;
	unsigned char *yuyv= yuv_buffer;
	for (x = 0; x < iWidth*iHeight; x++)
	{
		int r, g, b;
		int y, u, v;
		if (!z)
		y = yuyv[0] << 8;
		else
		y = yuyv[2] << 8;
		u = yuyv[1] - 128;
		v = yuyv[3] - 128;
		r = (y + (359 * v)) >> 8;
		g = (y - (88 * u) - (183 * v)) >> 8;
		b = (y + (454 * u)) >> 8;
		*(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b);
		*(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g);
		*(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r);
		if(z++)
		{
			z = 0;
			yuyv += 4;
		}
	}
}
  • 图像采集和BMP图片编码
int main()
{
	int fd=Camera_Init();
	if(fd<0)
	{
		printf("初始化摄像头失败,res=%d\n",fd);
		return 0;
	}
	printf("初始化摄像头成功\n");
	struct v4l2_buffer v4l2buf;
	memset(&v4l2buf,0,sizeof(v4l2buf));//初始化结构体
	v4l2buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;//视频捕获格式
	v4l2buf.memory=V4L2_MEMORY_MMAP;//内存映射
	BMP_HEADER bmp_head;//bmp头数据
	BMP_INFO bmp_info;//位图数据
	memset(&bmp_head,0,sizeof(BMP_HEADER));
	memset(&bmp_info,0,sizeof(BMP_INFO));
	bmp_head.bfType='M'<<8|'B';//图片类型
	bmp_head.bfSize=imag_w*imag_h*3+sizeof(BMP_HEADER)+sizeof(BMP_INFO);
	bmp_head.bfOffBits=sizeof(BMP_INFO)+sizeof(BMP_HEADER);/*RGB颜色偏移量*/

	bmp_info.biSize=sizeof(BMP_INFO);//当前结构体大小
	bmp_info.biWidth=imag_w;//图片宽
	bmp_info.biHeight=imag_h;//图片高
	bmp_info.biPlanes=1;//固定为1
	bmp_info.biBitCount=24;//24位真彩色
	
	char *rgb=malloc(imag_w*imag_h*3);//存放rgb图像数据
	int count=1;
	char buff[20];
	FILE *fp;
	while(1)
	{
		/*从采集队列中取出图像数据*/
		if(ioctl(fd,VIDIOC_DQBUF,&v4l2buf))break;//取数据失败
		printf("v4l2buff[%d]=%p\n",v4l2buf.index,video_buff[v4l2buf.index]);
		/*将头数据和位图数据写入到文件中*/
		snprintf(buff,sizeof(buff),"./image/%d.bmp",count);//图片名字
		fp=fopen(buff,"w+b");
		if(fp==NULL)
		{
			printf("文件创建失败\n");
			continue;
		}
		count++;
		fwrite(&bmp_head,sizeof(BMP_HEADER),1,fp);//写头数据
		fwrite(&bmp_info,sizeof(BMP_INFO),1,fp);//写头数据
		/*将yuyv数据转换RGB888*/
		yuv_to_rgb(video_buff[v4l2buf.index],rgb,imag_w,imag_h);
		/*将颜色数据写入到文件中*/
		fwrite(rgb,imag_w*imag_h*3,1,fp);//写头数据
		fclose(fp);
		/*将缓冲区添加回采集队列中*/
		if(ioctl(fd,VIDIOC_QBUF,&v4l2buf))break;//添加到采集队列失败
	}
	close(fd);//关闭摄像头
	
}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340