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.ui,进入可视化设计界面。默认情况中间的主设计区下只有一个Widget的对象。由于USB摄像头采集到的图像需要显示到一个QLabel的部件上,从右侧的部件列
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
的“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 1设置图示)
(Push button 2设置图示) Push Button 2设置图示)
最后生成的界面:
点击Debug会得到Debug文档,里面有你设置的信息。
编译运行后,会在建立的工程文件夹下生成很多文件,重要的是widget.ui文件,其他的文件要根据你具体的应用作出相应的修改。
Qt界面最终的效果图为:
二. USB摄像头视频采集与Qt界面显示源代码分析
源码包含:common.h VideoDevice.h widget.h VideoDevice.cpp widget.cpp main.cpp
common.h主要定义了USB采集到的图像的宽度,高度等信息;
Videodevice.h定义了VideoDevice类,使它继承于Qt的基类QObject,定义了VideoDevice的构造
函
关于工期滞后的函关于工程严重滞后的函关于工程进度滞后的回复函关于征求同志党风廉政意见的函关于征求廉洁自律情况的复函
数与析构函数,重要的是定义了实现v4l2视频架构的函数;
Videodevice.cpp具体实现了Videodevice.h定义的函数,完成了基于v4l2架构的视频采集;
widget.h定义了Widget窗口类,使它继承于Qt窗口类QWidget,并定义了YUV到RGB颜色转变的函数;QT界面按钮操作的实现函数,以及视频窗口的刷新时间painEvent函数;
widget.cpp实现了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)Videodevice.cpp
#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:\t\t%s\n",cap.driver);//驱动名
printf("card:\t\t%s\n",cap.card);//摄像头信息
printf("bus_info:\t%s\n",cap.bus_info);//PCI总线信息
printf("version:\t%d\n",cap.version);//内核版本
printf("capabili ties:\t%x\n",cap.capabilities);
//以上打印信息详见设备能力结构体(struct v4l2_capability)
if ((cap.capabilities & v4l2_CAP_VIDEO_capture))
{
printf("Device %s: supports capture.\n",FILE_VIDEO);
}
if ((cap.capabilities & v4l2_CAP_STREAMING))
{
printf("Device %s: supports streaming.\n",FILE_VIDEO);
}
}
//列举摄像头所支持像素格式
fmtdesc.index=0;
fmtdesc.type=v4l2_BUF_TYPE_VIDEO_capture;
printf("Support format:\n");
while(ioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc)!=-1)
{
printf("\t%d.%s\n",fmtdesc.index+1,fmtdesc.description);
fmtdesc.index++;
}
//set fmt
fmt.type = v4l2_BUF_TYPE_VIDEO_capture;//恒为此项
fmt.fmt.pix.pixelformat = v4l2_PIX_FMT_YUYV;//视频数据存储类型
fmt.fmt.pix.height = 480;
fmt.fmt.pix.width = 640;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;//隔行扫描
if(ioctl(fd, VIDIOC_S_FMT, &fmt) == -1)
{
printf("Unable to set format\n");
return FALSE;
}
if(ioctl(fd, VIDIOC_G_FMT, &fmt) == -1)
{
printf("Unable to get format\n");
return FALSE;
}
//set fps 具体参考结构体v4l2_captureparm
setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
/*timeperframe字段用于指定想要使用的帧频率,它是一个结构体:
numerator 和denominator所描述的系数给出的是成功的帧之间的时间间隔。
numerator分子,denominator分母。主要表达每次帧之间时间间隔。numerator/denominator秒一帧。*/
setfps.parm.capture.timeperframe.numerator = 1;
setfps.parm.capture.timeperframe.denominator = 30;//本摄像头帧频范围[5,30]帧/秒
if(ioctl(fd, VIDIOC_S_PARM, &setfps) == -1)
{
printf("Unable to set frame rate\n");
return FALSE;
}
else
{
printf("set fps OK!\n");
}
if(ioctl(fd, VIDIOC_G_PARM, &setfps) == -1)
{
printf("Unable to get frame rate\n");
return FALSE;
}
else
{
printf("get fps OK!\n");
printf("timeperframe.numerator:\t%d\n",setfps.parm.capture.timeperframe.numerator);
printf("timeperframe.denominator:\t%d\n",setfps.parm.capture.timeperframe.denominator);
}
//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) widget.cpp
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 %ld\n",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) main.cpp
#include
#include "widget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
三. 运行效果
点击保存按钮后存放视频的文件,当关闭时自动生成下一个save01.yuv文件已接受下次视频数据,以此类推。