利用opencv实现人脸识别
introduction
- 使用弱联级分类器主要包含两部分:训练和检测,检测通常使用HAAR或LBP模型,这部分的描述在另外一个文件中,这部分主要介绍弱联级加速分类器的训练,主要包括:收集训练数据,准备或者处理训练数据,以及进行训练;
- 使用的工具 opencv_traincascade tool.这个是C++利用opencv2.x和opencv3.x写的,这个训练工具支持HAAP/LBP特征,对于训练的效果,这个主要取决于所用的数据和参数的选择
- 实现的工具:VS2015+opencv(plus contrib);
question
- 这篇文章是跟一位大神学得大神博客地址和opencv官网,按照步骤写的,我自己也实践了;所以就按照自己的理解再写一点,将自己填的坑记录一下;
- 做人脸识别需要清楚一些opencv的一些基础的概念以及图像处理的一些基础知识,比如一些很简单的区别人脸识别和人脸检测等;
- opencv官方版没有将一些算法开放,还有一些在contrib中,所以我们想用的一些opencv 的高级一点的算法均在contrib中,这个就需要自己去编译了,具体的编译网上有很多的个版本,但一定要找到合适自己电脑的那一个,我将自己的编译的坑也总结出来了,用得到的可以去看看;建议你刚开始用就自己编译的加了contrib的opencv,否则训练模型的时候就会各种问题,各种API不支持的奇葩问题出现;
- 生成标签的部分用到了python,由于以前没有接触过,语法也很不熟悉,需要熟悉这部分来实现标签的区分;
practice
数据的收集和预处理
- 数据的收集,使用到的数据库是opencv官方给出的 The AT&T Facedatabase ,又称之为ORL数据库,但你下载下来之后发现是由10个文件夹包含的400张pgm的照片,但是在window 上你还打不开看不了这个到底张什么样,只能通过opencv自己写个小程序imread()来看看了..
- 收集到别人的数据库也不可啊,你得需求是让计算机得认得我啊,所以还需要你自己的照片,拿这些照片和你的照片一起来训练模型,既然学了opencv那么就自己写个小程序来实现,在我的前两篇文章中写了具体的实现代码,具体的逻辑就是打开电脑的摄像头,当按下空格键的时候保存当前帧并显示出来,感兴趣的可以关注我的,此片博文的链接opencv实现简单的拍照程序及照片的裁切;
- 这样收集到自己的图片了,发现自己的图片太大,所以需要处理一下,利用opencv自己的模型检测并分割出人脸,这个地方需要注意的是下载的图片是92X112,那么你在检测之后对得到的ROI做一次reSize()即可;这样就可以得到想要的大小的图片了;
- 将处理之后的图片放置在第41文件夹中;这个时候就需要处理at.txt了,这个相当于一个标签,标注每个图片代表的是谁,那几张图片表示的是同一个人;需要处理这个;
- 对于数据的收集和预处理的总结:
- 这部分没有什么代码,但是要深刻理解这里面的一些词的含义,一些细节性的东西,前期的准备一定要做足,理论知识,工具,数据资料等;我的时间主要浪费在自己编译opencv+contrib上面了,下载了多个不同版本的vs,安装且试用了,也利用不同版本的编译了,下载时间+安装时间+卸载时间的耗费超大,所以给大家建议,一定要看清楚vs版本自己电脑的配置以及opencv的版本这几个的搭配再搞;
- 这部分的理论知识:
- 要做人脸识别,首先要做的就是收集数据训练模型,这个模型就是含有你特征的模型,在识别的时候加载模型看相似度,在一定值范围之内则判定为同一个人;
- 然后理解cv的每一个API的利用场景,比如:若是要训练模型接受的图像必须是灰度图,且为了减少光照干扰灰度图必须实现归一化,若是再做的好点可以在把图片resize一下;
训练模型
CSV文件的生成,记录每张图片的位置和是谁,这个也就相当于一个标签,这个csv文件的生成比较麻烦,使用python脚本自动生成;代码在下面贴出来;
-
训练模型;csv文件和图片已经准备好了,接下来就是训练模型,这个要用到opencv里面的Facerecognizer类,opencv里面的所有的人脸识别模型都来自于这个类,这个类为所有的人脸识别算法提供了通用的借口,这个类包含了接下来的几个函数:
Moreover every FaceRecognizer supports the: * Training of a FaceRecognizer with FaceRecognizer::train() on a given set of images (your face database!). * Prediction of a given sample image, that means a face. The image is given as a Mat. * Loading/Saving the model state from/to a given XML or YAML. * Setting/Getting labels info, that is storaged as a string. String labels info is useful for keeping names of the recognized people
-
使用facerecoginzer类来训练模型
Ptr
model = createEigenFaceRecognizer(); model->train(images, labels); model->save("MyFacePCAModel.xml"); Ptr model1 = createFisherFaceRecognizer(); model1->train(images, labels); model1->save("MyFaceFisherModel.xml"); Ptr model2 = createLBPHFaceRecognizer(); model2->train(images, labels); model2->save("MyFaceLBPHModel.xml"); -
要训练模型就需要image和lable了,也就是把刚刚用python生成的at.txt读出来了,读取这个文件在cv中使用stringstream和getline;
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())); } }
-
训练完了还有比较重要的一部prediction
int predictedLabel = model->predict(testSample); int predictedLabel1 = model1->predict(testSample); int predictedLabel2 = model2->predict(testSample); // 还有一种调用方式,可以获取结果同时得到阈值: // int predictedLabel = -1; // double confidence = 0.0; // model->predict(testSample, predictedLabel, confidence); 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;
-
模型训练的总结
- 主要分为准备数据做csv文件,读取文件,训练模型,做预测,这个是主要的步骤,但里面需要注意的点很多;我在上面也分别做了说明
- 下面的代码分为两部分,一部分是训练,另一部分则是一个生成csv文件的python脚本;
源代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace cv;
using namespace cv::face;
using namespace std;
static Mat norm_0_255(InputArray _src) {
Mat src = _src.getMat();
// 按照不同通道创建和返回一个归一化后的图像矩阵:
Mat dst;
switch (src.channels()) {
case 1:
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC1);
break;
case 3:
cv::normalize(_src, dst, 0, 255, NORM_MINMAX, CV_8UC3);
break;
default:
src.copyTo(dst);
break;
}
return dst;
}
//使用CSV文件去读图像和标签,主要使用stringstream和getline方法,这里面涉及到一些常见的C++语法
static void read_csv(const string& filename, vector& images, vector& 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()));
}
}
}
int main()
{
//读取你的CSV文件路径.
string fn_csv = "at.txt";
// 2个容器来存放图像数据和对应的标签
vector images;
vector labels;
// 读取数据. 如果文件不合法就会出错
// 输入的文件名已经有了.
try
{
read_csv(fn_csv, images, labels);
}
catch (cv::Exception& e)
{
cerr << "Error opening file \"" << fn_csv << "\". 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);
}
// 下面的几行代码仅仅是从你的数据集中移除最后一张图片
//[gm:自然这里需要根据自己的需要修改,他这里简化了很多问题]
Mat testSample = images[images.size() - 1];
int testLabel = labels[labels.size() - 1];
images.pop_back();
labels.pop_back();
// 下面几行创建了一个特征脸模型用于人脸识别,
// 通过CSV文件读取的图像和标签训练它。
//如果你只想保留10个主成分,使用如下代码
// cv::createEigenFaceRecognizer(10);
//
// 如果你还希望使用置信度阈值来初始化,使用以下语句:
// cv::createEigenFaceRecognizer(10, 123.0);
//
// 如果你使用所有特征并且使用一个阈值,使用以下语句:
// cv::createEigenFaceRecognizer(0, 123.0);
Ptr model = createEigenFaceRecognizer();
model->train(images, labels);
model->save("MyFacePCAModel.xml");
Ptr model1 = createFisherFaceRecognizer();
model1->train(images, labels);
model1->save("MyFaceFisherModel.xml");
Ptr model2 = createLBPHFaceRecognizer();
model2->train(images, labels);
model2->save("MyFaceLBPHModel.xml");
// 下面对测试图像进行预测,predictedLabel是预测标签结果
int predictedLabel = model->predict(testSample);
int predictedLabel1 = model1->predict(testSample);
int predictedLabel2 = model2->predict(testSample);
// 还有一种调用方式,可以获取结果同时得到阈值:
// int predictedLabel = -1;
// double confidence = 0.0;
// model->predict(testSample, predictedLabel, confidence);
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;
getchar();
waitKey(0);
return 0;
}
人脸识别
实现理论:这个部分就是做检测了,打开摄像头,加载人脸检测器,检测出人脸,然后拿这个检测出来的人脸和模型里面的对比,看看是谁的;原理很简单但实现出来的结果差强人意;
-
问题点:
- 的确cv实现的比较慢而且卡顿现象比较明显,python的确是机器学习的利器,而且上手容易
- 使用cv最好使用自己编译的,官方的这个版本很多的比较好的算法都没有,自己编译的时候有很多的坑,我自己整理了一些opencv+contrib+vs编译的一些问题;
- cv用起来不难,但是理解起来比较难,里面涉及到的算法有点多;很多都很不好理解;
-
代码实现
#include
#include #include using namespace std; using namespace cv; using namespace cv::face; int main() { VideoCapture cap(0); if (!cap.isOpened()) { return -1; } Mat frame; Mat edges; Mat gray; CascadeClassifier cascade; bool stop = false; //训练好的文件名称,放置在可执行文件同目录下 cascade.load("lbpcascade_frontalface.xml"); Ptr modelPCA = createEigenFaceRecognizer(); modelPCA->load("MyFacePCAModel.xml"); while (1) { cap >> frame; //建立用于存放人脸的向量容器 vector faces(0); cvtColor(frame, gray, CV_BGR2GRAY); //改变图像大小,使用双线性差值 //Mat smallImg(cvRound(frame.rows / 1.3), cvRound(frame.cols / 1.3), CV_8UC1); //resize(gray, smallImg, smallImg.size(), 0, 0, INTER_LINEAR); //变换后的图像进行直方图均值化处理 equalizeHist(gray, gray); cascade.detectMultiScale(gray, faces, 1.1, 2, 0 //|CV_HAAR_FIND_BIGGEST_OBJECT //|CV_HAAR_DO_ROUGH_SEARCH | CV_HAAR_SCALE_IMAGE, Size(30, 30)); Mat face; Point text_lb; for (size_t i = 0; i < faces.size(); i++) { if (faces[i].height > 0 && faces[i].width > 0) { face = gray(faces[i]); text_lb = Point(faces[i].x, faces[i].y); rectangle(frame, faces[i], Scalar(255, 0, 0), 1, 8, 0); } } Mat face_test; int predictPCA = 0; if (face.rows >= 120) { resize(face, face_test, Size(92, 112)); } //Mat face_test_gray; //cvtColor(face_test, face_test_gray, CV_BGR2GRAY); if (!face_test.empty()) { //测试图像应该是灰度图 predictPCA = modelPCA->predict(face_test); } cout << predictPCA << endl; if (predictPCA == 41) { string name = "Lemon"; putText(frame, name, text_lb, FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255)); } else { putText(frame, "unknow", text_lb, FONT_HERSHEY_COMPLEX, 1, Scalar(0, 0, 255)); } imshow("face", frame); waitKey(200); } return 0; }