使用机器学习的方法进行人脸检测的第一步需要训练人脸分类器,这是一个耗时耗力的过程,需要收集大量的正负样本,并且样本质量的好坏对结果影响巨大,如果样本没有处理好,再优秀的机器学习分类算法都是零。
今年3月23日,微软公司在推特(Twitter)社交平台上推出了一个基于机器学习的智能聊天机器人Tay,Tay被设定为一个年龄为十几岁的女孩,主要目标受众是18岁至24岁的青少年。人们只需要@一下Tay,Tay就会追踪该用户的网名、性别、喜欢的食物、邮编、感情状况等个人信息。除了聊天,Tay还可以说笑话,讲故事等,让人感觉这就是一个知道你很多小秘密,嘴甜心暖的“小闺蜜”。Tay会在与人们的交流中不断学习,随着时间积累,她的理解能力将逐步提升,变得愈发“智能”。
然而比较戏剧性的是在Tay推出24小时不到,这个人工智能机器人就被人类彻底“教坏”,成为一个集反犹太人、性别歧视、种族歧视于一身的“不良少女”。逼得微软不得不让Tay暂时“下岗”。
微软在一项声明中表示,“人工智能机器人Tay属于机器学习项目,设计它的目的就是让它与人类进行互动。在学习的过程中,它把人类的好坏言论都学了。因此,它发表了一些不恰当的言论。我们正在纠偏,对Tay进行调整。”
微软的机器学习算法不可谓不先进,但是为什么会出现这种情况呢,比较合理的解释就是Tay是被人们输入的“不良”样本带坏的。
一个好消息是,OpenCV安装包里自带有已经训练好的人脸分类器“haarcascade_frontalface_alt.xml”,位置在“XX\opencv\sources\data\haarcascades”里,我们可以直接拿来使用,检测效果还可以接受。这个文件夹下还有其他一些分类器,像左右眼、上身、笑脸检测等等。
下边就使用OpenCV自带的基于Haar特征的级联分类器实现人脸检测,使用的版本是VS2012+OpenCV2.4.10,本来我的机器上安装的是OpenCV2.4.13版本,但是发现13版本的加载分类器的函数cvLoad总是报错,换成10版本就没问题了(包括之前在训练决策树的时候,也发现2.4.13版本的训练结果总是不对)。
程序涉及到Mat和IplImage指针的一个转换,还有一个比较关键的函数cvHaarDetectObjects,函数原型是:
cvHaarDetectObjects( const CvArr* image,
CvHaarClassifierCascade* cascade, CvMemStorage* storage,
double scale_factor CV_DEFAULT(1.1),
int min_neighbors CV_DEFAULT(3), int flags CV_DEFAULT(0),
CvSize min_size CV_DEFAULT(cvSize(0,0)), CvSize max_size CV_DEFAULT(cvSize(0,0)));
- 第一个参数image,表示待检测图像;
- 第二个参数cascade,表示Haar特征分类器,需要使用cvLoad()加载Haar特征分类器XML文件;
- 第三个参数为storage,是申请的一块内存空间,用于管理所使用的内存。
- 第四个参数scale_factor,表示下一次对图像的扫描中,搜索窗口增长的比例系数,即下次搜索窗口扩大的比例,有其默认值1.1。
- 第五个参数min_neighbors,从其命名上就可以看出来,表示构成检测目标的相邻矩形的最小个数,如果在检测目标位置的“邻居”位置上,检测到的潜在的人脸目标小于设定值,则不认为是人脸区域,所有调节这个参数可以对人脸的检测个数特别是目标的鲁棒性产生影响。值越小,检测到的人脸区域越多,同时意味着可能有误检和重复区域。
- 第六个参数CV_DEFAULT,不甚理解,使用默认值吧。
- 第七个参数min_size和第八个参数max_size表示检测窗口的最小值和最大值,程序一般会根据图像大小自动设置。
#include <opencv2/opencv.hpp>
#include "highgui/highgui.hpp"
#include <Windows.h>
using namespace std;
using namespace cv;
int main()
{
// 加载Haar特征检测分类器
const char *pstrCascadeFileName = "D:\\ProgramFilesD\\opencv\\sources\\data\\haarcascades\\haarcascade_frontalface_alt.xml";
CvHaarClassifierCascade *pHaarCascade = NULL;
pHaarCascade = (CvHaarClassifierCascade*)cvLoad(pstrCascadeFileName);
// 载入图像并转换为灰度图像
const char *pstrImageName = "E:\\Picture\\Face03.jpg";
Mat image=imread(pstrImageName);
Mat imageGray;
cvtColor(image,imageGray,CV_RGB2GRAY);
// 人脸识别与标记
if (pHaarCascade != NULL)
{
MemStorage mStorage=cvCreateMemStorage(0);
cvClearMemStorage(mStorage);
// 识别
DWORD dwTimeBegin, dwTimeEnd;
dwTimeBegin = GetTickCount();
//Mat转换为IplImage
IplImage IimageGray(imageGray);
CvSeq *pcvSeqFaces = cvHaarDetectObjects(&IimageGray, pHaarCascade, mStorage);
dwTimeEnd = GetTickCount();
cout<<"检测到的人脸总数:\n"<<pcvSeqFaces->total<<endl<<endl;
cout<<"检测耗时:\n"<<dwTimeEnd-dwTimeBegin<<endl;
// 用矩形框标记
for(int i = 0; i <pcvSeqFaces->total; i++)
{
CvRect* r = (CvRect*)cvGetSeqElem(pcvSeqFaces, i);
Point2f p1=Point2f(r->x,r->y); //矩形左上角点位
Point2f p2=Point2f(r->x+r->height,r->y+r->width); //矩形右下角点位
rectangle(image,p1,p2,Scalar(255,0,0),2); //矩形框标注人脸位置
}
}
namedWindow("人脸检测",0);
imshow("人脸检测", image);
waitKey(0);
return 0;
}
标准脸:
艺术照:
多人合照:
这个是专门针对正面人脸的,检测效果还可以,有时候会有误检,通过调整参数一般可以消除掉。