一、简介
主要使用OpenCV自带的经典人脸特征进行识别:
-
Fisherface(LDA)特征
Eigenface(PCA)特征
这两个特征预处理时,图像大小必须一致 -
LBPHface(LBPH)特征
该特征优点是不会受到光照、缩放、旋转和平移的影响。
三个特征使用的创建方法:
-
static Ptr<FisherFaceRecognizer> create (int num_components = 0, double threshold = DBL_MAX)
static Ptr<EigenFaceRecognizer> create (int num_components = 0, double threshold = DBL_MAX) -
static Ptr<LBPHFaceRecognizer> create(int radius=1, int neighbors=8, int grid_x=8, int grid_y=8, double threshold = DBL_MAX)
二、测试
头文件 FaceRecognize.h
:
#pragma once
#include <opencv2/opencv.hpp>
#include <opencv2/face.hpp>
#include <iostream>
#include <fstream>
using namespace cv;
using namespace std;
class FaceRecognize {
public:
void face_recog_demo1(const char* csv_path);
void face_recog_demo2(const char* csv_path, const char* model_path);
};
主函数main.cpp
#include "FaceRecognize.h"
int main(int argc, char**argv) {
const char* csv_path = "D:/opencv-c++/orl_faces/images_info.txt";
const char* model_path = "D:/opencv-c++/orl_faces/images_info.xml";
FaceRecognize face_recognize;
//face_recognize.face_recog_demo1(csv_path);
face_recognize.face_recog_demo2(csv_path, model_path);
waitKey(0);
destroyAllWindows();
return 0;
}
其中images_info.txt格式如下:
D:/opencv-c++/orl_faces/s1/1.pgm 1
(文件路径\t标签)
FisherFace 和 EigenFace 特征
void load_image_sample(const char* csv_path, vector<Mat>& images, vector<int>& labels) {
ifstream file(csv_path, ifstream::in);
if (!file) {
cout << "could not read files." << endl;
exit(-1);
}
string line, imgPath, classLabel;
while (getline(file, line)) {
stringstream lines(line);
getline(lines, imgPath, '\t');
getline(lines, classLabel);
if (!imgPath.empty() && !classLabel.empty()) {
images.push_back(imread(imgPath, 0));
labels.push_back(atoi(classLabel.c_str()));
}
}
}
void FaceRecognize::face_recog_demo1(const char* csv_path) {
vector<Mat> images;
vector<int> labels;
images.reserve(400); //准备了400个样本
labels.reserve(400);
load_image_sample(csv_path, images, labels);
if (images.size() < 1 || labels.size() < 1) {
printf("invalid image path...\n");
exit(-1);
}
int height = images[0].rows;
int width = images[0].cols;
printf("查看第一个样本信息:height : %d, width : %d\n", height, width);
// 弹出最后最后一个样本作为测试数据
Mat testImage = images[images.size() - 1];
int testLable = labels[labels.size() - 1];
images.pop_back();
labels.pop_back();
//训练
//Ptr<cv::face::BasicFaceRecognizer> model = cv::face::FisherFaceRecognizer::create();
Ptr<cv::face::BasicFaceRecognizer> model = cv::face::EigenFaceRecognizer::create();
model->train(images, labels);
//预测
int predictLabel = model->predict(testImage);
printf("actual label : %d, predict label : %d\n", testLable, predictLabel);
// 可视化均值脸
Mat eigenValues = model->getEigenValues();
Mat eigenVectors = model->getEigenVectors();
Mat meanValue = model->getMean();
cout << "meanValue = " << meanValue.size() << endl;
Mat meanFace = meanValue.reshape(1, height);
Mat dst;
if (meanFace.channels() == 1) {
normalize(meanFace, dst, 0, 255, NORM_MINMAX, CV_8UC1);
}
else if (meanFace.channels() == 3) {
normalize(meanFace, dst, 0, 255, NORM_MINMAX, CV_8UC3);
}
imshow("Mean Face", dst);
// 可视化特征脸
for (int i = 0; i < min(16, eigenVectors.cols); i++) {
Mat ev = eigenVectors.col(i).clone();
Mat grayscale;
Mat eigenFace = ev.reshape(1, height);
if (eigenFace.channels() == 1) {
normalize(eigenFace, grayscale, 0, 255, NORM_MINMAX, CV_8UC1);
}
else if (eigenFace.channels() == 3) {
normalize(eigenFace, grayscale, 0, 255, NORM_MINMAX, CV_8UC3);
}
Mat colorface;
applyColorMap(grayscale, colorface, COLORMAP_BONE);
string winTitle = "eigenface_" + to_string(i);
imshow(winTitle, colorface);
}
// PCA特征重建查看:https://wangsp.blog.csdn.net/article/details/120361267
// 重建人脸
for (int num = 0; num < min(eigenVectors.cols, 16); num++) {
Mat evs = eigenVectors.col(num);
Mat projection = LDA::subspaceProject(evs, meanValue, images[0].reshape(1, 1));
Mat reconstruction = LDA::subspaceReconstruct(evs, meanValue, projection);
Mat result = reconstruction.reshape(1, height);
if (result.channels() == 1) {
normalize(result, reconstruction, 0, 255, NORM_MINMAX, CV_8UC1);
}
else if (result.channels() == 3) {
normalize(result, reconstruction, 0, 255, NORM_MINMAX, CV_8UC3);
}
string winTitle = "recon_face_" + to_string(num);
imshow(winTitle, reconstruction);
}
}
LBPHFace 特征
void lbphface_train(const char* model_path, vector<Mat>& mats, vector<int>& labels) {
if (mats.empty() || labels.empty() || mats.size() != labels.size()) {
printf("传入参数错误,请检查!");
exit(-1);
}
// 创建模型 -> 训练模型:(矩阵向量,标签向量) -> 保存模型:(文件名)
Ptr<cv::face::LBPHFaceRecognizer> model = cv::face::LBPHFaceRecognizer::create();
model->train(mats, labels);
model->save(model_path);
printf("训练模型完成!");
}
void lbphface_update(const char* model_path, vector<Mat>& mats, vector<int>& labels) {
if (mats.empty() || labels.empty() || mats.size() != labels.size()) {
printf("传入参数错误,请检查!");
exit(-1);
}
// 加载模型 -> 更新训练模型 -> 保存模型
Ptr<cv::face::LBPHFaceRecognizer> model = Algorithm::load<cv::face::LBPHFaceRecognizer>(model_path);
model->update(mats, labels);
model->save(model_path);
printf("更新模型完成!");
}
void lbphface_predict(const char* model_path, vector<Mat>& testImages, vector<int>& pred_label) {
Ptr<cv::face::LBPHFaceRecognizer> model = Algorithm::load<cv::face::LBPHFaceRecognizer>(model_path);
if (model->empty()) {
cout << "face model load faied!" << endl;
exit(-1);
}
if (testImages.empty()) {
printf("传入预测矩阵错误!");
exit(-1);
}
int label = -1; //标签变量
double confidence = 0; //置信值
for (int i = 0; i < testImages.size(); i++) {
//预测分类结果,参数:(图矩阵,标签变量,置信概率变量)
model->predict(testImages[i], label, confidence);
cout << "confi_value = " << confidence << endl;
cout << "predi_label = " << label << endl;
if (confidence > 80) {
label = -1; //标签给-1(表示未知)
}
pred_label.push_back(label);
}
printf("人脸预测完成!");
int radius = model->getRadius();
int neibs = model->getNeighbors();
int grad_x = model->getGridX();
int grad_y = model->getGridY();
double t = model->getThreshold();
printf("radius : %d\n", radius);
printf("neibs : %d\n", neibs);
printf("grad_x : %d\n", grad_x);
printf("grad_y : %d\n", grad_y);
printf("threshold : %.2f\n", t);
}
void FaceRecognize::face_recog_demo2(const char* csv_path, const char* model_path) {
vector<Mat> images;
vector<int> labels;
images.reserve(401);
labels.reserve(401);
load_image_sample(csv_path, images, labels);
if (images.size() < 1 || labels.size() < 1) {
printf("invalid image path...\n");
exit(-1);
}
int height = images[0].rows;
int width = images[0].cols;
printf("查看第一个样本信息:height : %d, width : %d\n", height, width);
// 弹出最后最后一个样本作为测试数据
Mat testImage = images[images.size() - 1];
int testLable = labels[labels.size() - 1];
images.pop_back();
labels.pop_back();
lbphface_train(model_path, images, labels);
vector<int> pred_label;
vector<Mat> testImages;
testImages.push_back(testImage);
lbphface_predict(model_path, testImages, pred_label);
cout << "pred_label[0] = " << pred_label[0] << endl;
}