本人从事机械设计12年,业余时间自学编程。
2022年4月6日,开始学习C#,
2022年9月7日,开始学习c++和Qt,
2022年10月28日,开始学习OpenCV,
今天终于搞定了传说中的 人脸识别 ,在此,做个记录。
人脸检测,是基于Haar特征的cascade分类器,
人脸识别,是基于LDA理论的Fisherface算法。
话不多说,上视频!(CSDN上传的视频,太清晰!)
人脸识别测试程序
- 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
- #ifndef SM_H
- #define SM_H
-
- #include
- #include "opencv2/core.hpp"
-
-
-
- class sm
- {
- public:
- sm();
-
- //读取文件
- static void read_csv(const std::string& filename, std::vector
& images, std::vector<int>& labels, char separator) ; -
- //图像预处理:检测人脸、裁剪、缩放、保存、生成列表
- static void pretreatment(std::vector
images, std::vector<int> labels,std::string path,int width,int height) ; -
- };
-
- #endif // SM_H
- #ifndef WIDGET_H
- #define WIDGET_H
-
- #include
-
- 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
- #include "widget.h"
-
- #include
-
- int main(int argc, char *argv[])
- {
- QApplication a(argc, argv);
- Widget w;
- w.show();
- return a.exec();
- }
- #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()));
- }
- }
- }
- }
- #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);
- }
- }


综上,将导入的图像进行裁剪和缩放,仅保存人脸部分,用于训练模型;然后加载训练好的模型,进行人脸识别,最后将识别的信息予以显示。
代码经过修改,可以用于 门禁系统 或者 人脸打卡 。