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);//关闭摄像头
	
}
Logo

更多推荐