您的位置:首页 > 运维架构 > Linux

基于Video4Linux的视频采集模块开发

2012-08-21 13:09 260 查看
原文

linux系统中,摄像头驱动程序安装好后,为了进行视频采集必须加入Video4Linux模块,从而可以通过Video4Linux模块提供的编程接口(API)从摄像头设备中获取图像帧。下面具体研究基于V4L的视频采集程序设计。

1 Video4Linux概述

Video4Linux是Linux中关于视频设备的内核驱动,为针对视频设备的应用程序编程提供一系列接口函数,在Linux下,视频采集设备的正常使用依赖Video4-Linux标准的支持,在编译内核时要选中Video4Linux项,对应的设备文件是/dev/video。对于USB口摄像头,其驱动程序中需要提供基本的I/O操作接口函数open、read、write、close的实现。对中断的处理实现,内存映射功能以及对I/O通道的控制接口函数ioctl的实现等,并把它们定义在structfile
operations中。这样当应用程序对设备文件进行诸如open、read、write、close等系统调用时,Linux内核将通过file operations结构访问驱动程序提供的函数。

2.视频编程的数据结构及函数

Linux下V4L视频采集,主要是调用V4L模块参数进行视频原始数据采集。使用的一些主要参数和函数定义在系统/include/linux/videodev.h文件中。下面是videodev.h中定义的几个重要的数据结构如下:

[cpp]
view plaincopyprint?

typedef struct v4l_struct { int fd; struct video_capability capability; struct video_channel channel[4]; struct video_picture picture; struct video_window window; struct video_capture capture; struct video_buffer buffer; struct video_mmap mmap; struct video_mbuf mbuf; unsigned char *map; int framestat[2]; }v4l_device;

typedef struct v4l_struct
{
int   fd;
struct video_capability capability;
struct video_channel channel[4];
struct video_picture picture;
struct video_window window;
struct video_capture capture;
struct video_buffer buffer;

struct video_mmap mmap;
struct video_mbuf mbuf;
unsigned char *map;
int framestat[2];
}v4l_device;

video_capability结构包含设备的基本信息,如设备名称、支持的最大最小分辨率、信号源信息等。它包含以下分量:name[32]指定设备名称,maxwidth,maxheight,minwidth,minheight指定摄像头能捕捉的最大和最小的图像尺寸。Channels指定信号源个数,type表示所截取的图像是否能被捕捉,是彩色还是黑白,是否能裁剪,值如VID
TYPE CAPTURE等。

video_channel结构指定各个信号源的属性,它包含以下分量:channel指定信号源的编号,name为每个信号源的名称,Type指定所输入的信号类型,包含两种类型:VIDEO_TYPE_TV表示为电视信号;VIDEO_TYPE_CAMERA表示为摄像头信号。Norm表示电视信号所使用的制式,有VIDEO_MODE_PAL、VIDEO_MODE_NTSC、VIDEO_MODE_SECAM、VIDEO_MODE_AUTO等几

种选项。

video_picture结构用来设置采集图像的各种属性。它包括以下几个分量:brightness、hue、colour、contrast来为采集的图像提供类似电视信号的控制,whiteness来为灰度图像提供额外控制。

video_window结构设置采集的图像的显示方式。它包括以下几个分量:X用来描述窗口左上方的X坐标,Y用来描述左上方的Y坐标,width用来描述采集图像的宽度,height用来描述采集图像的高度,当采用色度键时chromakey用来表示颜色,用RGB32格式表示的。

video_mbuf结构用来描述利用mmap进行映射的帧的信息,实际上是输入到摄像头存储器缓冲区中的帧信息。它包含以下几个分量:size表示每帧大小,frames用来表示最多支持的帧数,offsets表示每帧相对基址的偏移。

video_buffer结构用来对最底层的buffer进行描述,它包含以下几个含量:void *baseBase描述buffer的物理地址,height描述frame buffer的高度,width描述frame buffer的宽度,depth描述frame buffer的深度,bytesperline描述每两个相邻的线之间的存储器字节数。

video mmap用于内存映射。

在V4L编程中,与图像设备交互主要是依靠系统调用ioctl函数来实现的。Ioctl(input/output control)函数是通过打开的文件描述符对各种文件尤其是字符设备文件进行控制,完成特定的I/O操作。V4L支持的ioctl命令通常需要几个参数,一般情况下,第一个参数fd代表打开的文件/设备,第二个参数cmd为驱动程序的特殊命令,有的还有第3个或者更多的参数,根据不同的驱动程序不同。

在V4L中定义了一些重要的宏,用来作为ioctl函数的第二个参数,用来操纵设备,一些主要的宏定义说明如下:

1)VIDIOCGCAP:Ioctl操作得到图像设备的基本信息,保存在video_capability结构体中。

2)VIDIOCSCAP:Ioctl操作根据video_capability结构体中的值来设置图像设备初始参数。

3)VIDIOCGPICT:Ioctl操作得到图像的基本信息,保存在video_picture中,包括图像的亮度、色度、对比度等。

4)VIDIOCSPICT:Iocfl操作根据video_picture结构体中的值来设置采集图像的初始参数。

5)VIDIOCGMBUF:Ioctl操作获取摄像头存储缓冲区的帧信息。

6)VIDIOCAMCAPTURE:获取视频图像。

7)VIDIOCGWIN:loctl操作得到采集图像区域的基本信息,保存在video_window结构体中。设置目标窗口的属性。

8)VIDIOCSWIN:Ioctl操作根据video_window结构体中的值来设置采集图像区域大小等参数。

9)VIDIOSYNC:Ioctl操作判断视频图像是否截取成功。

视频采集函数采用自顶向下的操作方式,首先规划并定义这些函数。

1)v41_open() :开启视频采集设备函数

首先定义了一个默认的设备文件路径,如果可以开启“/dev/video0"则将取回的信息放到vd中。

[cpp]
view plaincopyprint?

#define DEFAULT_DEVICE "dev/video0"

int v4l_open(char *dev,v4l_device *vd)
{
if(!dev)
dev=DEFAULT_DEVICE;
if((vd->fd=open(dev,O_RDWR))<0)
{
perror("camera open:");
return-1;
}
if(v4l_get_capability(vd))
return -1;
if(v41_get_picture(vd))
return -1;
return 0;
}

#define DEFAULT_DEVICE "dev/video0"
int v4l_open(char *dev,v4l_device *vd)
{
if(!dev)
dev=DEFAULT_DEVICE;
if((vd->fd=open(dev,O_RDWR))<0)
{
perror("camera open:");
return-1;
}
if(v4l_get_capability(vd))
return -1;
if(v41_get_picture(vd))
return -1;
return 0;
}

当应用程序输入的dev设备文件参数不存在时,就使用“/dev/video0”这个值。Linux下对设备文件的打开是通过open()函数完成的。

[cpp]
view plaincopyprint?

if((vd->fd=open(dev,O_RDWR))<0) { perror("camera_open:"); return-1; }

if((vd->fd=open(dev,O_RDWR))<0)
{
perror("camera_open:");
return-1;
}

设备文件开启后,将回传的文件描述符放到vd->fd里。在应用程序中使用v4l_open()函数时,需要先声明一个video_device类型的设备变量,然后通过调用v4l_open()函数将设备打开。如果可以正常打开设备文件,设备变量也就获得了相应的回传信息。v41_open()函数被调用的程序如下:

[cpp]
view plaincopyprint?

video_device vd; if(camera_open('‘/dev/video0”,&vd)) { return-1; }
video_device vd;
if(camera_open('‘/dev/video0”,&vd))
{
return-1;
}

2)v4l_get_capability():获取设备信息

成功开启设备后,首先取得设备信息,v4l_get_capability()函数调用ioctl()取得设备文件相关信息,并且将取得的信息放到

[cpp]
view plaincopyprint?

video_capability结构体中。 int v4l_get_capability(v4l_device *vd) { if(ioctl(vd->fd,VIDEOCGCAP,&(vd->capability))<0) { perror("camera_get_capability:"); return-1; } return 0; }

video_capability结构体中。
int v4l_get_capability(v4l_device *vd)
{
if(ioctl(vd->fd,VIDEOCGCAP,&(vd->capability))<0)
{
perror("camera_get_capability:");
return-1;
}
return 0;
}

这个函数的主要内容是ioctl(vd->fd,VIDEOCGCAP,&(vd->capability))。vd->fd是由v4l_open()传回来的文件描述符,而传递VIDIOCGCAP给ioctl()则会传回设备相关信息,存放与vd->capability。

3)v4l_get_picture():图像初始化

取得设备信息后,还要取得图像信息。所谓图像信息是指输入到视频捕捉设备的图像格式。

[cpp]
view plaincopyprint?

int v4l_get_picture(v4l_device *vd) { if(ioctl(vd->fd,VIDIOCGPICT,&(vd->picture))<0) { perror(“camera_get_picture:"); return-1; } return 0; }

int v4l_get_picture(v4l_device *vd)
{
if(ioctl(vd->fd,VIDIOCGPICT,&(vd->picture))<0)
{
perror(“camera_get_picture:");
return-1;
}
return 0;
}

4)改变video_picture中的分量的值,先为分量赋新值,再传递VIDIOCSPICT给ioctl()函数进行设置。

[cpp]
view plaincopyprint?

vd->picture.colour=65535; if(ioctl(vd->fd,VIDIOCSPICT,&(vd->picture))<0) { perror("VIDIOCSPICT"); return-1; }

vd->picture.colour=65535;
if(ioctl(vd->fd,VIDIOCSPICT,&(vd->picture))<0)
{
perror("VIDIOCSPICT");
return-1;
}

5)v4l_get_channels():初始化channel

[cpp]
view plaincopyprint?

int v4l_get_channels(camera_device *vd)
{
int i;
for(i=0;i < vd->capability.channels;i++){
vd->channel[i].channel=i;
if(ioctl(vd->fd,VIDIOCGCHAN,&(vd->channel[i]))<0)
{
perror("camera_get_channel:");
return -1;
}}
return 0;
}

int v4l_get_channels(camera_device *vd)
{
int i;
for(i=0;i < vd->capability.channels;i++){
vd->channel[i].channel=i;
if(ioctl(vd->fd,VIDIOCGCHAN,&(vd->channel[i]))<0)
{
perror("camera_get_channel:");
return -1;
}}
return 0;
}

6)v4l_close():关闭设备

[cpp]
view plaincopyprint?

int v4l_close(camera_device *vd) { close(vd->fd); return 0; }
int v4l_close(camera_device *vd)
{
close(vd->fd);
return 0;
}

3.图像采集流程

从V4L的数据结构看出,完成基于V4L的USB视频数据采集,先要获得相关的视频采集设备的信息和图像信息,并对采集窗口、颜色模式、帧状态初始化,然后才能进行视频图像的采集。

具体操作流程如下:

1)打开设备进行设定,通过使用标准的文件打开函数操作;

2)查询图像缓冲区信息并设定;VIDIOCSFBUF主要是设定图像缓冲区的基地址和缓冲区大小;

3)查询图像截取窗口信息并设定:VIDIOCGWIN和VIDIOCSWIN主要设定截取图像尺寸和位置;

4)查询通道信息并设定,可以从一个或者多个通道捕获数据,来进行通道的查询设定函数VIDEOCGCHAN和VIDEOCSCHAN;

5)获取图像并存放在缓冲区。

在V4L中图像截取方式有两种:

1)通过调用read具有阻塞功能的函数,将输出图像数据复制到预先设定好的数据缓冲区中,就可以实现对每帧图像数据的读取;

2)用mmap方式,直接将设备文件/dev/video0映射到内存中,不需要额外的对数据缓冲区进行复制工作,加快了图像信息的捕捉速度。另外,mmap()系统调用使得进程之间通过映射同一文件实现共享内存,各个进程可以像访问普通内存一样对文件进行访问,访问时只需要使用指针而不用调用文件操作函数。

视频采集的流程如下图所示。



鉴于mmap()以上优点,本文采用的第2种方式,利用mmap()方式对视频进行采集与裁剪的操作如下:

1)先使用ioctl(vd->fd,VIDIOCGMBUF,&vd->mbuf)数获得摄像头存储缓冲区的帧信息,之后修改video_map中的设置,例如重新设置图像帧的垂直及水平分辨率、彩色显示格式以及当前帧的状态。可利用如下语句

[cpp]
view plaincopyprint?

vd->mmap.height=240; vd->mmap.width=320; vd->mmap.format=VIDEO_PALETTE_RGB24; vd->framestat[0]=vd->framestat[1]=0; vd->flame=0;

vd->mmap.height=240;
vd->mmap.width=320;
vd->mmap.format=VIDEO_PALETTE_RGB24;
vd->framestat[0]=vd->framestat[1]=0;
vd->flame=0;

2)接着把摄像头对应的设备文件映射到内存区,命令为

[cpp]
view plaincopyprint?

vd->map=(unsignedchar *)mmap(0,vd->mbuf.size,PROT_READ|PROT_WRITE,MAP_SHARED,vd->fd,0)

vd->map=(unsignedchar *)mmap(0,vd->mbuf.size,PROT_READ|PROT_WRITE,MAP_SHARED,vd->fd,0)

这样设备文件的内容就映射到内存区,映射内容区可读可写并且不同进程间可共享。该函数成功时返回映像到内存区的指针,失败时返回1。mmap()函数中第一个参数表示共享内存的起始地址,在此外设为0表示由系统分配。第二个参数表示映射到调用进程地址空间的字节数,此值为vd->mbuf.size。第三个参数指定共享内存的访问权限,它有以下几个值PORT_READ(可读),PROT_WRITE(可写),PROT_EXEC(可执行),此处设为可读和可写。第四个参数指定共享内存的属性,有MAP_SHARED、MAP
_PRIVATE、MAP_FIXED三种属性,一般情况下从中选一个使用。

3)视频截取。把视频映射到内存后就可进行视频的截取。命令为

[cpp]
view plaincopyprint?

ioctl(vd->fd,VIDIOCMCAPTRUE,&(vd->mmap));

ioctl(vd->fd,VIDIOCMCAPTRUE,&(vd->mmap));

若调用成功,开始一帧的截取,该操作是非阻塞的,是否截取完毕留给VIDIOCSYNC来判断。

4)调用VIDEOCSYNC等待一帧截取结束。

[cpp]
view plaincopyprint?

if(ioctl(vd->fd,VIDIOCSYNC,&frame)<0) { perror("VIDIOCSYNC ERRORI!!”); return -1; }

if(ioctl(vd->fd,VIDIOCSYNC,&frame)<0)
{
perror("VIDIOCSYNC ERRORI!!”);
return -1;
}

函数调用成功,表明一帧图像截取完成,可以开始进行下一次视频截取。其中frame是当前截取的帧的序号。

5)由于采集是采用双缓冲的方式,这样在处理一帧时可以采集另一帧。frame表示当前采集的是哪一帧图像,framestat[2]表示帧的状态(如未开始采集和等待采集结束)。帧在内存中的地址由vd->map+vd->mbuf.offsets[vd->frame]确定。采集结束后调用munmap命令取消映射:munmap(vd->map,vd->mbuf.size)。

以上详细分析了图像采集流程,下面给出图像采集的具体实现。

4.摄像头采集模块的具体实现

视频采集的具体实现过程如下:

第一部分,设备初始化。

(1)首先:必须声明包含2个头文件:

[cpp]
view plaincopyprint?

#include<sys/types.h> #include<linux/videodev.h>

#include<sys/types.h>
#include<linux/videodev.h>

(2)然后,进行设备初始化,打开视频设备,摄像头在系统中对应的设备文件为/dev/video0,通过调用驱动程序中定义的open操作来完成。如下:

[cpp]
view plaincopyprint?

int fd=open("/dev/video”,O_RDW-R);

int fd=open("/dev/video”,O_RDW-R);

其中fd是设备打开后返回的文件描述符,系统调用函数可使用它对设备文件进行操作。

(3)接着通过调用ioctl VIDIOCGCAP操作读取struct video_capability中有关摄像头信息。如下:

[cpp]
view plaincopyprint?

struct video_capability grab_capability; ioctl(fd,VIDIOCGCAP,&grab_capability);/*获得struct video_capability中的摄像头的信息*/

struct video_capability grab_capability;
ioctl(fd,VIDIOCGCAP,&grab_capability);/*获得struct video_capability中的摄像头的信息*/

(4) 然后通过调用ioctl VIDIOCSFBUF设置内存缓冲区的相关信息,缓冲区的信息可以通过printf函数输出;

(5)通过调用Ioctl VIDIOCSWIN来完成对视频窗口的设置,如窗口宽度,高度等;

(6)接着通过调用loctl VIDIOCGPICT操作读取struct video_capability中有关图像信息。如下:

[cpp]
view plaincopyprint?

struct video_picture grab_picture; ioctl(fd,VIDIOCGPICT,&grab_picture);/*获取图像信息*/

struct video_picture grab_picture;
ioctl(fd,VIDIOCGPICT,&grab_picture);/*获取图像信息*/

(7)在获取图像信息后,还可以根据需要改变这些信息,例如对比度、亮度、调色板等,具体做法是先给video_picture中的相应变量赋新值,再利用Ioctl VIDIOCSPICT函数进行设置。如下:

[cpp]
view plaincopyprint?

grab_picture.colour=65535; if(ioctl(fd,VIDIOCSPICT,&grab_picture))<0) { perror("VIDIOCSPICT”); return-1; }

grab_picture.colour=65535;
if(ioctl(fd,VIDIOCSPICT,&grab_picture))<0)
{
perror("VIDIOCSPICT”);
return-1;
}

(8)接着初始化channel,必须先完成vd->capability中的信息调用,使用下列函数:

[cpp]
view plaincopyprint?

int v4l_get_channels(v4l_device *vd)

int v4l_get_channels(v4l_device *vd)

第二部分,使用mmap方式截取视频。

(1)首先调用ioctl(fd,VIDIOCGMBUF,&grab_vm)函数获取摄像头存储缓冲区的帧信息,之后初始化video_mbuf修改video_mmap中的设置,重新设置图像信息如帧的垂直及水平分辨率、彩色显示格式等。如下:

[cpp]
view plaincopyprint?

structvideo mmap grab_buf ; //以下为设置图像帧缓冲区信息

grab_buf.frame=0; //一次只采集一帧
grab_buf.height= 240; //图像高度

grab_buf.width=320; //图像宽度
grab_buf.format=VIDEO_PALETTE_RGB24; //图像的调色板格式,24位真彩色

unsigned char *data=mmap(0,240*320*3,PROT_READ| PROT_WRITE,MAP_SHARD,fd,0); //内存映射

ioctl(grab fd,VIDIOCMCAPTURE,&grab_buf); //采集图像

structvideo mmap grab_buf ;  //以下为设置图像帧缓冲区信息
grab_buf.frame=0;   //一次只采集一帧
grab_buf.height= 240;  //图像高度
grab_buf.width=320;   //图像宽度
grab_buf.format=VIDEO_PALETTE_RGB24;   //图像的调色板格式,24位真彩色
unsigned char *data=mmap(0,240*320*3,PROT_READ| PROT_WRITE,MAP_SHARD,fd,0); //内存映射
ioctl(grab fd,VIDIOCMCAPTURE,&grab_buf);  //采集图像

(2)然后调用ioctl(grab fd,VIDIOCSYNC,&frame)函数,该函数成功返回则表示采集完毕,采集到的图像数据放到以data为起始地址,长度为240*320*3的内存区域中,读取该内存中的数据便可得到图像数据。

(3)在此基础上同样可实现连续帧的采集,即一次采集连续多帧图像的数据。此时首先要设置grab_bur.frame为要采集的帧数。在循环语句中,也是使用ioctl VIOCMCAPTURE和ioctl VIDIOCSYNC操作完成每帧读取,但是要给采集到的每帧图像赋地址为data+grab_vm.offsets[frame],然后保存文件格式。其中grab_vm为video_mbuf结构体变量的一个声明,利用ioctl(fd,VIDIOCGMBUF,

&grab_vm)便可获得grab 的信息。

(4)若要继续采集可再加一个外循环,在外循环语句中只要给原来的内循环再赋伊grab_buf.frame=0即可。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: