本文记录安装openCV以及在Qt上创建桌面程序进行人脸识别的全过程。

安装OpenCV参考自博客:

Qt配置OpenCV教程,亲测已试过(详细版):https://blog.csdn.net/weixin_43763292/article/details/112975207

Qt人脸识别程序参考自博客:

基于Qt的OpenCV人脸识别:https://blog.csdn.net/huhuandk/category_8468159.html

一、安装环境

  • OS:Windows10

  • Qt版本:5.15.2

  • CMake版本:3.20.2

  • OpenCV

    版本:起初选择的版本是4.5.5,安装完后发现程序运行有问题,后来换成了3.4.16版本。

    说明:安装版本4.5.5,运行Qt程序总是崩溃,下意识怀疑是该版本与Qt版本不匹配。但实际是不是版本问题尚不确定,因为是第一次安装和在Qt上使用OpenCV,可能存在误判。后续需要重新测试一下4.5.5版本。

  • OpenCV-contrib

    与OpenCV版本一致(3.4.16),这是从OpenCV分离出来的未稳定的功能模块,人脸识别需要的face库就在该文件下。到github上选择相应的tag下载。

二、安装前配置

添加三个路径到系统PATH:

  1. Qt库的MinGw路径:Qt/5.15.2/mingw81_64/bin

  2. Qt的MinGw工具路径:Qt/Tools/mingw810_64/bin

  3. CMake路径:CMake/bin

配置完后需要重启电脑。

三、下载

OpenCV和OpenCV-contrib,这两个都可以在github上下载

四、安装

  1. 将OpenCV文件解压至将要安装的目录下:D:/Tools/opencv/opencv-3.4.16

  2. 将OpenCV-contrib放到OpenCV的解压目录下,与sources同级:D:/Tools/opencv/opencv-3.4.16/opencv-contrib-3.4.16

  3. 前往CMake目录下运行cmake-gui.exe,

    1. 配置source code目录为sources路径:D:/Tools/opencv/opencv-3.4.16/sources

    2. 配置build目录为与sources同级的新建目录opencv-build:D:/Tools/opencv/opencv-3.4.16/opencv-build

    3. 勾选Advanced

    4. 点击Configure

      1. 选择MinGw MakefilesSpecify native compilers

      2. 选择Compilers,

        • C:Qt/Tools/mingw810_64/bin/gcc.exe

        • C++:Qt/Tools/mingw810_64/bin/g++.exe

      3. Finish

    5. Configuring done后,在opencv配置参数中选中WITH_OPENGLWITH_QT,搜索OPENCV_EXTRA_MODULES_PATH,填入opencv-contrib目录下的modules路径:D:/Tools/opencv/opencv-3.4.16/opencv_contrib-3.4.16/modules

    6. 点击Configure,done之后,opencv配置参数中有红色条框,确认路径无误后,再次点击Configure

    7. 红色消失,Generate,生成完成

  4. 进入opencv-build目录下,生成安装文件:mingw32-make -j 12

  5. 100%完成后,安装:mingw32-make install,完成

五、将OpenCV加入PATH

添加动态链接库:D:/Tools/opencv/opencv-3.4.16/opencv-build/install/x64/mingw/bin

六、Qt人脸识别程序

  1. 新建Qt的MainWindow工程opencv-test,选择生成器qmake,编译工具Qt MinGw 64-bit

  2. 设计ui,一个用于显示摄像头拍摄的QLabel,三个QPushButton分别是开启摄像头,关闭摄像头,拍照

  3. 在opencv-test.pro文件中添加头文件搜索路径(INCLUDEPATH)和链接库(LIBS),opencv-test.pro文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    QT       += core gui

    greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

    CONFIG += c++11

    # You can make your code fail to compile if it uses deprecated APIs.
    # In order to do so, uncomment the following line.
    #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

    SOURCES += \
    main.cpp \
    mainwindow.cpp

    HEADERS += \
    mainwindow.h

    FORMS += \
    mainwindow.ui

    #添加这两条
    INCLUDEPATH += D:\Tools\opencv\opencv-3.4.16\opencv-build\install\include
    LIBS += D:\Tools\opencv\opencv-3.4.16\opencv-build\lib\libopencv_*.a

    # Default rules for deployment.
    qnx: target.path = /tmp/$${TARGET}/bin
    else: unix:!android: target.path = /opt/$${TARGET}/bin
    !isEmpty(target.path): INSTALLS += target
  4. 头文件mainwindow.h

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H

    #include <QMainWindow>

    #include <opencv2/core/core.hpp>
    #include <opencv2/highgui/highgui.hpp>
    #include <opencv2/imgproc/imgproc.hpp>
    #include <opencv2/objdetect.hpp>
    #include <opencv2/opencv.hpp>
    #include <opencv2/face.hpp>

    using namespace cv;
    using namespace cv::face;

    QT_BEGIN_NAMESPACE
    namespace Ui { class MainWindow; }
    QT_END_NAMESPACE

    class MainWindow : public QMainWindow
    {
    Q_OBJECT

    public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    private slots:
    void on_openBtn_clicked();
    void on_captureBtn_clicked();
    void on_closeBtn_clicked();

    private:
    Ui::MainWindow *ui;
    VideoCapture capture;
    Mat frame;
    QImage image;
    CascadeClassifier faceDetector;
    CV_OUT std::vector<Rect> faces;
    Ptr<BasicFaceRecognizer>model;
    private:
    void closeEvent(QCloseEvent *event);
    QImage Mat2QImage(Mat cvImg);
    void Sleep(int msec);
    };
    #endif // MAINWINDOW_H
  5. 源文件mainwindow.cpp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    #include <QTime>
    #include <QDebug>

    #include <vector>
    #include <QTimer>

    using namespace std;
    using namespace cv;

    void Delay_MSec(unsigned int msec)
    {
    QEventLoop loop;//定义一个新的事件循环
    QTimer::singleShot(msec, &loop, SLOT(quit()));//创建单次定时器,槽函数为事件循环的退出函数
    loop.exec();//事件循环开始执行,程序会卡在这里,直到定时时间到,本循环被退出
    }

    MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    {
    ui->setupUi(this);
    //加载人脸检测模型
    if(!faceDetector.load("D:\\Tools\\opencv\\opencv-3.4.16\\opencv-build\\install\\etc\\haarcascades\\haarcascade_frontalface_alt.xml"))
    {
    qDebug() << "--(!)Error loading";
    QApplication::exit();
    }
    model =EigenFaceRecognizer::create();
    vector<Mat>images;
    vector<int>labels;
    Mat mat;
    /**不直接读成灰度图,而是多经过一步转换,是因为在train时会报错:
    OpenCV: terminate handler is called! The last OpenCV error is:
    OpenCV(3.4.16) Error: Image step is wrong (The matrix is not continuous, thus its number of rows can not be changed) in reshape, file D:\Tools\opencv\opencv-3.4.16\sources\modules\core\src\matrix.cpp, line 1115
    从StackOverflow上看到这个问题可能的原因:1、未找到图片,2、图像矩阵不连续,注意图片编码格式,解决方法是多经一步转换
    **/
    mat=imread("myFace/0.png",1);//一般样本是提前准本好的,从文件里读取。测试程序就简单点直接从文件夹里读图片。
    cvtColor(mat,mat,CV_BGR2GRAY);//多一步转换,解决上述报错
    images.push_back(mat);

    mat=imread("myFace/1.png",1);
    cvtColor(mat,mat,CV_BGR2GRAY);
    images.push_back(mat);

    mat=imread("myFace/2.png",1);
    cvtColor(mat,mat,CV_BGR2GRAY);
    images.push_back(mat)

    labels.push_back(0);
    labels.push_back(0);
    labels.push_back(0);

    model->train(images,labels);//训练
    }

    MainWindow::~MainWindow()
    {
    delete ui;
    }


    void MainWindow::on_openBtn_clicked()
    {
    if(capture.isOpened())
    return;
    capture.open(0);

    if(capture.isOpened())
    {
    for(;;)
    {
    if(!capture.isOpened())
    break;
    capture >> frame;
    flip(frame, frame, 1); //画面翻转
    if (!frame.empty())
    {
    //人脸检测
    Mat frame_gray;
    Mat dst;
    Mat testSample;
    cvtColor(frame, frame_gray, COLOR_BGR2GRAY);
    equalizeHist(frame_gray, frame_gray);
    faceDetector.detectMultiScale(frame_gray, faces, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(30, 30));
    for (int i = 0; i < faces.size(); i++)
    {
    #if 0
    //只摄像
    rectangle(frame, faces[i], Scalar(0, 0, 255), 1, 8, 0);
    #else
    //人脸识别
    Mat roi = frame(faces[i]);
    cvtColor(roi, dst, COLOR_BGR2GRAY);
    cv::resize(dst, testSample, Size(92, 112));
    int label = model->predict(testSample);
    rectangle(frame, faces[i], Scalar(0, 0, 255), 2, 8, 0);
    putText(frame, format("I'm %s", (label == 0 ? "ly" : "Unknow")),
    faces[i].tl(), FONT_HERSHEY_PLAIN, 1.0, Scalar(0, 0, 255), 2, 8);
    #endif
    }
    //显示
    image = Mat2QImage(frame);
    ui->label->setPixmap(QPixmap::fromImage(image));
    }
    //Sleep(10); //延时10ms
    Delay_MSec(10);
    }
    }
    }

    //发现如果不关闭摄像头,直接关闭窗口,则程序不会被杀死,变成僵尸程序在后台运行,因此点击关闭窗口时,先关闭掉摄像头
    void MainWindow::closeEvent(QCloseEvent *event)
    {
    if(capture.isOpened()) capture.release();
    }

    //要想在Qt上显示OpenCV的图像,必须将Mat转化为QImage
    QImage MainWindow::Mat2QImage(Mat cvImg)
    {
    QImage qImg;
    if(cvImg.channels()==3) //3 channels color image
    {
    cv::cvtColor(cvImg,cvImg,CV_BGR2RGB);
    qImg =QImage((const unsigned char*)(cvImg.data),
    cvImg.cols, cvImg.rows,
    cvImg.cols*cvImg.channels(),
    QImage::Format_RGB888);
    }
    else if(cvImg.channels()==1) //grayscale image
    {
    qImg =QImage((const unsigned char*)(cvImg.data),
    cvImg.cols,cvImg.rows,
    cvImg.cols*cvImg.channels(),
    QImage::Format_Indexed8);
    }
    else
    {
    qImg =QImage((const unsigned char*)(cvImg.data),
    cvImg.cols,cvImg.rows,
    cvImg.cols*cvImg.channels(),
    QImage::Format_RGB888);
    }
    return qImg;
    }


    //延时函数
    void MainWindow::Sleep(int msec)
    {
    QTime dieTime = QTime::currentTime().addMSecs(msec);
    while( QTime::currentTime() < dieTime )
    QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
    }


    void MainWindow::on_captureBtn_clicked()
    {
    static int count = 0;
    Mat dst;
    for (int i = 0; i < faces.size(); i++)
    {
    cv::resize(frame(faces[i]), dst, Size(92, 112));
    cvtColor(dst,dst,COLOR_BGR2RGB);
    imwrite(format("myFace/%d.png", count), dst);
    count++;
    }
    }

    void MainWindow::on_closeBtn_clicked()
    {
    if(capture.isOpened()) capture.release();
    }