基于opencv的人脸性别识别主要包含三部分:人脸检测、特征提取、性别分类。
一、开启摄像头
1、新建工程并配置OpenCv(注意工程类型选择win32控制台应用程序)
2、包含头文件
#include <opencv2\opencv.hpp>
using namespace cv;
using namespace std;
- 1
- 2
- 3
- 4
3、初始化一个摄像头捕捉器
首先,需要建立一个摄像头捕捉器,并将其与当前设备中的摄像头相关联:
CvCapture* capture = cvCreateCameraCapture(0);
cvNamedWindow("Camera");
- 1
- 2
4、调用摄像头步骤画面并显示
IplImage* cameraImage = NULL;
while ((cameraImage = cvQueryFrame(capture)) != NULL)
{
cvShowImage("Camera",cameraImage);
cvWaitKey(1);
}
- 1
- 2
- 3
- 4
- 5
- 6
显然cvQueryFrame()函数的作用是在当前的时间点从摄像头抓取的视频流中截出一帧,这里将其赋值给变量camearImage(IplImage*类型,因为这是1.0的代码),若其非空,则显示在屏幕上。注意这里必须添加延时函数cvWaitKey(单位是毫秒),哪怕只延时一毫秒否则将无法正常显示摄像头输出。
二、人脸检测
OpenCv2.x版本中封装的人脸检测函数基于AdaBoost(级联分类器)人脸检测算法,当然这里我们无需深入了解算法相关的知识,因为Intel已经将需要用到的、训练好的人脸检测器(分类器)放在了安装文件里:
1、准备工作
调用人脸检测函数前需要做一些准备工作,分别是初始化所需内存、初始化检测器指针、设置检测器路径:
static CvMemStorage* storage = NULL;
static CvHaarClassifierCascade* cascade = NULL;
const char* cascadePath = "D:\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt_tree.xml";
- 1
- 2
- 3
(1)从路径中可以看出,检测器位于安装目录下的source文件夹下的data文件夹下的haarcascades文件夹中。
(2)在C++表示路径是要用双斜杠,因为第一个斜杠会默认为是转义字符,对第二个斜杠进行转义。
(3)这三个变量均为全局变量。
2、图像灰度化
由于这里用到的人脸检测函数主要针对于灰度图像,因此需要将摄像头采集的彩色图像灰度化:
IplImage* grayImage = cvCreateImage(cvSize(cameraImage->width,cameraImage->height),8,1);
cvCvtColor(cameraImage,grayImage,CV_BGR2GRAY);
- 1
- 2
这里涉及到如何通过cvCreatImage创建一个空的8位无符号整型单通道图,即需要通过一个cvSize结构体来指定图像初始的尺寸,这点在opencv2.x得到了很大改良(Mat类的加入)。
3、调用人脸检测函数
首先,创建一块内存区域,并加载相应的检测器(这个在主循环外完成即可):
storage = cvCreateMemStorage(0);
cascade = (CvHaarClassifierCascade*)cvLoad(cascadePath);
- 1
- 2
然后,清空指定位置内存块,调用人脸检测函数:
cvClearMemStorage(storage);
CvSeq* objects = cvHaarDetectObjects(grayImage,cascade,storage,1.1,2,0,cvSize(30,30));
- 1
- 2
cvhaardetectobjects函数的参数较为复杂,具体参数设置参见:cvhaardetectobjects参数设置。我们这里需要了解的就是这个函数的返回参数是一系列检测结果序列,每个检测结果实际上就是一个矩形结构体对象。
4、绘制人脸区域矩形框
接下来一一绘制检测到的矩形结果:
for (int i = 0; i < (objects ? objects->total : 0); i++)
{
CvRect* rect = (CvRect*)cvGetSeqElem(objects,i);
cvRectangle(cameraImage,cvPoint(rect->x,rect->y),
cvPoint(rect->x + rect->width,rect->y + rect->height),cvScalar(0.0,255));
}
cvShowImage("Camera",cameraImage);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
5、性别识别
人脸识别可能需要分为几十甚至上百个类(因为有几十甚至上百个人),而性别识别则是一种特殊的人脸识别——只有两个类。
1、基本工具
通过OpenCv进行性别识别的基本工具是FaceRecognizer。这是OpenCv2.x版本中的一个基本的人脸识别类,它封装了三种基本但也是经典的人脸识别算法:基于PCA变换的人脸识别(EigenFaceRecognizer)、基于Fisher变换的人脸识别(FisherFaceRecognizer)、基于局部二值模式的人脸识别(LBPHFaceRecognizer)。这些算法差不多都是十年以前的人脸识别方法了,因此在今天看来正确率应该不会太让人满意,不过我们这里重在实践,而非算法研究(虽然本人就是搞图像识别算法研究的),因此我们不会在算法创新方面下太多功夫,所以选择了这三个基本的识别算法。
这里我们直接使用FaceRecognizer类的相关操作方法,对于其基本用法就不再赘述。
2、数据集准备
进行性别识别理所应当需要先准备一些性别识别方面的训练样本,需要强调的一点是,数据集的准备过程中也需要一些小的技巧,在之后我会专门写一篇博文来解释如何制作一个简易的性别识别训练集,这里直接用已经做好的训练集,性别识别训练集是取自中科院的人脸数据库CAS-PEAL的光照子集,包含400张男性人脸图片和400张女性人脸图片,剩余人脸图片作为测试样本。训练集包含三部分:男性样本、女性样本、测试样本。
3、识别算法的训练与测试
(1)新建一个控制台工程,配置OpenCv
(2)编写批量读取文件函数read_csv()
首先,批量txt文件是典型的io操作,需要包含以下头文件:
#include <iostream>
#include <sstream>
#include <fstream>
- 1
- 2
- 3
read_csv函数代码:
void read_csv(string& fileName,vector<Mat>& images,vector<int>& labels,char separator = ';')
{
ifstream file(fileName.c_str(),ifstream::in); //以读入的方式打开文件
String line,path,label;
while (getline(file,line)) //从文本文件中读取一行字符,未指定限定符默认限定符为“/n”
{
stringstream lines(line);
getline(lines,path,separator); //根据指定分割符进行分割,分为“路径+标号”
getline(lines,label);
if (!path.empty()&&!label.empty()) //如果读取成功,则将图片和对应标签压入对应容器中
{
images.push_back(imread(path,1)); //读取训练样本
labels.push_back(atoi(label.c_str())); //读取训练样本标号
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
read_csv()函数的主要功能就是读取指定目录下的路径文件(例如这里的at.txt),然后根据路径文件中的记录,逐行读入对应路径的训练样本路径及其标号,并放入对应容器(vector)中。至于为什么采用vector数据结构来存储训练样本,一是因为这样做简单直观,二是因为OpenCv的训练函数提供的是vector接口。当然这样做也存在一定弊端,就是必须一次性将训练样本全部读入到内存中,当训练样本数量庞大时这种方法不但会消耗掉巨额内存,而且效率低下。
3、读入训练样本
接下来在主函数中调用read_csv()函数,读取训练样本及标签,并放入对应容器中:
int _tmain(int argc, _TCHAR* argv[])
{
String csvPath = "E:\\性别识别数据库—CAS-PEAL\\at.txt";
vector<Mat> images;
vector<int> labels;
read_csv(csvPath,images,labels);
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
4、训练分类器
OpenCv中的FaceRecognizer类提供的分类器训练API函数非常简单,只需三句话,以EigenFaceRecognizer为例:
Ptr<FaceRecognizer> modelPCA = createEigenFaceRecognizer();
modelPCA->train(images,labels);
modelPCA->save("E:\\性别识别数据库—CAS-PEAL\\PCA_Model.xml");
- 1
- 2
- 3
同理,训练FisherFaceRecognizer、LBPHFaceRecognizer两个分类器并保存:
tr<FaceRecognizer> modelFisher = createFisherFaceRecognizer();
modelFisher->train(images,labels);
modelFisher->save("E:\\性别识别数据库—CAS-PEAL\\Fisher_Model.xml");
Ptr<FaceRecognizer> modelLBP = createLBPHFaceRecognizer();
modelLBP->train(images,labels);
modelLBP->save("E:\\性别识别数据库—CAS-PEAL\\LBP_Model.xml");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
4、测试分类器
训练完分类器后,接下来我们介绍如何使用这些训练好的分类器对测试样本进行分类。首先加载三个分类器:
Ptr<FaceRecognizer> modelPCA = createEigenFaceRecognizer();
Ptr<FaceRecognizer> modelFisher = createFisherFaceRecognizer();
Ptr<FaceRecognizer> modelLBP = createLBPHFaceRecognizer();
modelPCA->load("E:\\性别识别数据库—CAS-PEAL\\PCA_Model.xml");
modelFisher->load("E:\\性别识别数据库—CAS-PEAL\\Fisher_Model.xml");
modelLBP->load("E:\\性别识别数据库—CAS-PEAL\\LBP_Model.xml");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
然后读入一张测试样本,通过三个分类器对其进行预测:
Mat testImage = imread("E:\\性别识别数据库—CAS-PEAL\\测试样本\\男性测试样本\\face_480.bmp",0);
int predictPCA = modelPCA->predict(testImage);
int predictLBP = modelLBP->predict(testImage);
int predictFisher = modelFisher->predict(testImage);