转自:http://www.cnblogs.com/surpassal/archive/2012/12/19/zed_webcam_lab1.html

一直想把USB摄像头接到Zedboard上,搭建一个简易视频监控系统方便后续做视频处理。Xilinx官方给出了一个Webcam摄像头监控的例子,跑的是linaro,不知道是我的SD卡问题还是摄像头的问题,播放视频的时候总是会很卡,而且突然系统就死掉了。还是很喜欢自己动手,能学到新东西。Digilent官方给的OOB设计,那个精简的linux足够做简单的linux开发了,而且OOB设计中USB驱动和V4L(Video for Linux)都提供好了。这几天找了一些的V4L的资料,完成了摄像头的单帧图片采集,接下来要做的是QT界面显示和视频流的显示了,最终的计划是完成视频采集、编码、存储和以太网传输。希望能有时间和精力完成这么多。先把这几天的做的东西整理出来和大家分享。为了方便大家对程序的理解,先简单介绍一下linux下的V4L2的一些知识,然后再详细介绍V4L2编程。

更多更新请关注我的博客:@超群天晴 http://www.cnblogs.com/surpassal/

相关阅读 :

(原创)基于ZedBoard的Webcam设计(一):USB摄像头(V4L2接口)的图片采集

(原创)基于ZedBoard的Webcam设计(二):USB摄像头图片采集+QT显示

(原创)基于ZedBoard的Webcam设计(三):视频的采集和动态显示

(原创)基于ZedBoard的Webcam设计(四):MJPG编码和AVI封装

(原创)基于ZedBoard的Webcam设计(五):x264编码在zedboard上的实现(软编码)

硬件平台:Digilent ZedBoard + USB 摄像头

开发环境:Windows XP 32 bit + Wmare 8.0 + Ubuntu 10.04 + arm-linux-xilinx-gnueabi交叉编译环境

Zedboard linux: Digilent OOB Design 

一、一些知识

1、V4L和V4L2。

V4L是Linux环境下开发视频采集设备驱动程序的一套规范(API),它为驱动程序的编写提供统一的接口,并将所有的视频采集设备的驱动程序都纳入其的管理之中。V4L不仅给驱动程序编写者带来极大的方便,同时也方便了应用程序的编写和移植。V4L2是V4L的升级版,由于我们使用的OOB是3.3的内核,不再支持V4L,因而编程不再考虑V4L的api和参数定义。

2、YUYV与RGB24

RGB是一种颜色的表示法,计算机中一般采用24位来存储,每个颜色占8位。YUV也是一种颜色空间,为什么要出现YUV,主要有两个原因,一个是为了让彩色信号兼容黑白电视机,另外一个原因是为了减少传输的带宽。YUV中,Y表示亮度,U和V表示色度,总之它是将RGB信号进行了一种处理,根据人对亮度更敏感些,增加亮度的信号,减少颜色的信号,以这样“欺骗”人的眼睛的手段来节省空间。YUV到RGB颜色空间转换关系是:

R = Y + 1.042*(V-128);
G = Y - 0.34414*(U-128) - 0.71414*(V-128);
B = Y + 1.772*(U-128);

YUV的格式也很多,不过常见的就是422、420等。YUYV就是422形式,简单来说就是,两个像素点P1、P2本应该有Y1、U1、V1和Y2、U2、V2这六个分量,但是实际只保留Y1、U1、Y2、V2。

图1 YUYV像素

二、应用程序设计

先定义一些宏和结构体,方便后续编程

 1 #define  TRUE    1
2 #define FALSE 0
3
4 #define FILE_VIDEO "/dev/video0"
5 #define BMP "/usr/image_bmp.bmp"
6 #define YUV "/usr/image_yuv.yuv"
7
8 #define IMAGEWIDTH 640
9 #define IMAGEHEIGHT 480
10
11 static int fd;
12 static struct v4l2_capability cap;
13 struct v4l2_fmtdesc fmtdesc;
14 struct v4l2_format fmt,fmtack;
15 struct v4l2_streamparm setfps;
16 struct v4l2_requestbuffers req;
17 struct v4l2_buffer buf;
18 enum v4l2_buf_type type;
19 unsigned char frame_buffer[IMAGEWIDTH*IMAGEHEIGHT*3];

其中

#define FILE_VIDEO     "/dev/video0"

是要访问的摄像头设备,默人都是/dev/video0

#define BMP          "/usr/image_bmp.bmp"
#define YUV "/usr/image_yuv.yuv"

是采集后存储的图片,为了方便测试,这里将直接获取的yuv格式数据也保存成文件,可以通过yuvviewer等查看器查看。

static   int      fd;
static struct v4l2_capability cap;
struct v4l2_fmtdesc fmtdesc;
struct v4l2_format fmt,fmtack;
struct v4l2_streamparm setfps;
struct v4l2_requestbuffers req;
struct v4l2_buffer buf;
enum v4l2_buf_type type;

这些结构体的定义都可以从/usr/include/linux/videodev2.h中找到定义,具体含义在后续编程会做相应解释。

#define  IMAGEWIDTH    640
#define IMAGEHEIGHT 480

为采集图像的大小。

定义一个frame_buffer,用来缓存RGB颜色数据

unsigned char frame_buffer[IMAGEWIDTH*IMAGEHEIGHT*3]

这些宏和定义结束后,就可以开始编程配置摄像头并采集图像了。一般来说V4L2采集视频数据分为五个步骤:首先,打开视频设备文件,进行视频采集的参数初始化,通过V4L2接口设置视频图像的采集窗口、采集的点阵大小和格式;其次,申请若干视频采集的帧缓冲区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据;第三,将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集;第四,驱动开始视频数据的采集,应用程序从视频采集输出队列取出帧缓冲区,处理完后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据;第五,停止视频采集。在本次设计中,定义了三个函数实现对摄像头的配置和采集。

int init_v4l2(void);
int v4l2_grab(void);
int close_v4l2(void);

同时由于采集到的图像数据是YUYV格式,需要进行颜色空间转换,定义了转换函数。

int yuyv_2_rgb888(void);

下面就详细介绍这几个函数的实现。

1、初始化V4l2

(1)打开视频。linux对摄像头的访问和普通设备一样,使用open函数就可以,返回值是设备的id。

1 if ((fd = open(FILE_VIDEO, O_RDWR)) == -1)
2 {
3 printf("Error opening V4L interface\n");
4 return (FALSE);
5 }

(2)读video_capability中信息。通过调用IOCTL函数和接口命令VIDIOC_QUERYCAP查询摄像头的信息,结构体v4l2_capability中有包括驱动名称driver、card、bus_info、version以及属性capabilities。这里我们需要检查一下是否是为视频采集设备V4L2_CAP_VIDEO_CAPTURE以及是否支持流IO操作V4L2_CAP_STREAMING。

 1 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1)
2 {
3 printf("Error opening device %s: unable to query device.\n",FILE_VIDEO);
4 return (FALSE);
5 }
6 else
7 {
8 printf("driver:\t\t%s\n",cap.driver);
9 printf("card:\t\t%s\n",cap.card);
10 printf("bus_info:\t%s\n",cap.bus_info);
11 printf("version:\t%d\n",cap.version);
12 printf("capabilities:\t%x\n",cap.capabilities);
13
14 if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE)
15 {
16 printf("Device %s: supports capture.\n",FILE_VIDEO);
17 }
18
19 if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING)
20 {
21 printf("Device %s: supports streaming.\n",FILE_VIDEO);
22 }
23 }

(3)列举摄像头所支持像素格式。使用命令VIDIOC_ENUM_FMT,获取到的信息通过结构体v4l2_fmtdesc查询。这步很关键,不同的摄像头可能支持的格式不一样,V4L2可以支持的格式很多,/usr/include/linux/videodev2.h文件中可以看到。

1 fmtdesc.index=0;
2 fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
3 printf("Support format:\n");
4 while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
5 {
6 printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
7 fmtdesc.index++;
8 }

(4)设置像素格式。一般的USB摄像头都会支持YUYV,有些还支持其他的格式。通过前一步对摄像头所支持像素格式查询,下面需要对格式进行设置。命令为VIDIOC_S_FMT,通过结构体v4l2_format把图像的像素格式设置为V4L2_PIX_FMT_YUYV,高度和宽度设置为IMAGEHEIGHT和IMAGEWIDTH。一般情况下一个摄像头所支持的格式是不可以随便更改的,我尝试把把一个只支持YUYV和MJPEG的摄像头格式改为RGB24或者JPEG,都没有成功。

 1 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
2 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
3 fmt.fmt.pix.height = IMAGEHEIGHT;
4 fmt.fmt.pix.width = IMAGEWIDTH;
5 fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
6
7 if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
8 {
9 printf("Unable to set format\n");
10 return FALSE;
11 }

为了确保设置的格式作用到摄像头上,再通过命令VIDIOC_G_FMT将摄像头设置读取回来。

 1 if(ioctl(fd, VIDIOC_G_FMT, &fmt) == -1)
2 {
3 printf("Unable to get format\n");
4 return FALSE;
5 }
6 {
7 printf("fmt.type:\t\t%d\n",fmt.type);
8 printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
9 printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);
10 printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);
11 printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);
12 }

完整的初始化代码如下:

2、图像采集

(1)申请缓存区。使用参数VIDIOC_REQBUFS和结构体v4l2_requestbuffers。v4l2_requestbuffers结构中定义了缓存的数量,系统会据此申请对应数量的视频缓存。

1 req.count=4;
2 req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
3 req.memory=V4L2_MEMORY_MMAP;
4 if(ioctl(fd,VIDIOC_REQBUFS,&req)==-1)
5 {
6 printf("request for buffers error\n");
7
8 }

(2)获取每个缓存的信息,并mmap到用户空间。定义结构体

struct buffer
{
void * start;
unsigned int length;
} * buffers;

来存储mmap后的地址信息。需要说明的是由于mmap函数定义时返回的地址是个void *,因而这里面的start也是个 void *。实际地址在运行的时候会自动分配。

 1 for (n_buffers = 0; n_buffers < req.count; n_buffers++)
2 {
3 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
4 buf.memory = V4L2_MEMORY_MMAP;
5 buf.index = n_buffers;
6 //query buffers
7 if (ioctl (fd, VIDIOC_QUERYBUF, &buf) == -1)
8 {
9 printf("query buffer error\n");
10 return(FALSE);
11 }
12
13 buffers[n_buffers].length = buf.length;
14 //map
15 buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ |PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
16 if (buffers[n_buffers].start == MAP_FAILED)
17 {
18 printf("buffer map error\n");
19 return(FALSE);
20 }
21 }

(3) 之后就可以开始采集视频了。使用命令VIDIOC_STREAMON。

1 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
2 ioctl (fd, VIDIOC_STREAMON, &type);

(4)取出缓存中已经采样的缓存。使用命令VIDIOC_DQBUF。视频数据存放的位置是buffers[n_buffers].start的地址处。

1 ioctl(fd, VIDIOC_DQBUF, &buf);

完整的采集代码:

3、YUYV转RGB24

由于摄像头采集的数据格式为YUYV,为了方便后续设计,需要转变为RGB24,并将转换完成的数据存储到frame_buffer中。值得一提的是,由于定义的时候buffers[index].start是个void *,没有办法进行+1这样的操作,需要强制转换为

char * pointer
pointer = buffers[0].start

由于后续RGB的数据要存储到BMP中,而BMP文件中颜色数据是“倒序”,即从下到上,从左到右,因而在向frame_buffer写数据时是从最后一行最左测开始写,每写满一行行数减一。

4、停止采集和关闭设备

使用命令VIDIOC_STREAMOFF停止视频采集,并关闭设备。

 1 int close_v4l2(void)
2 {
3 ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
4 if(fd != -1)
5 {
6 close(fd);
7 return (TRUE);
8 }
9 return (FALSE);
10 }

5、主函数

需要把我们采集到图像数据存储成图片,为了方便调试,先将原始的数据存储为yuv格式文件,再将转换成RGB后的数据存储为BMP。定义BMP头结构体

 1 typedef struct tagBITMAPFILEHEADER{
2 WORD bfType; // the flag of bmp, value is "BM"
3 DWORD bfSize; // size BMP file ,unit is bytes
4 DWORD bfReserved; // 0
5 DWORD bfOffBits; // must be 54
6
7 }BITMAPFILEHEADER;
8
9
10 typedef struct tagBITMAPINFOHEADER{
11 DWORD biSize; // must be 0x28
12 DWORD biWidth; //
13 DWORD biHeight; //
14 WORD biPlanes; // must be 1
15 WORD biBitCount; //
16 DWORD biCompression; //
17 DWORD biSizeImage; //
18 DWORD biXPelsPerMeter; //
19 DWORD biYPelsPerMeter; //
20 DWORD biClrUsed; //
21 DWORD biClrImportant; //
22 }BITMAPINFOHEADER;

完整的主函数

//@超群天晴
//http://www.cnblogs.com/surpassal/
int main(void)
{ FILE * fp1,* fp2; BITMAPFILEHEADER bf;
BITMAPINFOHEADER bi; fp1 = fopen(BMP, "wb");
if(!fp1)
{
printf("open "BMP"error\n");
return(FALSE);
} fp2 = fopen(YUV, "wb");
if(!fp2)
{
printf("open "YUV"error\n");
return(FALSE);
} if(init_v4l2() == FALSE)
{
return(FALSE);
} //Set BITMAPINFOHEADER
bi.biSize = 40;
bi.biWidth = IMAGEWIDTH;
bi.biHeight = IMAGEHEIGHT;
bi.biPlanes = 1;
bi.biBitCount = 24;
bi.biCompression = 0;
bi.biSizeImage = IMAGEWIDTH*IMAGEHEIGHT*3;
bi.biXPelsPerMeter = 0;
bi.biYPelsPerMeter = 0;
bi.biClrUsed = 0;
bi.biClrImportant = 0; //Set BITMAPFILEHEADER
bf.bfType = 0x4d42;
bf.bfSize = 54 + bi.biSizeImage;
bf.bfReserved = 0;
bf.bfOffBits = 54; v4l2_grab();
fwrite(buffers[0].start, 640*480*2, 1, fp2);
printf("save "YUV"OK\n"); yuyv_2_rgb888();
fwrite(&bf, 14, 1, fp1);
fwrite(&bi, 40, 1, fp1);
fwrite(frame_buffer, bi.biSizeImage, 1, fp1);
printf("save "BMP"OK\n"); fclose(fp1);
fclose(fp2);
close_v4l2(); return(TRUE);
}

 三、PC测试

程序编写完后,可以先在PC上做测试(实际整个调试过程都是在PC上,直道最后PC上能实现功能再挪到ZedBoard上的)。PC上测试的结果

在/usr目录下可以查看到采集到的图片

四、Zedboard测试

PC上测试OK后,可以“挪”到ZedBoard上了。使用arm-xilinx-linux交叉编译环境对源文件进行交叉编译,将生成的可执行文件拷贝到ZedBoard上运行即可。

使用命令

arm-xilinx-linux-gnueabi-gcc v4l2grab.c -o zed-camera

对程序进行编译,编译通过后将生成的可执行文件zed-camera拷贝到到ZedBoard上,并将USB摄像头连接到ZedBoard上,通过命令

ls /dev 

查看dev目录下的是否有video0设备。如果有,可以运行可执行文件了。在运行前我比较习惯获得可执行文件的权限,使用命令

chmod +x zed-camera

参数+x的意思是这个文件对于当前用户是可执行的。也可以使用

chmod 777 zed-camera

这样所有用户都有读写执行的权限。使用命令

./zed-camera

执行可执行程序,程序运行,并输出以下信息:

zynq> ./zed-camera
[ 318.290000] usb 1-1.3: reset high-speed USB device number 3 using xusbps-ehci driver: uvcvideo
card: UVC Camera (046d:0825)
bus_info: usb-xusbps-ehci.0-1.3
version: 197376
capabilities: 4000001
Device /dev/video0: supports capture.
Device /dev/video0: supports streaming.
Support format:
1.YUV 4:2:2 (YUYV)
2.MJPEG
fmt.type: 1
pix.pixelformat: YUYV
pix.height: 480
pix.width: 640
pix.field: 1
init /dev/video0 [OK]
grab yuyv OK
save /usr/image_yuv.yuv OK
change to RGB OK
save /usr/image_bmp.bmp OK

可以看到我使用的USB摄像支持YUYV和MJPEG两种格式。我也试过其他USB摄像头,大部分都只支持YUYV而不支持MJPEG或者RGB24。

采集到的图片默认是在/usr目录下的,将其拷贝出来

cp /usr/image* /mnt

再PC上查看,效果还不错

=============================

完整工程和代码:lab_v4l2_yuyv.zip

可以指定任意分辨率摄像头的代码:v4l2grab_Anysize.rar 《感谢@jiaquwang分享》

最新文章

  1. can&#39;t run roscore 并且 sudo 指令返回 unable to resolve host
  2. Git 相关总结
  3. 安装Python环境时遇到的问题
  4. 使用CodeIgniter框架搭建RESTful API服务
  5. c 函数及指针学习 9
  6. AngularJs记录学习01
  7. stardict dict
  8. 使用Machin公式计算
  9. SharePoint的安装配置
  10. 分析java 嵌套类与内部类
  11. es6 语法
  12. java本地方法
  13. poj3270Cow Sorting(置换+贪心)
  14. java proxy 转包
  15. 洗礼灵魂,修炼python(64)--爬虫篇—re模块/正则表达式(2)
  16. linux的LNMP架构介绍、MySQL安装、PHP安装
  17. jquery 学习(二) - 属性操作
  18. 深度排序与alpha混合
  19. IT小小鸟读后感言
  20. php if语句判定my查询是否为空

热门文章

  1. 完善好的web项目(校园包车)
  2. OS X(10.10) python3.4 matplotlib的安装
  3. Beta阶段冲刺三
  4. Linux命令(九)比较文件差异 diff
  5. linux 解压文件
  6. ef 更新数据库
  7. Valid BFS? CodeForces - 1037D(思维 bfs)
  8. Could not load file or assembly &#39;Microsoft.ReportViewer.WebForms, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91&#39; or one of its dependencies
  9. Mybatis.xml文件中大于小于等于
  10. HGOI20190126 模拟赛