windows下Qt调用opencv完成目标检测

1,832 阅读6分钟

这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战

一、前言

OpenCV是开源的计算机视觉、机器学习软件库,其图片处理的功能非常强大,并且速度很快。 作为目标检测功能,OpenCV里本身就自带了很多的模型,比如: 人眼检测、鼻子检测、嘴巴检测、人脸检测、人体检测、猫脸检测等等,下载完OpenCV,就能直接进行图像识别测试体验,并且OpenCV也可以直接调用YOLO的模型,精确识别各种物体,yolo v3 里自带的模型文件可以精确识别常见的很多物体: 比如: 狗、汽车、自行车、人体、书本、手机等等。

这篇文章就介绍在Qt里如何部署opencv环境,完成目标物体检测。

二、部署OpenCV开发环境

首先介绍我的开发环境:

我分别使用常用的两种编译器部署OpenCV环境。

  1. MSVC 2017 64位
  2. MinGW 730 32位

OpenCV的官网下载地址: opencv.org/releases/pa…

image.png

OpenCV 在2.X版本的时候还有x86的库,从3.X版本开始就只有x64的库,并且只是支持MSVC编译器。

目前我使用的OpenCV版本是: OpenCV 3.4.7

下载地址就是上面的地址,直接向下翻就可以找到这个版本。

image.png

下载下来是一个exe文件,双击就可以安装,实际就是解压,可以选择解压的路径,解压出来的文件包含源文件、库文件一大堆,比较大,可以直接放在一个固定的目录,后面程序里直接填路径来调用即可。 这个下载下来的库文件里只包含了X64的库,适用于MSVS 64位编译器。

如果是MinGw编译器,可以从这里github.com/huihut/Open…下载对应的OpenCV库进行使用。

GitHub的地址在CodeChina有镜像,可以从这里去下载,速度比较快:gitcode.net/mirrors/hui…

打开链接后,自己去选择适合自己编译器的版本,我的MinGW是730刚好就使用下面这个版本。 image.png

下面分别介绍VS2017 64位编译器和MinGW 32位编译器如何引用OpenCV的库。

  1. 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
  1. 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目录下。

image.png

三、调用OpenCV完成目标检测(以人脸检测为例)

OpenCV自带的模型文件在C:\opencv\sources\data\haarcascades_cuda 这个目录下。

image.png

这个就是人脸检测模型文件:

image.png

OpenCV完成目标检测有两种调用方法。

3.1 方法1: cvHaarDetectObjects

image.png

//人脸检测代码
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

image.png

//人脸检测代码
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();
    }
}