利用Opencv实现人脸识别
转发请注明本文链接
http://www.cnblogs.com/alala713/p/face.html
这学期公选课选了一门课,期末要做一个实验报告,于是我就做了这个利用Opencv实现的人脸识别。
采用的算法是传统的人脸识别算法,LBPH。
环境与工具:
MacOS, Xcode, Opencv3(包含opencv_contrib模块)。
目标:
将一个需要识别的人脸和人脸库中的某个人脸对应起来,目的是完成识别功能。
原理:
使用LBPH算法实现人脸识别。
简要概括原理及概念:
LBP算子定义:
原始的LBP算子定义为在3*3的窗口内,以窗口中心像素为阈值,将相邻的8个像素的灰度值与其进行比较,若周围像素值大于或等于中心像素值,则该像素点的位置被标记为1,否则为0。这样,3*3邻域内的8个点经比较可产生8位二进制数(通常转换为十进制数即LBP码,共256种),即得到该窗口中心像素点的LBP值,并用这个值来反映该区域的纹理特征。如下图所示:
圆形LBP算子
基本的 LBP算子的最大缺陷在于它只覆盖了一个固定半径范围内的小区域,这显然不能满足不同尺寸和频率纹理的需要。为了适应不同尺度的纹理特征,Ojala等对LBP算子进行了改进,将3×3邻域扩展到任意邻域,并用圆形邻域代替了正方形邻域,改进后的LBP算子允许在半径为R的圆形邻域内有任意多个像素点,从而得到了诸如半径为R的圆形区域内含有P个采样点的LBP算子,OpenCV中正是使用圆形LBP算子,下图示意了圆形LBP算子:
利用LBP特征实现人脸识别
对一幅图像(记录的是每个像素点的灰度值)提取其原始的LBP算子之后,得到的原始LBP特征依然是“一幅图片”(记录的是每个像素点的LBP值),如图所示:
采用LBP特征谱的统计直方图作为特征向量进行分类识别,并且可以将一幅图片划分为若干的子区域,对每个子区域内的每个像素点都提取LBP特征,然后,在每个子区域内建立LBP特征的统计直方图。如此一来,每个子区域,就可以用一个统计直方图来进行描述,整个图片就由若干个统计直方图组成,这样做的好处是在一定范围内减小图像没完全对准而产生的误差,分区的另外一个意义在于我们可以根据不同的子区域给予不同的权重,比如说我们认为中心部分分区的权重大于边缘部分分区的权重,意思就是说中心部分在进行图片匹配识别时的意义更为重大。
OpenCV在LBP人脸识别中使用的是如下相似度公式:
使用人脸库:
AT&T Cambridge Faces Databases + 白岩松(10张图片) + 汪峰(10张图片)
新加入的图片:
白岩松:
汪峰:
测试图片集:
人脸库的导入:
使用一个文档at.txt导入图片文件与图片想对应的标签。
at.txt文档由代码自动生成:
主要代码内容:
使用两个vector容器存储图片与对应标签:
使用opencv3 contrib模块中的FaceRecognizer模块进行对图片库的训练
输入测试图片,输出预测结果。检查结果是否相符:
完整代码:
// // faceRecognize.hpp // 公选课人脸识别 // // Created by Alala on 2017/5/12. // Copyright © 2017年 SYSU. All rights reserved. // #ifndef faceRecognize_h #define faceRecognize_h #include <opencv2/opencv.hpp> #include <opencv2/face.hpp> #include <iostream> #include <fstream> #include <sstream> #include <math.h> #include "opencv2/opencv_modules.hpp" #include "opencv2/imgcodecs.hpp" #include "opencv2/highgui.hpp" #include "opencv2/imgproc.hpp" using namespace cv; using namespace face; using namespace std; class faceRecognize { public: faceRecognize(const Mat input, const string path); void read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator = ';'); Mat norm_0_255(Mat _src); void recognize(); private: Mat source; string csv_path; }; faceRecognize::faceRecognize(const Mat input, const string path) { source = input.clone(); csv_path = path; } void faceRecognize::read_csv(const string& filename, vector<Mat>& images, vector<int>& labels, char separator) { std::ifstream file(filename.c_str(), ifstream::in); 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)) { stringstream liness(line); 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())); } } } Mat faceRecognize::norm_0_255(Mat _src) { Mat src = _src.clone(); Mat dst; switch (src.channels()) { case1: cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1); break; case3: cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3); break; default: src.copyTo(dst); break; } return dst; } void faceRecognize::recognize() { vector<Mat> images; vector<int> labels; try { read_csv(csv_path, images, labels); } catch (cv::Exception& e) { cerr << "Error opening file \"" << csv_path << "\". Reason: " << e.msg << endl; exit(1); } if (images.size() <= 1) { string error_message = "This demo needs at least 2 images to work. Please add more images to your data set!"; CV_Error(CV_StsError, error_message); } Mat testSample = source; source = norm_0_255(source); images.pop_back(); labels.pop_back(); Ptr<FaceRecognizer> model = createLBPHFaceRecognizer(); model->train(images, labels); model->save("MyFaceLBPHModel.xml"); // predictedLabel是预测标签结果 int predictedLabel = model->predict(testSample); string result_message = format("Predicted class = %d", predictedLabel); cout << result_message << endl; imshow("predict", images[predictedLabel*9 - 10 + predictedLabel]); imshow("actual", source); waitKey(0); } #endif /* faceRecognize_h */
测试结果展示:
7张测试图片中6张正确,对结果比较满意。
参考链接:
http://www.cnblogs.com/zhaoweiwei/p/LBPH.html