前言
本人从事机械设计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
测试结果
综上,将导入的图像进行裁剪和缩放,仅保存人脸部分,用于训练模型;然后加载训练好的模型,进行人脸识别,最后将识别的信息予以显示。
代码经过修改,可以用于 门禁系统 或者 人脸打卡 。