Qt-OpenCV学习笔记–人脸识别

  • Post author:
  • Post category:其他


前言

本人从事机械设计12年,业余时间自学编程。

2022年4月6日,开始学习C#,

2022年9月7日,开始学习c++和Qt,

2022年10月28日,开始学习OpenCV,

今天终于搞定了传说中的

人脸识别

,在此,做个记录。

人脸检测,是基于Haar特征的cascade分类器,

人脸识别,是基于LDA理论的Fisherface算法。

话不多说,上视频!(CSDN上传的视频,太清晰!)

人脸识别测试程序

测试代码

FaceRecognition.pro

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 \
    sm.cpp \
    widget.cpp

HEADERS += \
    sm.h \
    widget.h

FORMS += \
    widget.ui

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

unix|win32: LIBS += -L$$PWD/../../../../../opencv/install/x64/mingw/lib/ -llibopencv_world460.dll

INCLUDEPATH += $$PWD/../../../../../opencv/install/include
DEPENDPATH += $$PWD/../../../../../opencv/install/include

sm.h

#ifndef SM_H
#define SM_H

#include <iostream>
#include "opencv2/core.hpp"



class sm
{
public:
    sm();

    //读取文件
    static void read_csv(const std::string& filename, std::vector<cv::Mat>& images, std::vector<int>& labels, char separator);

    //图像预处理:检测人脸、裁剪、缩放、保存、生成列表
    static void pretreatment(std::vector<cv::Mat> images, std::vector<int> labels,std::string path,int width,int height);

};

#endif // SM_H

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

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

private slots:
    void on_pushButton_clicked();

    void on_pushButton_7_clicked();

    void on_pushButton_2_clicked();

    void on_pushButton_3_clicked();

    void on_pushButton_5_clicked();

    void on_pushButton_6_clicked();

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

main.cpp

#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}

sm.cpp

#include "sm.h"

//引用依赖
#include "opencv2/core.hpp"
#include "opencv2/face.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <fstream>
#include <sstream>

#include <QDebug>

sm::sm()
{

}

void sm::pretreatment(std::vector<cv::Mat> images, std::vector<int> labels, std::string path, int width, int height)
{
    cv::Mat dst_shear;
    cv::Mat dst_resize;

    //创建级联分类器
    cv::CascadeClassifier cascade;
    //载入Haar特征分类器
    cascade.load("C:/opencv/date/haarcascade_frontalface_default.xml");
    //创建矩形容器
    std::vector<cv::Rect> rects;
    //遍历
    int flag=1;
    for(uint i=0;i<images.size();i++)
    {
        //人脸检测
        cascade.detectMultiScale(images[i],rects);
        //裁剪
        dst_shear = images[i](rects[0]).clone();
        //缩放
        resize(dst_shear,dst_resize,cv::Size(width,height),0,0,cv::INTER_AREA);
        //保存路径拼接
        std::string im_path1 = std::to_string(labels[i]);
        std::string im_path2 = std::to_string(flag);
        flag++;
        if(labels[i+1]!=labels[i])
        {
            flag=1;
        }
        std::string im_path3 = ".png";
        std::string im_path = path+im_path1+"-"+im_path2+im_path3;

        //调试
        QString qstr_im = QString::fromStdString(im_path);
        qDebug()<<qstr_im;

        //保存
        imwrite(im_path,dst_resize);
        //显示
        //std::string str = std::to_string(i);
        //imshow(str,dst_resize);
        //生成列表文件
        std::string list_name = "list.txt";
        std::string list_path = path + list_name;

        //调试
        QString qstr_list = QString::fromStdString(list_path);
        qDebug()<<qstr_list;

        std::ofstream list(list_path, std::ios::app);
        if (list.fail())
        {
            qDebug()<<"list文件打开失败,请检查文件路径!";
        }
        else
        {
            list<<im_path;
            list<<";";
            list<<im_path1;
            list<<"\n";
        }
    }
}

void sm::read_csv(const std::string &filename, std::vector<cv::Mat> &images, std::vector<int> &labels, char separator)
{
    //以只读方式读取文件
    std::ifstream file(filename, std::ios::in);
    if (!file)
    {
        qDebug()<<"文件打开失败,请检查文件路径!";        
    }
    else
    {
        //逐行读取文本,分离路径和标签
        std::string line, path, classlabel;
        //逐行读取
        while (getline(file, line))
        {
            //将读取到的文本转为字符串流
            std::stringstream stream(line);
            //分离路径
            getline(stream, path, separator);
            //分离标签
            getline(stream, classlabel);
            //若分离成功,则按照路径载入图像,设置标签
            if(!path.empty() && !classlabel.empty())
            {
                images.push_back(cv::imread(path,0));
                labels.push_back(atoi(classlabel.c_str()));
            }
        }
    }
}

widget.cpp

#include "widget.h"
#include "ui_widget.h"

//引用
#include "sm.h"

#include "opencv2/core.hpp"
#include "opencv2/face.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"

#include <iostream>
#include <fstream>
#include <sstream>

#include <QFileDialog>
#include <QDebug>
#include <QMessageBox>
#include <qdatetime.h>

//进行人脸识别的路径
QString face_path;

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

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

//选择文件
void Widget::on_pushButton_clicked()
{
    QString filename = QFileDialog::getOpenFileName(this,"请选择列表文件",".","*.txt");
    if(!filename.isEmpty())
        {
            ui->lineEdit->setText(filename);

        }
}

//选择保存目录
void Widget::on_pushButton_7_clicked()
{
    QString dir = QFileDialog::getExistingDirectory(this,"请选择保存目录",".");
    if(!dir.isEmpty())
        {
            QString str = dir + "/";
            ui->lineEdit_3->setText(str);

        }
}

//训练模型
void Widget::on_pushButton_2_clicked()
{
    //获取文件路径
    std::string src_filename = ui->lineEdit->text().toStdString();
    if(src_filename.empty())
    {
        QMessageBox::warning(this,"警告","图像载入失败,请检查文件路径!");
        return;
    }
    //图像集合
    std::vector<cv::Mat> src_images;
    //标签集合
    std::vector<int> src_labels;
    //加载文件
    sm::read_csv(src_filename,src_images,src_labels,';');
    //判断读取是否成功
    if(src_images.size()<=1||src_labels.size()<=1)
    {        
        QMessageBox::warning(this,"警告","数据量不足,请检查数据列表!");
        return;
    }

    //调试
    qDebug()<<src_images.size();
    qDebug()<<src_labels.size();

    //获取图像保存路径
    std::string dst_path = ui->lineEdit_3->text().toStdString();
    if(dst_path.empty())
    {
        QMessageBox::warning(this,"警告","请检查文件保存路径!");
        return;
    }
    //图像预处理,生成新文件
    sm::pretreatment(src_images,src_labels,dst_path,100,100);

    //获取新文件路径
    std::string dst_filename = dst_path+"list.txt";
    //新图像集合
    std::vector<cv::Mat> dst_images;
    //新标签集合
    std::vector<int> dst_labels;
    //重新加载文件
    sm::read_csv(dst_filename,dst_images,dst_labels,';');

    // 创建模型
    cv::Ptr<cv::face::FisherFaceRecognizer> model = cv::face::FisherFaceRecognizer::create();
    // 训练模型
    model->train(dst_images, dst_labels);
    //保存模型
    model->write(dst_path+"model.xml");

    //提示
    QMessageBox::information(this,"消息","模型训练完成!");

}

//选择模型路径
void Widget::on_pushButton_3_clicked()
{
    QString filename = QFileDialog::getOpenFileName(this,"请选择模型",".","*.xml");
    if(!filename.isEmpty())
        {
            ui->lineEdit_2->setText(filename);

        }
}

//选择需要识别的图像,缩放,保持比例,显示
void Widget::on_pushButton_5_clicked()
{
    face_path = QFileDialog::getOpenFileName(this,"选择一个图片",".","*.jpg *.png *.bmp");
    if(!face_path.isEmpty())
    {
        //加载图像
        QPixmap* pix= new QPixmap;
        pix->load(face_path);
        //图像缩放
        QPixmap* npix= new QPixmap;
        *npix = pix->scaled(ui->label_4->size(),Qt::KeepAspectRatio);
        //显示
        ui->label_4->setPixmap(*npix);

    }

}

//人脸识别
void Widget::on_pushButton_6_clicked()
{
    cv::Mat src,
            dst_shear,
            dst_resize;


    //创建级联分类器
    cv::CascadeClassifier cascade;
    //载入Haar特征分类器
    cascade.load("C:/opencv/date/haarcascade_frontalface_default.xml");

    //加载图像
    if(face_path.isEmpty())
    {
        QMessageBox::warning(this,"警告","请先选择一个图像!");
        return;
    }
    else
    {
        src = cv::imread(face_path.QString::toStdString(),0);
    }

    //创建矩形容器
    std::vector<cv::Rect> rects;
    //识别人脸
    cascade.detectMultiScale(src,rects);

    //裁剪图像
    dst_shear = src(rects[0]).clone();

    //缩放
    cv::resize(dst_shear,dst_resize,cv::Size(100,100),0,0,cv::INTER_AREA);

    if(ui->lineEdit_2->text().isEmpty())
    {
        QMessageBox::warning(this,"警告","请检查模型加载路径!");
    }
    else
    {
        // 创建模型
        cv::Ptr<cv::face::FisherFaceRecognizer> model = cv::face::FisherFaceRecognizer::create();
        //载入训练好的模型
        model->read(ui->lineEdit_2->text().QString::toStdString());

        //进行识别
        int predictedLabel;
        double confidence;
        model->predict(dst_resize,predictedLabel,confidence);

        //打印结果
        QDateTime cur = QDateTime::currentDateTime();

        QString str;
        switch (predictedLabel)
        {
            case 1:
                str = "周敏慧";
                break;
            case 2:
                str = "林志玲";
                break;
            case 3:
                str = "黄渤";
                break;
            case 4:
                str = "单大伟";
                break;
            default:
                str = "这个人我不认识!";
        }

        ui->textBrowser->append(cur.toString("yyyy-MM-dd hh:mm:ss"));
        ui->textBrowser->append(str);
    }
}

widget.ui

测试结果

综上,将导入的图像进行裁剪和缩放,仅保存人脸部分,用于训练模型;然后加载训练好的模型,进行人脸识别,最后将识别的信息予以显示。

代码经过修改,可以用于

门禁系统

或者

人脸打卡



版权声明:本文为ssismm原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。