opencv自学笔记之人脸检测、分类

目录

  • 前言
  • 人脸检测
  • 人脸分类

前言

本文记录opencv人脸检测、分类的简单操作。

人脸检测

参考blog:https://blog.csdn.net/xingchenbingbuyu/article/details/51105159
这里采用opencv训练好的haar分类器进行正面人脸及眼睛部位的检测,也可以采用其他分类器,或者自己训练分类器。Haar特征分类器就是一个XML文件,该文件中会描述人体各个部位的Haar特征值。包括人脸、眼睛、嘴唇等等。
实现时,首先将正面人脸检测haarcascade_frontalface_default.xml和人眼检测haarcascade_eye_tree_eyeglasses.xml文件从*\opencv\sources\data\haarcascades 路径下copy到自己的运行路径下。
然后应用detectMultiScale()函数进行检测,它可以检测出图片中所有的人脸(眼),并将用vector保存各个人脸(眼)的坐标、大小(用矩形表示),函数由分类器对象调用:

void detectMultiScale(  
    const Mat& image,  //待检测图片,一般为灰度图像加快检测速度
    CV_OUT vector<Rect>& objects,  //被检测物体的矩形框向量组
    double scaleFactor = 1.1,  //表示在前后两次相继的扫描中,搜索窗口的比例系数。默认为1.1即每次搜索窗口依次扩大10%
    int minNeighbors = 3,//   表示每一个目标至少要被检测到3次才算是真的目标(因为周围的像素和不同的窗口大小都可以检测到人脸);如果min_neighbors 为 0, 则函数不做任何操作就返回所有的被检候选矩形框,这种设定值一般用在用户自定义对检测结果的组合程序上;
    int flags = 0,  //要么使用默认值,要么使用CV_HAAR_DO_CANNY_PRUNING,如果设置为CV_HAAR_DO_CANNY_PRUNING,那么函数将会使用Canny边缘检测来排除边缘过多或过少的区域,因此这些区域通常不会是人脸所在区域;
    Size minSize = Size(), //目标的最小尺寸
    Size maxSize = Size()  //目标的最大尺寸
);

github代码:https://github.com/chinesedreamer/face_detection
实现结果:
补充:好久没上传代码到github了,以前也不熟悉,参考了 https://zhuanlan.zhihu.com/p/31031838 、https://www.jianshu.com/p/c4267d0b4544 (我是这样操作的:点击vs2017右下角添加到源代码管理,然后点击团队资源管理器,点击发布到github);还想删除以前的一些文件,参考了 https://blog.csdn.net/weixin_42152081/article/details/80635777 。

人脸分类

参考blog:https://blog.csdn.net/qq_37791134/article/details/81385848
首先下载数据集,ORL人脸数据库下载:https://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html , 共40类,各10张图片 92X112像素 ,0-255灰度图。
然后生成labels.txt:cmd生成每张图片路径名,如下图。
opencv自学笔记之人脸检测、分类_第1张图片应用时,路径里包含了类别信息,即形成了路径与类别标签的联系。
具体代码如下:

#include "pch.h"
#include 
#include 
#include 
#include 
#include //用到strcpy_s
using namespace cv::face;
using namespace std;
using namespace cv;
//分割函数   (被分割string,分割string)
vector<string> split(const string& str, const string& delim) {
	vector<string> res;
	if ("" == str) return res;
	//先将要切割的字符串从string类型转换为char*类型  
	char * strs = new char[str.length() + 1]; //+1  '\0' 
	strcpy_s(strs,str.length()+1, str.c_str());//strcpy把含有'\0'结束符的字符串复制到另一个地址空间,返回值的类型为char*(要有\0)

	char * d = new char[delim.length() + 1];
	strcpy_s(d,delim.length()+1, delim.c_str());
	char * buf;
	char *p = strtok_s(strs, d,&buf);//分解字符串为一组字符串。s为要分解的字符,delim为分隔符字符
	//当strtok()在参数s的字符串中发现参数delim中包含的分割字符时,则会将该字符改为\0 字符。在第一次调用时,strtok()必需给予参数s字符串,往后的调用则将参数s设置成NULL。每次调用成功则返回指向被分割出片段的指针。
	//将剩余的字符串存储在buf变量中,而不是静态变量中,从而保证了安全性。
	while (p) {
		string s = p; //分割得到的字符串转换为string类型  
		res.push_back(s); //存入结果数组  
		p = strtok_s(NULL, d,&buf);
	}
	return res;
}
//读取labels.txt,得到图片信息及其label
void read_txt(string& filename, vector<Mat>& pics, vector<int>& labels)
{
	ifstream file(filename, ifstream::in);
	string path;
	vector<string> res;
	while (getline(file, path)) //从文本文件中读取一行字符,未指定限定符默认限定符为“\n”
	{
		if (!path.empty())//如果读取成功,则将图片和对应标签压入对应容器中
		{
			res = split(path, "\\");//          \---->\\      换行--->\n    以\将路径分割,提取label信息     res[res.size() - 2]-->s1等
			//printf("%s", res[res.size()-2].c_str());
			char * label = new char[res[res.size() - 2].length() + 1]; //将标签信息从s1变为1
			strcpy_s(label, res[res.size() - 2].length() + 1, res[res.size() - 2].c_str());
			//printf("%s\n", label);
			//将标签信息从s1变为1
			char * labelnumber= new char[res[res.size() - 2].length()];//去掉s后(一个字符长度)  
			labelnumber = &label[1];//不要第0个字符    labelnumber=第一个字符的地址
			labels.push_back(atoi(labelnumber));//atoi(表示 ascii to integer)是把字符串转换成整型数的一个函数
			//printf("%s\n", labelnumber);
			//printf("%s\n", path.c_str());//%s -->char*
			pics.push_back(imread(path, 0));//读入对应图片
		}
	}
}
int main()
{
	vector<Mat> pics; 
	vector<int> labels;
	string filename = "F:\\datasets\\people_faces\\ORL人脸\\labels.txt";
	try
	{
		read_txt(filename, pics, labels); 
	}
	catch (Exception& e)
	{
		cerr << "Error opening file \"" << filename << "\". Reason: " << e.msg << endl; 
		exit(1);
	}
	// 下面的几行代码从数据集中移除最后一张图片,作为测试图片   
	Mat testSample = pics[pics.size() - 1];
	int testLabel = labels[labels.size() - 1];
	pics.pop_back();//删除最后一张照片,此照片作为测试图片
	labels.pop_back();//删除最有一张照片的labels
	//判断分类器是否已被创建
	fstream _file1,_file2,_file3;
	_file1.open("MyFacePCAModel.xml", ios::in);
	_file2.open("MyFaceFisherModel.xml", ios::in);
	_file3.open("MyFaceLBPHModel.xml", ios::in);
	//创建三种人脸分类器   PCA Fisher LBPHF
	Ptr<BasicFaceRecognizer> model1 = EigenFaceRecognizer::create();
	Ptr<BasicFaceRecognizer> model2 = FisherFaceRecognizer::create();
	Ptr <LBPHFaceRecognizer > model3 = LBPHFaceRecognizer::create();
	if ((!_file1)&&(!_file2)&&(!_file3))
	{
		cout << "model没有被创建\n";
		
		model1->train(pics, labels);//调用其中的成员函数train()来完成分类器的训练
		model1->save("MyFacePCAModel.xml");

		
		model2->train(pics, labels);
		model2->save("MyFaceFisherModel.xml");

		
		model3->train(pics, labels);
		model3->save("MyFaceLBPHModel.xml");
		
	}
	else
	{
		cout << "model已经存在\n";
		model1->read("MyFacePCAModel.xml");
		model2->read("MyFaceFisherModel.xml");
		model3->read("MyFaceLBPHModel.xml");
	}
	
	// 下面对测试图像进行预测,predictedLabel是预测标签结果  
	//注意predict()入口参数必须为单通道灰度图像,如果图像类型不符,需要先进行转换
	//predict()函数返回一个整形变量作为识别标签
	int predictedLabel1 = model1->predict(testSample);//加载分类器
	int predictedLabel2 = model2->predict(testSample);
	int predictedLabel3 = model3->predict(testSample);
	
	string result_message1 = format("Predicted class = %d / Actual class = %d.", predictedLabel1, testLabel);
	string result_message2 = format("Predicted class = %d / Actual class = %d.", predictedLabel2, testLabel);
	string result_message3 = format("Predicted class = %d / Actual class = %d.", predictedLabel3, testLabel);
	cout << result_message1 << endl;
	cout << result_message2 << endl;
	cout << result_message3 << endl;
	

	//printf("%d", labels[399]);
	//printf("%d", pics.size());

	return 0;
}

补充:我的数据集、labels.txt放在移动硬盘上,所以跑代码时,忘插硬盘会出错。

你可能感兴趣的:(opencv,opencv,人脸)