2024年3月11日发(作者:)

USB摄像头视频采集与Qt界面显示

一. Qt界面制作

1. 新建Qt工程

启动Qt Creator,新建一个Qt Gui应用。

单击File选择New File or Project出现以下界面:

选择Qt Gui Application,之后选择好工程与路径名,其他默认,一直到设置Class

information(类信息)时,Class name设为Widget, Base name选择QWidget,其他

默认。设置好这些后,其他默认,直到工程设置结束。如下图所示:

2. 修改ui界面

打开,进入可视化设计界面。默认情况中间的主设计区下只有一个Widget

的对象。由于USB摄像头采集到的图像需要显示到一个QLabel的部件上,从右侧的部件

列表的“DisplayWidget”中选择“Label”部件拖动到中间;此外,我们需要两个按钮,

一个用于启动和终止视频数据的保存,一个用于以后的视频文件的压缩。从右侧

的”Buttons”中两次选择”Push Buttion”部件并拖动到Widget中。

从上图可以看出,对象Widget下已经添加了一个label部件,两个push button部

件。右上角Object与Class的关系是:Object对应的物体是属于Class对应的类,反映

了Qt的继承关系。

接下来设置上面四种部件的属性,Widget的属性按照下面图示设置,其中geometry

设置为[(0,0),650*550]说明界面左上角的坐标位于原点,大小为650*550;在window

name这一项设置的是你的界面的名字,我设置为USB_YUV_Camera。

注:图片未提及的采用默认就行,其他三个部件见图示。这些部件的objectName要

特别注意,因为会在后面编写的程序中用到。

(Widget设置图示) (label设置图示1) (label设置图示2)

(Push button 1设置图示)

(Push button 2设置图示)

(Push button 1设置图示)

Push button 2设置图示)

最后生成的界面:

点击Debug会得到Debug文档,里面有你设置的信息。

编译运行后,会在建立的工程文件夹下生成很多文件,重要的是文件,其

他的文件要根据你具体的应用作出相应的修改。

Qt界面最终的效果图为:

二. USB摄像头视频采集与Qt界面显示源代码分析

源码包含:common.h VideoDevice.h widget.h

common.h主要定义了USB采集到的图像的宽度,高度等信息;

Videodevice.h定义了VideoDevice类,使它继承于Qt的基类QObject,定义了

VideoDevice的构造函数与析构函数,重要的是定义了实现V4L2视频架构的函数;

具体实现了Videodevice.h定义的函数,完成了基于V4L2架构的

视频采集;

widget.h定义了Widget窗口类,使它继承于Qt窗口类QWidget,并定义了YUV到

RGB颜色转变的函数;QT界面按钮操作的实现函数,以及视频窗口的刷新时间painEvent

函数;

实现了widget.h定义的函数。

下面介绍各文件的主要代码段:

(1)common.h

#ifndef COMMON_H

#define COMMON_H

//… …

#define IMG_WIDTH 640//定义视频的宽度为640

#define IMG_HEIGTH 480//定义视频的高度为480

#endif // COMMON_H

(2) Videodevice.h

#define CLEAR(x) memset(&(x), 0, sizeof(x))//定义CLEAR为内存清零

class VideoDevice : public QObject

{

Q_OBJECT //有了这条语句才能使用QT中的signal和slot机制

public:

VideoDevice(QString dev_name);//构造函数定义,用于初始化

~VideoDevice();//析构函数用于释放内存

int get_frame(unsigned char ** yuv_buffer_pointer, size_t* len);//获取视频帧

int unget_frame();//释放视频帧,让出缓存空间准备新的视频帧数据

private:

int open_device();//打开设备

int init_device();//初始化设备

int start_capturing();//启动视频采集

int init_mmap();//内存映射初始化

int stop_capturing();//停止视频采集

int uninit_device();//注销设备

int close_device();//关闭设备

struct buffer

{

void * start;//视频缓冲区的起始地址

size_t length;//缓冲区的长度

};

QString dev_name;

int fd;//video0 file

buffer* buffers;

unsigned int n_buffers;

int index;

signals:

//void display_error(QString);

};

#endif // VIDEODEVICE_H

(3)

#define FILE_VIDEO "/dev/video0"

VideoDevice::VideoDevice(QString dev_name)//VideoDevice的构造函数进行初

始化

{

this->dev_name = dev_name;

this->fd = -1;

this->buffers = NULL;

this->n_buffers = 0;

this->index = -1;

if(open_device() == FALSE)

{

close_device();

}

if(init_device() == FALSE)

{

close_device();

}

if(start_capturing() == FALSE)

{

stop_capturing();

close_device();

}

}

VideoDevice::~VideoDevice()//VideoDevice的析构函数

{

if(stop_capturing() == FALSE)

{}

if(uninit_device() == FALSE)

{ }

if(close_device() == FALSE)

{}

}

int VideoDevice::init_device()//设备初始化

{

v4l2_capability cap;//设备能力结构体

v4l2_format fmt;//设置视频像素

v4l2_streamparm setfps;//设置采样率

v4l2_fmtdesc fmtdesc;//查询摄像头支持像素格式

if(ioctl(fd, VIDIOC_QUERYCAP, &cap) == -1)

{

printf("Error opening device %s: unable to query device.n",FILE_VIDEO);

return FALSE;

}

else

{

printf("driver:tt%sn",);//驱动名

printf("card:tt%sn",);//摄像头信息

printf("bus_info:t%sn",_info);//PCI总线信息

printf("version:t%dn",n);//内核版本

printf("capabili ties:t%xn",lities);

//以上打印信息详见设备能力结构体(struct v4l2_capability)

if ((lities & V4L2_CAP_VIDEO_CAPTURE))

{

printf("Device %s: supports capture.n",FILE_VIDEO);

}

if ((lities & V4L2_CAP_STREAMING))

{

printf("Device %s: supports streaming.n",FILE_VIDEO);

}

}

//列举摄像头所支持像素格式

=0;

=V4L2_BUF_TYPE_VIDEO_CAPTURE;

printf("Support format:n");

while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)

{

printf("t%d.%sn",+1,ption);

++;

}

//set fmt

= V4L2_BUF_TYPE_VIDEO_CAPTURE;//恒为此项

ormat = V4L2_PIX_FMT_YUYV;//视频数据存储类型

= 480;

= 640;

= V4L2_FIELD_INTERLACED;//隔行扫描

if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)

{

printf("Unable to set formatn");

return FALSE;

}

if(ioctl(fd, VIDIOC_G_FMT, &fmt) == -1)

{

printf("Unable to get formatn");

return FALSE;

}

//set fps 具体参考结构体v4l2_captureparm

= V4L2_BUF_TYPE_VIDEO_CAPTURE;

/*timeperframe字段用于指定想要使用的帧频率,它是一个结构体:

numerator 和denominator所描述的系数给出的是成功的帧之间的时间间隔。

numerator分子,denominator分母。主要表达每次帧之间时间间隔。

numerator/denominator秒一帧。*/

tor = 1;

nator = 30;//本摄像头帧频范围[5,30]

帧/秒

if(ioctl(fd, VIDIOC_S_PARM, &setfps) == -1)

{

printf("Unable to set frame raten");

return FALSE;

}

else

{

printf("set fps OK!n");

}

if(ioctl(fd, VIDIOC_G_PARM, &setfps) == -1)

{

printf("Unable to get frame raten");

return FALSE;

}

else

{

printf("get fps OK!n");

printf("tor:t%dn",

ator);

printf("nator:t%dn",

ominator);

}

//mmap

if(init_mmap() == FALSE )

{

printf("cannot mmap!n");

return FALSE;

}

return TRUE;

}

(4)widget.h

namespace Ui {

class Widget;

}

class Widget : public QWidget

{

Q_OBJECT //上面内容为固定格式

public:

explicit Widget(QWidget *parent = 0);//explicit可以避免发生隐式类型转换

~Widget();

private:

Ui::Widget *ui;

QImage *frame;

int rs;

unsigned int len;

int convert_yuv_to_rgb_buffer();

void print_quartet(unsigned int i);

VideoDevice *vd;

FILE * yuvfile;

unsigned char rgb_buffer[640*480*3];

unsigned char * yuv_buffer_pointer;

char Y_frame[640*480];//存储亮度Y分量

char Cr_frame[240*320]; //存储蓝色浓度偏移量即U分量

char Cb_frame[240*320]; //存储红色浓度偏移量即V分量

int write420();//视频图像保存为YUV420,也可存储为YUV422

private slots://定义槽

void on_pushButton_start_clicked();//按钮按下对应的处理,不定义成槽,按钮将

失效

void paintEvent(QPaintEvent *);//窗口刷新函数

};

#endif // WIDGET_H

(5)

char yuvfilename[11] = {'s','a','v','e','0','0','.','y','u','v','0'};//视频保存文件的名称

Widget::Widget(QWidget *parent) :

QWidget(parent),

ui(new Ui::Widget)

{

ui->setupUi(this);

vd = new VideoDevice(tr("/dev/video0"));

frame = new QImage(rgb_buffer,640,480,QImage::Format_RGB888);

}

void Widget::paintEvent(QPaintEvent *)

{

rs = vd->get_frame(&yuv_buffer_pointer,&len);

if(last_state==2 && state == 0)

{

yuvfile = fopen(yuvfilename,"wb+");

yuvfilename[5]++;

}

if(state == 1)

{

rs = write420();

}

if(last_state==1 && state==2)

{

fclose(yuvfile);

}

int Widget::write420()

{

int x,y;

long int index1 =0;

if (yuv_buffer_pointer[0] == '0')

{

return -1;

}

for(x=0;x<640;x++)

{

for(y=0;y<480;y++)

{

Y_frame[index1]=yuv_buffer_pointer[2*index1];

index1++;

}

}

index1=0;

for(x=0;x<480;x++,x++)

{

for(y=0;y<640;y++,y++)

{

Cb_frame[index1]=yuv_buffer_pointer[(x*640+y)*2+1];

Cr_frame[index1]=yuv_buffer_pointer[(x*640+y)*2+3];

index1++;

}

}

//YUV422的程序

/* for(x=0;x<480;x++)

{

for(y=0;y<320;y++)

{

Y_frame[index]= yuv_buffer_pointer[(x*320+y)*4];

Cb_frame[index]= yuv_buffer_pointer[(x*320+y)*4 + 1];

Y_frame[index+1]= yuv_buffer_pointer[(x*320+y)*4 + 2];

Cr_frame[index]= yuv_buffer_pointer [(x*320+y)*4 + 3];

index++;

}

}*/

fwrite(Y_frame, 307200, 1, yuvfile);

fwrite(Cb_frame,76800, 1, yuvfile);

fwrite(Cr_frame,76800, 1, yuvfile);

framecnt++;

printf("writed frame %ldn",framecnt);

}

int Widget::convert_yuv_to_rgb_buffer()

{

unsigned long in, out = 0;

int y0, u, y1, v;

int r, g, b;

for(in = 0; in < IMG_WIDTH * IMG_HEIGTH * 2; in += 4)

{

y0 = yuv_buffer_pointer[in + 0];

u = yuv_buffer_pointer[in + 1];

y1 = yuv_buffer_pointer[in + 2];

v = yuv_buffer_pointer[in + 3];

r = y0 + (1.370705 * (v-128));

g = y0 - (0.698001 * (v-128)) - (0.337633 * (u-128));

b = y0 + (1.732446 * (u-128));

/* r = y0 + 1.042*(v-128);

g = y0 - 0.34414*(u-128) - 0.71414*(v-128);

b = y0 + 1.772*(u-128);*/ // YUV422程序

if(r > 255) r = 255;

if(g > 255) g = 255;

if(b > 255) b = 255;

if(r < 0) r = 0;

if(g < 0) g = 0;

if(b < 0) b = 0;

rgb_buffer[out++] = r;

rgb_buffer[out++] = g;

rgb_buffer[out++] = b;

r = y1 + (1.370705 * (v-128));

g = y1 - (0.698001 * (v-128)) - (0.337633 * (u-128));

b = y1 + (1.732446 * (u-128));

/* r = y0 + 1.042*(v-128);

g = y0 - 0.34414*(u-128) - 0.71414*(v-128);

b = y0 + 1.772*(u-128);*/ // YUV422程序

if(r > 255) r = 255;

if(g > 255) g = 255;

if(b > 255) b = 255;

if(r < 0) r = 0;

if(g < 0) g = 0;

if(b < 0) b = 0;

rgb_buffer[out++] = r;

rgb_buffer[out++] = g;

rgb_buffer[out++] = b;

}

return 0;

}

void Widget::on_pushButton_start_clicked()

{

switch(state)

{

case 0:

{

ui->pushButton_start->setText("stop save");

state = 1;

break;

}

case 1:

{

ui->pushButton_start->setText("save again");

state = 2;

break;

}

case 2:

{

ui->pushButton_start->setText("start save");

framecnt=0;

state = 0;

break;

}

default :break;

}

(6)

#include

#include "widget.h"

int main(int argc, char *argv[])

{

QApplication a(argc, argv);

Widget w;

();

return ();

}

三. 运行效果

点击保存按钮后存放视频的文件,当关闭时自动生成下一个

文件已接受下次视频数据,以此类推。