本文记录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生成每张图片路径名,如下图。
应用时,路径里包含了类别信息,即形成了路径与类别标签的联系。
具体代码如下:
#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放在移动硬盘上,所以跑代码时,忘插硬盘会出错。