YUVX信号的定义和转换

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[IMAGEWIDTHIMAGEHEIGHT3];
复制代码
其中

#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[IMAGEWIDTHIMAGEHEIGHT3]
这些宏和定义结束后,就可以开始编程配置摄像头并采集图像了。一般来说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 }
复制代码
完整的初始化代码如下:

复制代码
1 int init_v4l2(void)
2 {
3 int i;
4 int ret = 0;
5
6 //opendev
7 if ((fd = open(FILE_VIDEO, O_RDWR)) == -1)
8 {
9 printf(“Error opening V4L interface\n”);
10 return (FALSE);
11 }
12
13 //query cap
14 if (ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1)
15 {
16 printf(“Error opening device %s: unable to query device.\n”,FILE_VIDEO);
17 return (FALSE);
18 }
19 else
20 {
21 printf(“driver:\t\t%s\n”,cap.driver);
22 printf(“card:\t\t%s\n”,cap.card);
23 printf(“bus_info:\t%s\n”,cap.bus_info);
24 printf(“version:\t%d\n”,cap.version);
25 printf(“capabilities:\t%x\n”,cap.capabilities);
26
27 if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE)
28 {
29 printf(“Device %s: supports capture.\n”,FILE_VIDEO);
30 }
31
32 if ((cap.capabilities & V4L2_CAP_STREAMING) == V4L2_CAP_STREAMING)
33 {
34 printf(“Device %s: supports streaming.\n”,FILE_VIDEO);
35 }
36 }
37
38 //emu all support fmt
39 fmtdesc.index=0;
40 fmtdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
41 printf(“Support format:\n”);
42 while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
43 {
44 printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
45 fmtdesc.index++;
46 }
47
48 //set fmt
49 fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
50 fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
51 fmt.fmt.pix.height = IMAGEHEIGHT;
52 fmt.fmt.pix.width = IMAGEWIDTH;
53 fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
54
55 if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
56 {
57 printf(“Unable to set format\n”);
58 return FALSE;
59 }
60 if(ioctl(fd, VIDIOC_G_FMT, &fmt) == -1)
61 {
62 printf(“Unable to get format\n”);
63 return FALSE;
64 }
65 {
66 printf(“fmt.type:\t\t%d\n”,fmt.type);
67 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);
68 printf(“pix.height:\t\t%d\n”,fmt.fmt.pix.height);
69 printf(“pix.width:\t\t%d\n”,fmt.fmt.pix.width);
70 printf(“pix.field:\t\t%d\n”,fmt.fmt.pix.field);
71 }
72 //set fps
73 setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
74 setfps.parm.capture.timeperframe.numerator = 10;
75 setfps.parm.capture.timeperframe.denominator = 10;
76
77 printf(“init %s \t[OK]\n”,FILE_VIDEO);
78
79 return TRUE;
80 }
复制代码
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);
完整的采集代码:

复制代码
1 int v4l2_grab(void)
2 {
3 unsigned int n_buffers;
4
5 //request for 4 buffers
6 req.count=4;
7 req.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
8 req.memory=V4L2_MEMORY_MMAP;
9 if(ioctl(fd,VIDIOC_REQBUFS,&req)==-1)
10 {
11 printf(“request for buffers error\n”);
12 }
13
14 //mmap for buffers
15 buffers = malloc(req.count*sizeof (*buffers));
16 if (!buffers)
17 {
18 printf (“Out of memory\n”);
19 return(FALSE);
20 }
21
22 for (n_buffers = 0; n_buffers < req.count; n_buffers++)
23 {
24 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
25 buf.memory = V4L2_MEMORY_MMAP;
26 buf.index = n_buffers;
27 //query buffers
28 if (ioctl (fd, VIDIOC_QUERYBUF, &buf) == -1)
29 {
30 printf(“query buffer error\n”);
31 return(FALSE);
32 }
33
34 buffers[n_buffers].length = buf.length;
35 //map
36 buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ |PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
37 if (buffers[n_buffers].start == MAP_FAILED)
38 {
39 printf(“buffer map error\n”);
40 return(FALSE);
41 }
42 }
43
44 //queue
45 for (n_buffers = 0; n_buffers < req.count; n_buffers++)
46 {
47 buf.index = n_buffers;
48 ioctl(fd, VIDIOC_QBUF, &buf);
49 }
50
51 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
52 ioctl (fd, VIDIOC_STREAMON, &type);
53
54 ioctl(fd, VIDIOC_DQBUF, &buf);
55
56 printf(“grab yuyv OK\n”);
57 return(TRUE);
58 }
复制代码
3、YUYV转RGB24

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

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

View Code
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);

}
复制代码

有线采集系统进度报告
                                    --20170309
 
内容 状态 备注
FPGA低功耗的调试 目前测试了7块低电压的采集站,由于初步测试结果不够严谨。
功耗0.15*44v=6.6W(含线损)既单站约 0.95W,相比标准电压版本(1.04W)大约降低80mw,还是比较可观。

 V

信号极性PN反向自适应 调试完成
120米距离通讯的稳定性调试 调试完成 通过减小限流电阻实现
采集站端口方向自动检测能力 调试完成 做了两种端口排布情况的识别,对应交叉站在哪一端
电源站隔离电池电压检测功能 调试完成  
交叉站站非隔离电池电压检测功能 调试完成

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值