windows下Qt调用opencv完成目标检测
一、前言
OpenCV是开源的计算机视觉、机器学习软件库,其图片处理的功能非常强大,并且速度很快。
作为目标检测功能,OpenCV里本身就自带了很多的模型,比如: 人眼检测、鼻子检测、嘴巴检测、人脸检测、人体检测、猫脸检测等等,下载完OpenCV,就能直接进行图像识别测试体验,并且OpenCV也可以直接调用YOLO的模型,精确识别各种物体,yolo v3 里自带的模型文件可以精确识别常见的很多物体: 比如: 狗、汽车、自行车、人体、书本、手机等等。
这篇文章就介绍在Qt里如何部署opencv环境,完成目标物体检测。
二、部署OpenCV开发环境
首先介绍我的开发环境:
我分别使用常用的两种编译器部署OpenCV环境。
MSVC 2017 64位
MinGW 730 32位
OpenCV的官网下载地址: https://opencv.org/releases/page/3/
OpenCV 在2.X版本的时候还有x86的库,从3.X版本开始就只有x64的库,并且只是支持MSVC编译器。
目前我使用的OpenCV版本是: OpenCV 3.4.7
下载地址就是上面的地址,直接向下翻就可以找到这个版本。
下载下来是一个exe文件,双击就可以安装,实际就是解压,可以选择解压的路径,解压出来的文件包含源文件、库文件一大堆,比较大,可以直接放在一个固定的目录,后面程序里直接填路径来调用即可。 这个下载下来的库文件里只包含了X64的库,适用于MSVS 64位
编译器。
如果是MinGw编译器,可以从这里https://github.com/huihut/OpenCV-MinGW-Build下载对应的OpenCV库进行使用。
GitHub的地址在CodeChina有镜像,可以从这里去下载,速度比较快:https://gitcode.net/mirrors/huihut/opencv-mingw-build?utm_source=csdn_github_accelerator
打开链接后,自己去选择适合自己编译器的版本,我的MinGW是730刚好就使用下面这个版本。
下面分别介绍VS2017 64位编译器和MinGW 32位编译器如何引用OpenCV的库。
- MSVC 64位编译器–QT的xx.pro工程文件里的写法
INCLUDEPATH += C:/opencv/build/include\
INCLUDEPATH += C:/opencv/build/include/opencv\
INCLUDEPATH += C:/opencv/build/include/opencv2
LIBS += -LC:/opencv/build/x64/vc14/lib\
-lopencv_world347d
LIBS += -LC:/opencv/build/x64/vc14/lib\
-lopencv_world347
- MinGW 32位编译器–QT的xx.pro工程文件里的写法
INCLUDEPATH+=C:/OpenCV-MinGW-Build-OpenCV-3.4.7/include \
C:/OpenCV-MinGW-Build-OpenCV-3.4.7/include/opencv \
C:/OpenCV-MinGW-Build-OpenCV-3.4.7/include/opencv2
LIBS+=C:/OpenCV-MinGW-Build-OpenCV-3.4.7/x86/mingw/bin/libopencv_*.dll
工程编程成功之后,需要将OpenCV对应的dll文件拷贝到exe同级目录,否则运行时找不到dll会导致程序异常结束。 这些dll文件就是在OpenCV的bin目录下。
三、调用OpenCV完成目标检测(以人脸检测为例)
OpenCV自带的模型文件在C:\opencv\sources\data\haarcascades_cuda
这个目录下。
这个就是人脸检测模型文件:
OpenCV完成目标检测有两种调用方法。
3.1 方法1: cvHaarDetectObjects
//人脸检测代码
void ImageHandle::opencv_face(QImage qImage)
{
QTime time;
time.start();
static CvMemStorage* storage = nullptr;
static CvHaarClassifierCascade* cascade = nullptr;
//加载分类器:正面脸检测
cascade = (CvHaarClassifierCascade*)cvLoad("C:/opencv/sources/data/haarcascades_cuda/haarcascade_frontalface_alt2.xml", 0, 0, 0 );
if(!cascade)
{
qDebug()<<"分类器加载错误.\n";
return ;
}
//创建内存空间
storage = cvCreateMemStorage(0);
//加载需要检测的图片
IplImage* img = QImageToIplImage(&qImage);
if(img ==nullptr )
{
qDebug()<<"图片加载错误.\n";
return;
}
double scale=2;
//创建图像首地址,并分配存储空间
IplImage* gray = cvCreateImage(cvSize(img->width,img->height),8,1);
//创建图像首地址,并分配存储空间
IplImage* small_img=cvCreateImage(cvSize(cvRound(img->width/scale),cvRound(img->height/scale)),8,1);
cvCvtColor(img,gray, CV_BGR2GRAY);
cvResize(gray, small_img, CV_INTER_LINEAR);
cvEqualizeHist(small_img,small_img); //直方图均衡
/*
* 指定相应的人脸特征检测分类器,就可以检测出图片中所有的人脸,并将检测到的人脸通过矩形的方式返回。
* 总共有8个参数,函数说明:
参数1:表示输入图像,尽量使用灰度图以加快检测速度。
参数2:表示Haar特征分类器,可以用cvLoad()函数来从磁盘中加载xml文件作为Haar特征分类器。
参数3:用来存储检测到的候选目标的内存缓存区域。
参数4:表示在前后两次相继的扫描中,搜索窗口的比例系数。默认为1.1即每次搜索窗口依次扩大10%
参数5:表示构成检测目标的相邻矩形的最小个数(默认为3个)。如果组成检测目标的小矩形的个数和小于 min_neighbors - 1 都会被排除。如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,这种设定值一般用在用户自定义对检测结果的组合程序上。
参数6:要么使用默认值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果设置为CV_HAAR_DO_CANNY_PRUNING,那么函数将会使用Canny边缘检测来排除边缘过多或过少的区域,因此这些区域通常不会是人脸所在区域。
参数7:表示检测窗口的最小值,一般设置为默认即可。
参数8:表示检测窗口的最大值,一般设置为默认即可。
函数返回值:函数将返回CvSeq对象,该对象包含一系列CvRect表示检测到的人脸矩形。
*/
CvSeq* objects = cvHaarDetectObjects(small_img,
cascade,
storage,
1.1,
3,
0/*CV_HAAR_DO_CANNY_PRUNING*/,
cvSize(50,50)/*大小决定了检测时消耗的时间多少*/);
qDebug()<<"人脸数量:"<<objects->total;
//遍历找到对象和周围画盒
QPainter painter(&qImage);//构建 QPainter 绘图对象
QPen pen;
pen.setColor(Qt::blue); //画笔颜色
pen.setWidth(5); //画笔宽度
painter.setPen(pen); //设置画笔
for(int i=0;i<(objects->total);++i)
{
//得到人脸的坐标位置和宽度高度信息
CvRect* r=(CvRect*)cvGetSeqElem(objects,i);
//将人脸区域绘制矩形圈起来
painter.drawRect(r->x*scale,r->y*scale,r->width*scale,r->height*scale);
}
cvReleaseImage(&gray); //释放图片内存
cvReleaseImage(&small_img); //释放图片内存
cvReleaseHaarClassifierCascade(&cascade); //释放内存-->分类器
cvReleaseMemStorage(&objects->storage); //释放内存-->检测出图片中所有的人脸
//释放图片
cvReleaseImage(&img);
qDebug()<<tr("耗时:%1 ms\n").arg(time.elapsed());
qDebug()<<"子线程:"<<QThread::currentThread();
//保存结果
m_image=qImage.copy();
}
/*将QImage图片转为opecv的qimage格式*/
IplImage *ImageHandle::QImageToIplImage(const QImage * qImage)
{
int width = qImage->width();
int height = qImage->height();
CvSize Size;
Size.height = height;
Size.width = width;
IplImage *IplImageBuffer = cvCreateImage(Size, IPL_DEPTH_8U, 3);
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
QRgb rgb = qImage->pixel(x, y);
CV_IMAGE_ELEM( IplImageBuffer, uchar, y, x*3+0 ) = qBlue(rgb);
CV_IMAGE_ELEM( IplImageBuffer, uchar, y, x*3+1 ) = qGreen(rgb);
CV_IMAGE_ELEM( IplImageBuffer, uchar, y, x*3+2 ) = qRed(rgb);
}
}
return IplImageBuffer;
}
3.2 方法2: face_cascade.detectMultiScale
//人脸检测代码
void ImageHandle::opencv_face(QImage qImage)
{
QTime time;
time.start();
//定义级联分类器
CascadeClassifier face_cascade;
//加载分类文件
if(!face_cascade.load("C:/opencv/sources/data/haarcascades_cuda/haarcascade_frontalface_alt2.xml") )
{
qDebug()<<"分类器加载错误";
return;
}
Mat frame=QImage2cvMat(qImage);
cvtColor(frame, frame, COLOR_BGR2GRAY );//转换成灰度图像
std::vector<Rect> faces;
//正脸检测
face_cascade.detectMultiScale(frame,faces);
qDebug()<<tr("耗时:%1 ms 识别:%2 数量:%3\n").arg(time.elapsed()).arg(faces.size()).arg(faces.size());
for ( size_t i = 0; i < faces.size(); i++ )
{
//绘图画框和画圈
Point center(faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2);
ellipse(frame, center, Size( faces[i].width/2, faces[i].height/2 ), 0, 0, 360, Scalar( 255, 0, 255 ), 4 );
rectangle(frame,
cvPoint(cvRound(faces[i].x), cvRound(faces[i].y)),
cvPoint(cvRound((faces[i].x + faces[i].width-1)),
cvRound((faces[i].y + faces[i].height-1))),
Scalar(255, 255, 255), 3, 8, 0);
// //提取识别结果 取出的frame1 就是框住的图像
// Mat frame1;
// for(size_t i=0;i<faces.size();i++)
// {
// Point center(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height / 2);
// frame1= frame(Rect(faces[i].x, faces[i].y, faces[i].width, faces[i].height));
// }
}
/*在控件上显示*/
m_image=Mat2QImage(frame);
}
Mat ImageHandle::QImage2cvMat(QImage image)
{
Mat mat;
switch(image.format())
{
case QImage::Format_ARGB32:
case QImage::Format_RGB32:
case QImage::Format_ARGB32_Premultiplied:
mat = Mat(image.height(), image.width(), CV_8UC4, (void*)image.constBits(), image.bytesPerLine());
break;
case QImage::Format_RGB888:
mat = Mat(image.height(), image.width(), CV_8UC3, (void*)image.constBits(), image.bytesPerLine());
cvtColor(mat, mat, CV_BGR2RGB);
break;
case QImage::Format_Indexed8:
mat = Mat(image.height(), image.width(), CV_8UC1, (void*)image.constBits(), image.bytesPerLine());
break;
}
return mat;
}
QImage ImageHandle::Mat2QImage(const Mat& mat)
{
// 8-bits unsigned, NO. OF CHANNELS = 1
if(mat.type() == CV_8UC1)
{
QImage image(mat.cols, mat.rows, QImage::Format_Indexed8);
// Set the color table (used to translate colour indexes to qRgb values)
image.setColorCount(256);
for(int i = 0; i < 256; i++)
{
image.setColor(i, qRgb(i, i, i));
}
// Copy input Mat
uchar *pSrc = mat.data;
for(int row = 0; row < mat.rows; row ++)
{
uchar *pDest = image.scanLine(row);
memcpy(pDest, pSrc, mat.cols);
pSrc += mat.step;
}
return image;
}
// 8-bits unsigned, NO. OF CHANNELS = 3
else if(mat.type() == CV_8UC3)
{
// Copy input Mat
const uchar *pSrc = (const uchar*)mat.data;
// Create QImage with same dimensions as input Mat
QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_RGB888);
return image.rgbSwapped();
}
else if(mat.type() == CV_8UC4)
{
// Copy input Mat
const uchar *pSrc = (const uchar*)mat.data;
// Create QImage with same dimensions as input Mat
QImage image(pSrc, mat.cols, mat.rows, mat.step, QImage::Format_ARGB32);
return image.copy();
}
else
{
return QImage();
}
}
- 点赞
- 收藏
- 关注作者
评论(0)