【QT】基于人脸识别的打卡系统(QT+Opencv + SQLite)--实现过程

交流群号:245022761(IT项目交流群)

目录

工具准备      ​

工程结构解析

配置文件中加入opencv相关文件

代码实现

(1)检测人脸

(2)采集头像

(3)训练模型

(4)人脸识别


工具准备

           1、安装Opencv,并添加到环境变量中  F:\Qt\opencv64\x64\mingw\bin

               【QT】基于人脸识别的打卡系统(QT+Opencv + SQLite)--实现过程_第1张图片

          2、将Opencv下的人脸检测分类器(haarcascade_frontalface_alt2.xml)拷贝到自己的工程下

          【QT】基于人脸识别的打卡系统(QT+Opencv + SQLite)--实现过程_第2张图片

         3、opencv3的人脸识别库拷贝到自己的工程下(src整个文件夹、face整个文件夹、face.hpp)

           下载地址:https://github.com/opencv/opencv_contrib

           【QT】基于人脸识别的打卡系统(QT+Opencv + SQLite)--实现过程_第3张图片

工程结构解析

一图看懂工程结构

【QT】基于人脸识别的打卡系统(QT+Opencv + SQLite)--实现过程_第4张图片

 

配置文件中加入opencv相关文件

# 添加src目录下的源文件
SOURCES += \
        main.cpp \
        faceattendance.cpp\
    src/bif.cpp \
    src/eigen_faces.cpp \
    src/face_alignment.cpp \
    src/face_basic.cpp \
    src/facemark.cpp \
    src/facemarkAAM.cpp \
    src/facemarkLBF.cpp \
    src/facerec.cpp \
    src/fisher_faces.cpp \
    src/getlandmarks.cpp \
    src/lbph_faces.cpp \
    src/mace.cpp \
    src/predict_collector.cpp \
    src/regtree.cpp \
    src/trainFacemark.cpp \
    admiwin.cpp \
    train.cpp \
    recordwin.cpp

HEADERS += \
        faceattendance.h\
    face/bif.hpp \
    face/face_alignment.hpp \
    face/facemark.hpp \
    face/facemark_train.hpp \
    face/facemarkAAM.hpp \
    face/facemarkLBF.hpp \
    face/facerec.hpp \
    face/mace.hpp \
    face/predict_collector.hpp \
    src/face_alignmentimpl.hpp \
    src/face_utils.hpp \
    src/precomp.hpp \
    face.hpp \
    admiwin.h \
    train.h \
    recordwin.h

FORMS += \
        faceattendance.ui \
    admiwin.ui \
    recordwin.ui

INCLUDEPATH +=F:\Qt\opencv64\include
INCLUDEPATH +=F:\Qt\opencv64\include\opencv
INCLUDEPATH +=F:\Qt\opencv64\include\opencv2

LIBS += F:\Qt\opencv64\x64\mingw\lib\libopencv*

代码实现

(1)检测人脸

        找出一张图片的人脸部分并矩形框出

         【QT】基于人脸识别的打卡系统(QT+Opencv + SQLite)--实现过程_第5张图片

#include 
using namespace cv;
装载人脸识别分类器  cascada.load("F:\\814_FaceAttendance\\tools\\haarcascade_frontalface_alt2.xml")
void AdmiWin::showImage()
{
  Mat temp;//临时保存RGB图像
  cap>>frame;//摄像头读取头像
  cvtColor(frame, frame_gray, COLOR_BGR2GRAY);//转灰度化,减少运算
  cascada.detectMultiScale(frame_gray, faces, 1.1, 4,
                           CV_HAAR_DO_ROUGH_SEARCH,
                           Size(70, 70),
                           Size(1000, 1000));
  ui->label_count->setText("face=" + QString::number(faces.size()));
  //qDebug()<<"检测到人脸个数:" + QString::number(faces.size());
  //识别到的脸用矩形圈出
  for (int i = 0; i < faces.size(); i++)
  {
      rectangle(frame, faces[i], Scalar(255, 0, 0), 2, 8, 0);
  }

  cvtColor(frame, temp, CV_BGR2RGB);//BGR convert to RGB
  QImage Qtemp = QImage((const unsigned char*)(temp.data),
                        temp.cols, temp.rows, temp.step,
                        QImage::Format_RGB888);
  ui->label_face->setScaledContents(true);
  ui->label_face->setPixmap(QPixmap::fromImage(Qtemp));
  ui->label_face->show();
}

(2)采集头像

采集的头像路径文件夹名称为工号,方便匹配,图片以数字命名

【QT】基于人脸识别的打卡系统(QT+Opencv + SQLite)--实现过程_第6张图片

/**
 * @brief 采集头像
 */
void AdmiWin::on_pushButton_cap_clicked()
{
    if(!ui->lineEdit_num->text().isEmpty())
    {
        //当只有一个人脸时,开始拍照
        if (faces.size() == 1)
        {
            pic_num ++;
            ui->label_tip->setText("已经采集头像第" + QString::number(pic_num) + "张");
            Mat faceROI = frame_gray(faces[0]);//在灰度图中将圈出的脸所在区域裁剪出
            ::resize(faceROI, myFace, Size(92, 112));//将兴趣域size为92*112
            //在 faces[0].tl()的左上角上面写序号
            putText(frame, to_string(pic_num), faces[0].tl(), 3, 1.2, (0, 0, 225), 2, 0);


            Mat temp;//临时保存RGB图像
            cvtColor(frame, temp, CV_BGR2RGB);//BGR convert to RGB
            QImage Qtemp = QImage((const unsigned char*)(temp.data),
                                  temp.cols, temp.rows, temp.step,
                                  QImage::Format_RGB888);
            ui->label_face_2->setScaledContents(true);
            ui->label_face_2->setPixmap(QPixmap::fromImage(Qtemp));
            ui->label_face_2->show();

            //图片的存放位置
            QString dir_str = "F:\\Qt\\workplaces\\814_FaceAttendance\\att_faces\\"
                    + ui->lineEdit_num->text() + "\\" + QString::number(pic_num) + ".jpg";


            //string filename = format("%s%d.jpg",pic_num);
            string filename = dir_str.toStdString();
            imwrite(filename, myFace);//存在当前目录下
        }
    }
    else {
        QMessageBox::about(NULL, "提示", "请输入工号!");
    }

}

(3)训练模型

    模型训练部分封装成线程(耗时太久,开启线程运行),训练完成后生成一下三个文件

     【QT】基于人脸识别的打卡系统(QT+Opencv + SQLite)--实现过程_第7张图片

    头文件

#ifndef TRAIN_H
#define TRAIN_H

#include "face.hpp"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
using namespace cv;

#define SEPARATOR ';'

class train:public QThread
{
    Q_OBJECT
signals:
    void finished();
public:
    explicit train(QObject *parent = nullptr);
    ~train();
    void run();
    void read_csv(const string &, vector &, vector &, char);
};

#endif // TRAIN_H
#include "train.h"


train::train(QObject *parent):QThread(parent)
{

}

train::~train()
{

}

void train::run()
{
     qDebug()<<"启动训练线程";

    //读取你的CSV文件路径.
    string fn_csv = "F:\\Qt\\workplaces\\814_FaceAttendance\\at.txt";

    // 2个容器来存放图像数据和对应的标签
    vector images;
    vector labels;
    try
    {
        read_csv(fn_csv, images, labels,SEPARATOR); //从csv文件中批量读取训练数据
    }
    catch (cv::Exception& e)
    {
        cerr << "Error opening file \"" << fn_csv << "\". Reason: " << e.msg << endl;
        exit(1);
    }
    // 如果没有读取到足够图片,也退出.
    if (images.size() <= 1) {
        string error_message = "至少需要2张图片才能运行,请添加更多的图片到您的数据集";
        CV_Error(CV_StsError, error_message);
    }

    for (int i = 0; i < images.size(); i++)
    {
        //cout< model = face::EigenFaceRecognizer::create();
    model->train(images, labels);
    model->save("MyFacePCAModel.xml");//保存路径可自己设置,但注意用“\\”

    Ptr model1 = face::FisherFaceRecognizer::create();
    model1->train(images, labels);
    model1->save("MyFaceFisherModel.xml");

    Ptr model2 = face::LBPHFaceRecognizer::create();
    model2->train(images, labels);
    model2->save("MyFaceLBPHModel.xml");

    // 下面对测试图像进行预测,predictedLabel是预测标签结果
    //注意predict()入口参数必须为单通道灰度图像,如果图像类型不符,需要先进行转换
    //predict()函数返回一个整形变量作为识别标签
    int predictedLabel = model->predict(testSample);//加载分类器
    int predictedLabel1 = model1->predict(testSample);
    int predictedLabel2 = model2->predict(testSample);
    string result_message = format("Predicted class = %d / Actual class = %d.", predictedLabel, testLabel);
    string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel);
    string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel);
    cout << result_message << endl;
    cout << result_message1 << endl;
    cout << result_message2 << endl;
    qDebug()<<"训练完成";
    emit finished();
}

/**
 * @brief 使用CSV文件去读图像和标签
 * @param filename
 * @param images
 * @param labels
 * @param separator
 */
void train::read_csv(const string& filename, vector& images,
                     vector& labels, char separator = ';')
{
    std::ifstream file(filename.c_str(), ifstream::in);//c_str()函数可用可不用,无需返回一个标准C类型的字符串
    if (!file)
    {
        string error_message = "No valid input file was given, please check the given filename.";
        CV_Error(CV_StsBadArg, error_message);
    }
    string line, path, classlabel;
    while (getline(file, line)) //从文本文件中读取一行字符,未指定限定符默认限定符为“/n”
    {
        stringstream liness(line);//这里采用stringstream主要作用是做字符串的分割
        getline(liness, path, separator);//读入图片文件路径以分好作为限定符
        getline(liness, classlabel);//读入图片标签,默认限定符
        if (!path.empty() && !classlabel.empty()) //如果读取成功,则将图片和对应标签压入对应容器中
        {
            images.push_back(imread(path, 0));
            labels.push_back(atoi(classlabel.c_str()));
        }
    }
}

(4)人脸识别

        #include
        #include
        #include "face.hpp"
        using namespace cv;

        装载人脸识别分类器

        cascade.load("F:\\Qt\\workplaces\\814_FaceAttendance\\tools\\haarcascade_frontalface_alt2.xml");
         model = face::FisherFaceRecognizer::create();
         加载训练好的模型
         model->read("MyFaceFisherModel.xml");

/**
 * @brief 识别图片
 * @param src_image
 * @return
 */
int FaceAttendance::Predict(Mat src_image)
{
    Mat face_test;
    int predict = 0;
    //截取的ROI人脸尺寸调整
    if (src_image.rows >= 120)
    {
        //改变图像大小,使用双线性差值
        ::resize(src_image, face_test, Size(92, 112));

    }
    //判断是否正确检测ROI
    if (!face_test.empty())
    {
        //测试图像应该是灰度图
        predict = model->predict(face_test);
    }
    cout << predict << endl;
    return predict;
}
void FaceAttendance::showImage()
{
   QDateTime local(QDateTime::currentDateTime());
   QString localTime = local.toString("yyyy-MM-dd hh:mm:ss");
   ui->label_time1->setText(local.toString("hh:mm:ss"));
   ui->label_date->setText(local.toString("yyyy年MM月dd日"));

  cap>>frame;//摄像头读取头像
  vector faces(0);//建立用于存放人脸的向量容器
  cvtColor(frame, gray, CV_RGB2GRAY);//测试图像必须为灰度图
  cascade.detectMultiScale(gray, faces, 1.1, 4,
                           CV_HAAR_DO_ROUGH_SEARCH,
                           Size(30, 30),
                           Size(500, 500));
  Mat* pImage_roi = new Mat[faces.size()];    //数组存所有的脸
  Mat face;
  Point text_lb;//文本写在的位置
  //框出人脸
  string str="NONE";
  for (int i = 0; i < faces.size(); i++)
  {
      pImage_roi[i] = gray(faces[i]); //将所有的脸部保存起来
      text_lb = Point(faces[i].x, faces[i].y);
      if (pImage_roi[i].empty())
          continue;
      qDebug()<<"预测结果:"<label_id->setText(result);
      QString sql = QString ("select name,part from staff_info where id='%1'").arg(result);
      QSqlQuery query(sql);
      if(query.next())
      {
          str = result.toStdString();
          ui->label_name->setText(query.value(0).toString());
          ui->label_part->setText(query.value(1).toString());
      }
      Scalar color = Scalar(g_rng.uniform(0, 255), g_rng.uniform(0, 255), g_rng.uniform(0, 255));//所取的颜色任意值
      rectangle(frame, Point(faces[i].x, faces[i].y), Point(faces[i].x + faces[i].width, faces[i].y + faces[i].height), color, 1, 8);//放入缓存
      putText(frame, str, text_lb, FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255));//添加文字
  }

  delete[]pImage_roi;

  Mat temp;
  cvtColor(frame, temp, CV_BGR2RGB);//BGR convert to RGB
  QImage Qtemp = QImage((const unsigned char*)(temp.data),
                        temp.cols, temp.rows, temp.step,
                        QImage::Format_RGB888);
  ui->label_face->setScaledContents(true);
  ui->label_face->setPixmap(QPixmap::fromImage(Qtemp));
  ui->label_face->show();
}

 

 

 

 

 

你可能感兴趣的:(QT,QT学习之路)