V4L2视频采集以及使用libjpeg 用yuv转JPEG
经过了一周的V4L2(video for linux two)的学习,终于用摄像头采集到视频图像了。接下来整理一下我的学习过程。首先介绍一下开发环境: 主机win7 + 虚拟机ubuntu + 广州斯道科技有限公司出品的Icool6410,采用交叉编译的方式,使用命令arm-linux-gcc –ccamara.c –o camtest将编译好的文件复制到win7上,再使用ftp挂载
经过了一周的V4L2(video for linux two)的学习,终于用摄像头采集到视频图像了。接下来整理一下我的学习过程。
首先介绍一下开发环境:
主机win7 + 虚拟机ubuntu + 广州斯道科技有限公司出品的Icool6410,采用交叉编译的方式,使用命令arm-linux-gcc –ccamara.c –o camtest将编译好的文件复制到win7上,再使用ftp挂载开发板(这个需要把开发板连接路由器,然后autoeth0查看路由器分配给开发板的IP,然后打开win7我的电脑运行ftp://IP,就可以挂载开发板了),把生成的执行文件camtest复制到开发板文件系统中,然后再修改文件权限chmod 777 camtest,再运行./camtest就可以生成一个out.yuv文件,再从开发板文件系统中把文件复制到win7上,然后再使用软件YUV viewer打开out.yuv文件,就可以看到摄像头采集到的图片了。
接下来介绍一下常用的结构体:
structv4l2_requestbuffers //申请帧缓冲,对应命令VIDIOC_REQBUFS
struct v4l2_capability //视频设备的功能,对应命令VIDIOC_QUERYCAP
structv4l2_input //视频输入信息,对应命令VIDIOC_ENUMINPUT
structv4l2_standard //视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD
structv4l2_format //帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
structv4l2_buffer //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
structv4l2_crop //视频信号矩形边框
structv4l2_std_id //视频制式
这些结构在都在include/linux/videodev2.h中定义,想详细了解这些结构体的可以去看看这个头文件。
2、常用的IOCTL接口命令也在include/linux/videodev2.h中定义
VIDIOC_REQBUFS//分配内存
VIDIOC_QUERYBUF//把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址
VIDIOC_QUERYCAP//查询驱动功能
IDIOC_ENUM_FMT//获取当前驱动支持的视频格式
VIDIOC_S_FMT//设置当前驱动的频捕获格式
VIDIOC_G_FMT//读取当前驱动的频捕获格式
VIDIOC_TRY_FMT//验证当前驱动的显示格式
VIDIOC_CROPCAP//查询驱动的修剪能力
VIDIOC_S_CROP //设置视频信号的矩形边框
VIDIOC_G_CROP //读取视频信号的矩形边框
VIDIOC_QBUF //把数据从缓存中读取出来
VIDIOC_DQBUF //把数据放回缓存队列
VIDIOC_STREAMON //开始视频显示函数
VIDIOC_STREAMOFF //结束视频显示函数
VIDIOC_QUERYSTD //检查当前视频设备支持的标准,例如PAL或NTSC
3. V4L2拥有一套标准的API,大家在做应用程序的时候应该多使用官方提供的接口函数。关于视频采集是有一定的顺序的。
(1)首先要打开摄像头设备文件,这里的前提是开发板一定要有摄像头对应的驱动,若没有就需要移植相应的驱动到内核了。然后使用命令查看开发板是否有/dev/video0这个文件,若有就证明可以打开摄像头了。
使用fd = open(dev_name, O_RDWR /* required */| O_NONBLOCK, 0) 可读可写、无阻塞的的方式打开摄像头文件,则当没有可用的视频数据时,不会阻塞,而立刻返回.
(2)检查当前视频设备支持的标准
do {
structv4l2_std_id std;ê?
ret =ioctl(fd, VIDIOC_QUERYSTD, &std);
} while (ret == -1 && errno ==EAGAIN);
switch (std) {
caseV4L2_STD_NTSC:
printf("thedevice support NTSC\n");
caseV4L2_STD_PAL:
printf("thedevice support STD_PAL\n");
}
(3)取得设备的capability 。
structv4l2_capability capability;
int ret =ioctl(fd, VIDIOC_QUERYCAP, &capability);
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
fprintf(stderr, "%s is no video capture device \n",dev_name);
if (!(cap.capabilities & V4L2_CAP_STREAMING))
fprintf(stderr, "%s does not support streaming i/o \n",dev_name);
看看设备具有什么功能,比如是否具有视频输入特性。
(4)查询驱动的修剪能力
if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap))
(5)设置视频的捕捉格式。
在使用这些结构体的时候先要清0一下,使用memset(&x,0,sizeof(structx)).
mt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = PAL_WIDTH; 图片的宽度
fmt.fmt.pix.height = PAL_HEIGHT; //图片的高度
fmt.fmt.pix.priv = 1;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
再使用
if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) 设置上面定义的这些格式。
(6) 向驱动申请帧缓存
struct v4l2_requestbuffers req;
CLEAR (req);
req.count = 4; //申请帧缓存的数目为4
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) {
if (EINVAL== errno) {
fprintf(stderr, "%s does not support "
"memory mapping/n", dev_name);
exit(EXIT_FAILURE);
} else{
errno_exit("VIDIOC_REQBUFS");
v4l2_requestbuffers结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立FIFO,来提高视频采集的效率。
(7)获取每个缓存的信息,并mmap到用户空间
struct buffer {
void *start;
size_tlength;//buffer's length is different from cap_image_size
}; //定义用户缓冲区
buffers = calloc(req.count,sizeof(*buffers)); 申请count个用户缓冲区
for (n_buffers = 0; n_buffers< req.count; ++n_buffers) {
struct v4l2_buffer buf;
CLEAR (buf);
buf.type =V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP; //定义使用mmap映射方式
buf.index = n_buffers;
if (-1 == xioctl(fd, VIDIOC_QUERYBUF,&buf)) //申请内核缓存区间,获取到对应的index的缓存信息,此处主要利用length信息及offset信息来完成后面的mmap操作。
errno_exit("VIDIOC_QUERYBUF");
buffers[n_buffers].length =buf.length; //转换成相对地址
buffers[n_buffers].start = mmap(NULL /*start anywhere */, buf.length,
PROT_READ | PROT_WRITE /*required */,
MAP_SHARED /* recommended */,fd, buf.m.offset); //mmap()返回内核区间映射到用户区间的首地址。
(8)开始视频采集
static void start_capturing(void){
unsigned int i;
enum v4l2_buf_type type;
for(i = 0;i < n_buffers;++i){
struct v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
errno_exit("VIDIOC_QBUF");
type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(-1 == xioctl(fd, VIDIOC_STREAMON, &type)) //开始视频采集
errno_exit("VIDIOC_STREAMON");
}
}
(9)读取缓存中的内容
static int read_frame(void){
struct v4l2_buffer buf;
unsigned int i;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //查询功能
buf.memory= V4L2_MEMORY_MMAP;
if(-1 == xioctl(fd,VIDIOC_DQBUF,&buf)){ //将缓冲出队列
errno_exit("VIDIOC_DQBUF");
}
assert(buf.index < n_buffers); //检查下标
// printf("length= %d/r", buffers[buf.index].length);
process_image(buffers[i].start,cap_image_size ); //视频处理函数,此函数中可以把视频数据取出来。
printf("image_size = %d, IO_METHOD_MMAP buffer.length=%d\n",cap_image_size, buffers[buf.index].length);
if(-1 ==xioctl(fd,VIDIOC_QBUF,&buf)) //将刚刚处理完的缓冲重新入队列尾,这样可以循环采集{
errno_exit("VIDIOC_QBUF");
}
}
视频处理函数
static void process_image(constvoid *p,int len){
fwrite(p, len, 1, file_fd); //从指针p所指的地址写1个len大小的字节到file_fd所指的文件中
printf("from %p write%d bytes to file_fd\n",p,len);
} //这里也可以对视频数据作别的处理。
(10)停止视频的采集
static voidstop_capturing(void){
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type)) //停止视频采集
errno_exit("VIDIOC_STREAMOFF");
}
(11) 释放用户区申请的缓存
static voiduninit_device(void){
unsigned int i;
for(i = 0;i < n_buffers;i++)
if(-1 == munmap(buffers[i].start,buffers[i].length)) //撤销map映射
errno_exit("munmap");
free(buffers); //释放用户区申请的缓存
}
(12)关闭视频设备
static void close_device(void){
if (-1 == close(fd))
errno_exit("close");
fd = -1;
}
另外贴一下mainloop()的代码。
static void mainloop(void){
unsigned int count;
count = 50; //由count控制画面的数目
while(count-- > 0){
for(;;){
fd_set fds;
struct timeval tv;
int r;
FD_ZERO(&fds); //将指定的文件描述符集清空
FD_SET(fd,&fds) ;//在文件描述符集合中增加一个新的文件描述符
/* Timeout. */
tv.tv_sec= 3;
tv.tv_usec= 0;
r =select(fd + 1, &fds, NULL, NULL, &tv); //判断是否可读(即摄像头是否准备好),tv是定时
if (-1 == r) {
if (EINTR == errno)
continue;
errno_exit("select");
}
if(0 == r) {
fprintf(stderr, "select timeout/n");
exit(EXIT_FAILURE);
}
if(read_frame()) ; //如果可读,执行read_frame()函数,并跳出循环,这个read_frame()在此用的很巧。
break;
}
}
}
图一 yuv格式图像
另外再自己还实现了yuv格式的图像转换成jpg格式的图像。
首先得先在ubuntu下安装libjpeg,直接上网站下载后,运行./configure--enable-static --enable-shared --prefix=/usr 这个命令让libjpeg在/usr下建立共享和静止的两种库。再make && makeinstall 等待就安装成功了,若在之后的程序中显示lib不匹配错误,多半是由于lib冲突的问题,把老库删除再重新安装下问题一般得到解决,实在不行的上谷歌找找,#defineJPEG_LIB_VERSION 62 /* Version 6b */ 程序运行出现需要62 但是库的版本为80,也就是库新了,另外在makefile里要注意加上-ljpeg选项。还有一个重要的地方,应用程序一定要记得加#include</usr/include/jpeglib.h>,或者是jpeglib的其他安装地方。总之就是要把jpeglib.h添加进去。我就弄了一下午才搞定,汗。
之后就是把yuv格式图像转化成jpg格式图像了,有两种方法,一种是直接转化,另外一种是先转化成rgb格式再转化成yuv格式。
压缩步骤:
1、申请并初始化jpeg压缩对象,同时要指定错误处理器
struct jpeg_compress_structjcs;// 声明错误处理器,并赋值给jcs.err域
struct jpeg_error_mgr jem;
jcs.err = jpeg_std_error(&jem);
jpeg_create_compress(&jcs);
2、指定压缩后的图像所存放的目标文件,注意,目标文件应以二进制模式打开
f=fopen("03.jpg","wb"); //图像保存在03.jpg下
jpeg_stdio_dest(&jcs, f);
3、设置压缩参数,主要参数有图像宽、高、色彩通道数(1:索引图像,3:其他),色彩空间(JCS_GRAYSCALE表示灰度图,JCS_RGB表示彩色图像),压缩质量等,如下:
jcs.image_width = nWidth; // 为图的宽和高,单位为像素
jcs.image_height = nHeight;
jcs.input_components = 3; // 在此为3,表示彩色位图, 如果是灰度图,则为1
jcs.in_color_space = JCS_GRAYSCALE; //JCS_GRAYSCALE表示灰度图,JCS_RGB表示彩色图像
jpeg_set_defaults(&jcs);
jpeg_set_quality (&jcs, 80, true);
需要注意的是,jpeg_set_defaults函数一定要等设置好图像宽、高、色彩通道数计色彩空间四个参数后才能调用,因为这个函数要用到这四个值,调用jpeg_set_defaults函数后,jpeglib库采用默认的设置对图像进行压缩,如果需要改变设置,如压缩质量,调用这个函数后,可以调用其它设置函数,如jpeg_set_quality函数。其实图像压缩时有好多参数可以设置,但大部分我们都用不着设置,只需调用jpeg_set_defaults函数值为默认值即可。
4、上面的工作准备完成后,就可以压缩了,压缩过程非常简单,首先调用jpeg_start_compress,然后可以对每一行进行压缩,也可以对若干行进行压缩,甚至可以对整个的图像进行一次压缩,压缩完成后,记得要调用jpeg_finish_compress函数,如下:
jpeg_start_compress(&jcs,TRUE);
JSAMPROW row_pointer[1]; // 一行位图
int row_stride; // 每一行的字节数
row_stride = jcs.image_width;// 如果不是索引图,此处需要乘以3
// 对每一行进行压缩
while (jcs.next_scanline < jcs.image_height) {
row_pointer[0] = & pDataConv[jcs.next_scanline * row_stride];
jpeg_write_scanlines(&jcs, row_pointer, 1);
}
jpeg_finish_compress(&jcs);
5、最后就是释放压缩工作过程中所申请的资源了,主要就是jpeg压缩对象,由于在本例中我是直接用的局部变量,所以只需调用jpeg_destroy_compress这个函数即可,如下:
jpeg_destroy_compress(&jcs);
YUV转换JPG算法如下:
static void jpegWrite(unsigned char* img)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW row_pointer[1];
//char name[4]={'a','b','c','x'};
FILE *outfile = fopen( jpegFilename, "wb" );
// try to open file for saving
if (!outfile) {
errno_exit("jpeg");
}
// create jpeg data
cinfo.err = jpeg_std_error( &jerr );
jpeg_create_compress(&cinfo);
jpeg_stdio_dest(&cinfo, outfile);
// set image parameters
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
// set jpeg compression parameters to default
jpeg_set_defaults(&cinfo);
// and then adjust quality setting
jpeg_set_quality(&cinfo, jpegQuality, TRUE);
// start compress
jpeg_start_compress(&cinfo, TRUE);
// feed data
while (cinfo.next_scanline < cinfo.image_height) {
row_pointer[0] = &img[cinfo.next_scanline * cinfo.image_width *
cinfo.input_components];
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
// finish compression
jpeg_finish_compress(&cinfo);
// destroy jpeg data
jpeg_destroy_compress(&cinfo);
// close output file
fclose(outfile);
}
/**
process image read
*/
static void imageProcess(const void* p)
{
unsigned char* src = (unsigned char*)p;
unsigned char* dst = malloc(width*height*3*sizeof(char));
// convert from YUV422 to RGB888
YUV422toRGB888(width,height,src,dst);
// write jpeg
jpegWrite(dst);
}
static void YUV422toRGB888(int width, int height, unsigned char *src, unsignedchar *dst)
{
int line, column;
unsigned char *py, *pu, *pv;
unsigned char *tmp = dst;
/* In this format each four bytes is two pixels. Each four bytes is two Y's, aCb
and a Cr.Each Y goes to one of the pixels, and the Cb and Cr belong to bothpixels. */
py = src;
pu = src + 1;
pv = src + 3;
#define CLIP(x) ( (x)>=0xFF ? 0xFF : ( (x) <= 0x00 ? 0x00 : (x) ) )
for (line = 0; line < height; ++line) {
for (column = 0; column < width; ++column) {
*tmp++ = CLIP((double)*py + 1.402*((double)*pv-128.0));
*tmp++ = CLIP((double)*py - 0.344*((double)*pu-128.0) -0.714*((double)*pv-128.0));
*tmp++ = CLIP((double)*py + 1.772*((double)*pu-128.0));
// increase py every time
py += 2;
// increase pu,pv every second time
if ((column & 1)==1) {
pu += 4;
pv += 4;
}
}
}
}
图二 转成jpg格式的图片
完整代码有好几百行,就不贴上来了
更多推荐
所有评论(0)