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 ();
}
三. 运行效果
点击保存按钮后存放视频的文件,当关闭时自动生成下一个
文件已接受下次视频数据,以此类推。
发布评论