交流群号:245022761(IT项目交流群)
目录
工具准备
工程结构解析
配置文件中加入opencv相关文件
代码实现
(1)检测人脸
(2)采集头像
(3)训练模型
(4)人脸识别
1、安装Opencv,并添加到环境变量中 F:\Qt\opencv64\x64\mingw\bin
2、将Opencv下的人脸检测分类器(haarcascade_frontalface_alt2.xml)拷贝到自己的工程下
3、opencv3的人脸识别库拷贝到自己的工程下(src整个文件夹、face整个文件夹、face.hpp)
下载地址:https://github.com/opencv/opencv_contrib
一图看懂工程结构
# 添加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*
找出一张图片的人脸部分并矩形框出
#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();
}
采集的头像路径文件夹名称为工号,方便匹配,图片以数字命名
/**
* @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, "提示", "请输入工号!");
}
}
模型训练部分封装成线程(耗时太久,开启线程运行),训练完成后生成一下三个文件
头文件
#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()));
}
}
}
#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();
}