转载的出处和链接:http://blog.csdn.net/jasonque/article/details/8471572
整理了大部分代码解析,以方便大家看懂程序。
#include "widget.h"
#include "ui_widget.h"
#include <QtGui>
#ifndef PROPERTY
#define PROPERTY
#define image_width 320 //图片显示宽度
#define image_height 240 //图片显示高度
#define image_Format QImage::Format_RGB888 //图片显示格式
#define cameraDevice "/dev/video3" //摄像头设备
#define haarXML "./data/haarcascade_frontalface_alt2.xml" //人脸正脸模型级联分类器文件
#define haarXML_profile "./data/haarcascade_profileface.xml" //人脸侧脸模型级联分类器文件
#define imgSizeScaleSmall 0.7 //图像放缩比例
#define imgSizeScaleBig 2 //图像放缩比例
#endif //PROPERTY
Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
ui->setupUi(this);
imgBuf = (unsigned char *)malloc(image_width * image_height* 3 * sizeof(char));
//申请图片总空间,共image_width * image_height个像素,一个像素RGB三个字节
frame = new QImage(imgBuf,image_width,image_height,image_Format);
//根据存储空间imgBuf开辟QImage图像
m_camera = new VideoDevice(tr(cameraDevice)); //创建摄像头设备,tr("string") connect(m_camera, SIGNAL(display_error(QString)), this,SLOT(display_error(QString))); //QT的信号和槽机制
camReturn = m_camera->open_device(); //打开摄像头
if(-1==camReturn) //出错处理
{
QMessageBox::warning(this,tr("error"),tr("open /dev/dsp error"),QMessageBox::Yes); m_camera->close_device();
}
camReturn = m_camera->init_device(); //初始化摄像头
if(-1==camReturn)
{
QMessageBox::warning(this,tr("error"),tr("init failed"),QMessageBox::Yes); m_camera->close_device();
}
camReturn = m_camera->start_capturing(); //摄像头开始捕获图像
if(-1==camReturn)
{
QMessageBox::warning(this,tr("error"),tr("start capture failed"),QMessageBox::Yes); m_camera->close_device();
}
if(-1==camReturn)
{
QMessageBox::warning(this,tr("error"),tr("get frame failed"),QMessageBox::Yes); m_camera->stop_capturing();
}
timer = new QTimer(this); //设定时间间隔,对图像界面进行刷新 connect(timer,SIGNAL(timeout()),this,SLOT(update())); //即每隔30ms重新绘制图像界面,update()触发paintEvent()???
timer->start(30); //设置定时器每30毫秒发送一个timeout()信号
cascadeFile = haarXML; //级联分类器文件
cascade = (CvHaarClassifierCascade *) cvLoad(cascadeFile.toUtf8()); //从指定的文件目录中加载级联分类器文件
cascadeFile_profile = haarXML_profile; //级联分类器文件
cascade_profile = (CvHaarClassifierCascade *) cvLoad(cascadeFile_profile.toUtf8()); //从指定的文件目录中加载级联分类器文件
m_FaceCount = 0; //记录检测到的人脸序列长度
storage = cvCreateMemStorage(0); //创建一个内存存储器,参数为0时内存块默认大小为64k
}
Widget::~Widget() //析构函数
{
delete ui;
}
void Widget::changeEvent(QEvent *e) {
QWidget::changeEvent(e);
switch (e->type())
{
case QEvent::LanguageChange: ui->retranslateUi(this); //语言方面的重翻译
break;
default:
break;
}
}
void Widget::paintEvent(QPaintEvent *)
{
uchar * pImgBuf;
unsigned int len;
camReturn = m_camera->get_frame((void **)&pImgBuf,&len); //摄像头开始取帧图像,摄像头取回来的帧是YUV格式,将取回帧图像的起始地址给pImgBuf,帧长度给len convert_yuv_to_rgb_buffer(pImgBuf,imgBuf,image_width,image_height); //将YUV格式转换成RGB格式
frame->loadFromData((uchar *)imgBuf,image_width * image_height * 3 * sizeof(char)); //将存储空间imgBuf的大小的内容加载到frame中,frame是QImage类型指针 //为什么QImage图像要转换为IplImage图像???!!! //因为后面处理压缩,放大的函数处理的是IplImage图像
IplImage* src = QImageToIplImage(frame); //将QImage图像转换为IplImage图像
if (!src)
{
printf("img error!");
return;
}
//更改图像大小(后期对人脸检测时间控制会有很大帮助)
//压缩图像大小,提升人脸检测的速度
double sizeScale = imgSizeScaleSmall; //0.7
CvSize img_cvsize; //CvSize数据结构,表示矩阵框大小,以像素为精度
img_cvsize.width = src->width * sizeScale; //压缩图像宽度
img_cvsize.height = src->height * sizeScale; //压缩图像高度
IplImage* dst = cvCreateImage(img_cvsize, src->depth, src->nChannels); //创建首地址dst并分配存储空间
cvResize(src, dst, CV_INTER_LINEAR); //函数cvResize 重新调整图像src,使它精确匹配目标dst,双线性插值(默认)
detect_and_draw(dst); //调用人脸检测函数,传入的是IplImage图像类型 //更改图像大小,清晰度会下降 //恢复原图像大小,但图像分辨率有所下降,图像较原始图像模糊
sizeScale = imgSizeScaleBig; //2,这样一来实际是放大1.4倍???
img_cvsize.width = dst->width * sizeScale;
img_cvsize.height = dst->height * sizeScale;
IplImage* img = cvCreateImage(img_cvsize, dst->depth, dst->nChannels); //创建首地址img并分配存储空间
cvResize(dst, img, CV_INTER_LINEAR); //函数cvResize 重新调整图像dst,使它精确匹配目标img,双线性插值(默认)
QImage qimage = QImage((uchar *)img->imageData,img->width,img->height,image_Format);//根据压缩放大后的img->imageData创建QImage图像 //IplImage为BGR格式,QImage为RGB格式,所以要交换B和R通道显示才正常 //可以用OpenCV的cvConcertImage函数交换,也可以用QImage的rgbSwapped函数交换
qimage = qimage.rgbSwapped(); //又将IplImage图像转化为QImage图像,用作显示 ui->m_imgLabel->setPixmap(QPixmap::fromImage(qimage)); //将QImage转换为QPixmap,QImage是设计并优化来为 I/O操作的,可以直接访问和操作像素,而QPixmap是设计并优化来在屏幕上显示图片的
camReturn = m_camera->unget_frame(); //摄像头停止取帧
cvReleaseImage(&img); //释放图片内存,cvReleaseImage(&dst) ?????
cvReleaseImage(&src);
}
void Widget::display_error(QString err)
{
QMessageBox::warning(this,tr("error"), err,QMessageBox::Yes);
}
/*yuv格式转换为rgb格式*/
int Widget::convert_yuv_to_rgb_buffer(unsigned char *yuv, unsigned char *rgb, unsigned int width, unsigned int height)
{
unsigned int in, out = 0;
unsigned int pixel_16;
unsigned char pixel_24[3];
unsigned int pixel32; int y0, u, y1, v;
for(in = 0; in < width * height * 2; in += 4) {
pixel_16 = yuv[in + 3] << 24 | yuv[in + 2] << 16 | yuv[in + 1] << 8 | yuv[in + 0];
y0 = (pixel_16 & 0x000000ff);
u = (pixel_16 & 0x0000ff00) >> 8;
y1 = (pixel_16 & 0x00ff0000) >> 16;
v = (pixel_16 & 0xff000000) >> 24;
pixel32 = convert_yuv_to_rgb_pixel(y0, u, v); //这里用到convert_yuv_to_rgb_pixel
pixel_24[0] = (pixel32 & 0x000000ff);
pixel_24[1] = (pixel32 & 0x0000ff00) >> 8;
pixel_24[2] = (pixel32 & 0x00ff0000) >> 16;
rgb[out++] = pixel_24[0];
rgb[out++] = pixel_24[1];
rgb[out++] = pixel_24[2];
pixel32 = convert_yuv_to_rgb_pixel(y1, u, v); //这里用到convert_yuv_to_rgb_pixel
pixel_24[0] = (pixel32 & 0x000000ff);
pixel_24[1] = (pixel32 & 0x0000ff00) >> 8;
pixel_24[2] = (pixel32 & 0x00ff0000) >> 16;
rgb[out++] = pixel_24[0];
rgb[out++] = pixel_24[1];
rgb[out++] = pixel_24[2];
}
return 0;
}
/*yuv格式转换为rgb格式子函数*/
int Widget::convert_yuv_to_rgb_pixel(int y, int u, int v) {
unsigned int pixel32 = 0;
unsigned char *pixel = (unsigned char *)&pixel32;
int r, g, b;
r = y + (1.370705 * (v-128));
g = y - (0.698001 * (v-128)) - (0.337633 * (u-128));
b = y + (1.732446 * (u-128));
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;
pixel[0] = r * 220 / 256;
pixel[1] = g * 220 / 256;
pixel[2] = b * 220 / 256;
return pixel32;
}
//QImage图像转换为IplImage图像
IplImage* Widget::QImageToIplImage(const QImage * qImage) {
int width = qImage->width();
int height = qImage->height();
CvSize Size; //表示矩阵框大小,以像素为精度
Size.height = height; //传递了qImage的高和宽
Size.width = width;
IplImage *IplImageBuffer = cvCreateImage(Size, IPL_DEPTH_8U, 3); //创建首地址并分配存储空间cvCreateImage( CvSize size, int depth, int channels )
for (int y = 0; y < height; ++y) //复制像素
{
for (int x = 0; x < width; ++x)
{
QRgb rgb = qImage->pixel(x, y); //取qImage的像素点
cvSet2D(IplImageBuffer, y, x, CV_RGB(qRed(rgb), qGreen(rgb), qBlue(rgb))); //彩色,在空间为IplImageBuffer的y行x列画彩色点 //灰色的可否?
}
}
return IplImageBuffer;
}
//接着就是对其进行人脸检测处理。
//人脸检测函数
void Widget::detect_and_draw(IplImage *img)
{ static CvScalar colors[] =
//用来存放4个double数值的数组,一般用来存放像素值,最多可以存放4个通道的
{ {{0,0,255}}, //蓝 {{0,128,255}}, {{0,255,255}}, //浅蓝 {{0,255,0}}, //绿 {{255,128,0}}, {{255,255,0}}, //黄 {{255,0,0}}, //红 {{255,0,255}} //紫
}; //RGB,表示八种不同的颜色
double scale = 2.0; //此变量关系到人脸检测的范围精度
IplImage* gray = cvCreateImage( cvSize(img->width,img->height), 8, 1 ); //建立一个空的灰度图,通道为1表示灰度图,深度为8
IplImage* small_img = cvCreateImage( cvSize( cvRound (img->width/scale), //将图片缩小,加快检测速度,cvRound对一个double型的数进行四舍五入,并返回一个整型数
cvRound (img->height/scale)), 8, 1 ); //建立一个空的缩小的灰度图
int i; //写的不好!!!!!!! //因为用的是类haar特征,所以都是基于灰度图像的,这里要转换成灰度图像
cvCvtColor( img, gray, CV_BGR2GRAY ); //图像转换 BGR格式(IplImage的图像格式)转为灰度图 cvResize( gray, small_img, CV_INTER_LINEAR ); //将尺寸缩小到1/scale,用双线性插值 cvEqualizeHist( small_img, small_img ); //使灰度图象直方图均衡化,图像对比度增强(src,dst) cvClearMemStorage( storage ); //新建一块存储区,以备后用
if( cascade ) //若找到级联分类器
{
double t = (double)cvGetTickCount(); //用来计算算法执行时间 //检测人脸 //cvHaarDetectObjects函数中faces表示检测到的人脸目标序列,small_img表示的是要检测的输入图像为small_img, //storage 用来存储检测到的一序列候选目标矩形框的内存区域,1.2搜索窗口的比例系数,表示将搜索窗口依次扩大20% //2构成检测目标的相邻矩形的最小个数,表示每一个目标至少要被检测到3次才算是真的目标(因为周围的像素和不同的窗口大 //小都可以检测到人脸),cvSize(30, 30)为检测窗口的最小尺寸
CvSeq* faces = cvHaarDetectObjects( small_img, cascade, storage, //序列CvSeq 1.2, 2, 0, cvSize(30, 30) ); //??????????CV_HAAR_DO_CANNY_PRUNING????cvSize(30, 30) //为了能对视频图像进行更快的实时检测,参数设置通常是: //scale_factor=1.2, min_neighbors=2, flags=CV_HAAR_DO_CANNY_PRUNING, min_size=<minimum possible face size> t = (double)cvGetTickCount() - t; //相减为算法执行的时间
printf( "detection time = %gms\n", t/((double)cvGetTickFrequency()*1000.) ); //检测时间(ms)
for( i = 0; i < (faces ? faces->total : 0); i++ )
{
CvRect* r = (CvRect*)cvGetSeqElem( faces, i ); //返回索引指定的元素指针,seq是需要检测的序列,index是元素在序列中的索引
CvPoint rectP1, rectP2;
rectP1.x = cvRound(r->x * scale); //还原成原来的大小
rectP1.y = cvRound(r->y * scale);
rectP2.x = cvRound((r->x + r->width) * scale);
rectP2.y = cvRound((r->y + r->height) * scale);
cvRectangle(img, rectP1, rectP2, colors[i%8], 3, 8, 0); //人脸检测方框,通过对角线上的两个顶点绘制矩形
}
m_FaceCount = faces->total; //人脸目标序列总长
}
cvReleaseImage( &gray );
cvReleaseImage( &small_img );
}