1、前面博文有演示过如何使用OpenCV自带的人脸与眼睛的级联分类器检测到图像中的人脸,这里将演示如何打开连接电脑的摄像头并检测人脸,然后拍照保存下来,用来做人脸识别的训练数据。
2.我的编程环境是Windows 7 64位,IDE是VS2015,配置了OpenCV3.3与OpenCV_Contrib,Boost 1.66,其中Boost是用来操作文件和目录用的,是于如果配置以上的环境,可以看我之前写的博文。
1.下载OpenCV官方的人脸数据,官方人脸数据总共给了40个人的正脸,分好类放在一个文件目录下,每个人的人脸数据是10张,格式是pgm的,pgm这个格式是在PC机是无法用看图软件打开的,但可以使用OpenCV写个小程序查看里面的内容。csdn下载地址是:https://download.csdn.net/download/matt45m/11090734 。下载之后打开是这样的:
每个文件夹下包含10张人脸数据,格式是pgm的,尺寸为92X112。
2.写个函数查看每个分类的人脸数据,这里使用到boost库的文件操作类递归遍历每个子目录下的图像数据并显示。
(1)函数代码
/显示目标路径下的所有图像
void showImage(string image_path)
{
//判断是事为文件夹
if (!fs::is_directory(image_path))
{
Mat image = imread(image_path);
if (!image.empty())
{
imshow(image_path, image);
waitKey(0);
}
}
else if(fs::is_directory(image_path))
{
fs::recursive_directory_iterator begin_iter(image_path);
fs::recursive_directory_iterator end_iter;
for (; begin_iter != end_iter; begin_iter++)
{
string file_name = begin_iter->path().string();
if (!fs::is_directory(file_name))
{
Mat image = imread(file_name);
if (!image.empty())
{
imshow(file_name, image);
waitKey(30);
}
}
}
}
}
(2)打开其中的一组数据,可以看到其中的人脸,有各种视角的正脸,这样我们在录入要识别的人脸尽量可借鉴官方的视角来收集。
收集自己要识别的人脸数据,可以从图像集里面检测出人脸,也可以从USB摄像头或笔记本自带的摄像头检测出人脸,这里给出的代码是从摄像头检测到人脸,然后截取,改成官方给的人脸图像大小一样的数据,保存。
1.使用OpenCV人脸检测的级联分类器检测到人脸,如果检测到当前视频中只有一张人脸时,然后再用眼睛分类器检测这张脸是否能完全检测两个眼睛,如果能同时检测到两个眼睛,按p键拍照并保存。保存10张以上的人脸,退出。
void photograph(string save_path,int _cap)
{
int image_number = 0;
if (!face_detector.load(face_path))
{
std::cout << "无法打开人脸检测的级联分类器!" << endl;
exit(0);
}
if (!eye_detector.load(eye_path))
{
std::cout << "无法打开左眼检测的级联分类器!" << endl;
exit(0);
}
cap.open(_cap);
if (!cap.isOpened())
{
std::cout << "无法打开摄像头!" << std::endl;
exit(0);
}
Mat frame;
Mat gray;
Mat use_face;
vector faces;
vector eyes;
while (cap.read(frame))
{
cvtColor(frame, gray, COLOR_BGR2GRAY);
equalizeHist(gray, gray);
//flip(gray, gray, 1);
vector faces;
face_detector.detectMultiScale(gray, faces, 1.2, 3, 0, Size(30, 30));
for (size_t t = 0; t < faces.size(); t++)
{
Rect roi;
roi.x = faces[static_cast(t)].x;
roi.y = faces[static_cast(t)].y;
roi.width = faces[static_cast(t)].width;
roi.height = faces[static_cast(t)].height / 2;
Mat faceROI = frame(roi);
//当检测到只有一张脸时,检测眼睛
if (faces.size() == 1)
{
eye_detector.detectMultiScale(faceROI, eyes, 1.2, 3, 0, Size(20, 20));
for (size_t k = 0; k < eyes.size(); k++)
{
Rect rect;
rect.x = faces[static_cast(t)].x + eyes[k].x;
rect.y = faces[static_cast(t)].y + eyes[k].y;
rect.width = eyes[k].width;
rect.height = eyes[k].height;
rectangle(frame, rect, Scalar(0, 0, 255), 2, 8, 0);
//只有都检测到两只眼睛时开始拍照
if (eyes.size() == 2)
{
char key = waitKey(100);
switch (key)
{
//按下p键时开始拍照
case'p':
{
Mat face_roi = gray(faces[0]);
//图像序号加1
image_number++;
//将检测到的人脸大小改为92*112与官司数据一样
resize(face_roi, use_face, Size(92, 112));
string filename = save_path + format("%d.jpg", image_number);
//存放到传入的目录
imwrite(filename, use_face);
imshow(filename, use_face);
waitKey(1000);
//销毁指定的窗口
destroyWindow(filename);
break;
}
}
//按ese键退出拍照
if (key == 27)
{
cap.release();
exit(0);
}
}
}
rectangle(frame, faces[t], Scalar(255, 0, 0), 2, 8, 0);
}
}
imshow("camera", frame);
waitKey(33);
}
}
2.我这里跟下载的官方数据保存在一起,文件夹名为41。
3.我多拍了十几张人脸照片,保存的格式还是跟官方一样为pgm,然后用showImage()函数去读取显示,把太相似的图像删除,只保存10张图像数据就可以了。
当训练人脸模型的时候,需要读取人脸和人脸对应的类名(标签)。官方给的教程是生成csv,本质上是用来让训练程序读取当前数据与对应的标签,opencv官方教程里面提供了自动生成csv文件的python脚本,文件路径与文件名是opencv_contrib-3.3.0\modules\face\samples\etc\create_csv.py。复制一份改成自己存放图像的路径运行就可以了。但我这里使用Boost库和C++的读写文件,生成了一个faceList.txt也是可以用的。
1.生成faceList.txt的函数代码:
void buildList(string face_iamge_path)
{
string bow_path = face_iamge_path + string("faceList.txt");
ifstream read_file(bow_path);
ofstream ous(bow_path);
fs::path face_path(face_iamge_path);
if (!fs::exists(face_path))
{
std::cout << "当前目录没有要训练的文件!" << std::endl;
exit(0);
}
fs::directory_iterator begin_iter(face_iamge_path);
fs::directory_iterator end_iter;
int i = 0;
//递归迭代rescursive 直接定义两个迭代器:i为迭代起点(有参数),end_iter迭代终点
for (; begin_iter != end_iter; ++begin_iter)
{
i++;
if (fs::is_directory(begin_iter->path()))
{
fs::directory_iterator begin(begin_iter->path());
fs::directory_iterator end;
for (; begin != end; ++begin)
{
string face_name = begin->path().string() + ";" + to_string(i);
//cout << face_name << endl;
ous << face_name << endl;
}
}
}
ous.close();
}
2.调用函数,传入数据路径,在当前目录下生成一个faceList.txt的文件,文件格式如下,标签名对应目录类名。
1.到这里所有数据都准备完成,之后就是训练和识别,影响识别的精准度有很多,训练数据收集应该是最重要的部分,所以尽量收集各种视角不用光线的正脸。
2.关于整个工程的源码,运行程序时的bug,或者有如何优化的想法都可以加这个群(487350510)互相讨论学习