有试过opencv和face++进行基于opencv的人脸检测测吗

OpenCV的例程中已经带有了人脸检测的例程,位置在:OpenCV\samples\facedetect.cpp文件,OpenCV的安装与这个例子的测试可以参考我之前的博文。
网上能够找到关于OpenCV人脸检测的例子也比较多,大多也都是基于这个例程来更改,只是多数使用的是OpenCV 1.0的版本,而OpenCV2.0以后由于模块结构的更改,很多人并没有将例程运行起来。如果是新版的OpenCV跑旧的例程,编译运行出错的话,需要确保:
#include &opencv2/objdetect/objdetect.hpp& 头文件被引用,老的头文件包含可能会提示找不到定义
libopencv_objdetect243.dll.a 库需要加入链接
之前找了几个例程,不尽如人意,于是决定还是改自带的例程更靠谱,更多的信息,已经在程序中添加注释,参见程序吧。
pro文件的工程配置,具体路径按照安装路径更改,Linux下也一样。
人脸检测基础知识整理
下面整理下人脸检测的相关知识。
人脸检测从整体来看分为四个部分:
1、Face detection 人脸识别,即识别出这是人的脸,而不管他是谁的。
2、Face preprocessing 面部预处理,即提取出脸部图像。
3、Collect and learn faces 脸部的特征采集和学习
4、Face recognition 脸部识别,找出最相近的相近脸部图像。
“基于知识的方法主要利用先验知识将人脸看作器官特征的组合,根据眼睛、眉毛、嘴巴、鼻子等器官的特征以及相互之间的几何位置关系来检测人脸。基于统计的方法则将人脸看作一个整体的模式——二维像素矩阵,从统计的观点通过大量人脸图像样本构造人脸模式空间,根据相似度量来判断人脸是否存在。在这两种框架之下,发展了许多方法。目前随着各种方法的不断提出和应用条件的变化,将知识模型与统计模型相结合的综合系统将成为未来的研究趋势。”(来自论文《基于Adaboost的人脸检测方法及眼睛定位算法研究》)
人脸检测算法的可靠性很大程度上依赖于分类器的设计,在2001年,Viola和Jones两位大牛发表了经典的《Rapid Object Detection using a Boosted Cascade of Simple Features》【1】和《Robust Real-Time Face Detection》【2】,在AdaBoost算法的基础上,使用Haar-like小波特征和积分图方法进行人脸检测,他俩不是最早使用提出小波特征的,但是他们设计了针对人脸检测更有效的特征,并对AdaBoost训练出的强分类器进行级联。这可以说是人脸检测史上里程碑式的一笔了,也因此当时提出的这个算法被称为Viola-Jones检测器。又过了一段时间,Rainer Lienhart和Jochen Maydt两位大牛将这个检测器进行了扩展【3】,最终形成了OpenCV现在的Haar分类器。在OpenCV2.0中又扩充了基于LBP特征的人脸检测器,某些情况下LBP特征比Haar来的更为快速。
在进行识别时首先通过大量的具有比较明显的haar特征(矩形)的物体图像用模式识别的方法训练出分类器,分类器是个级联的,每级都以大概相同的识别率保留进入下一级的具有物体特征的候选物体,而每一级的子分类器则由许多haar特征构成(由积分图像计算得到,并保存下位置),有水平的、竖直的、倾斜的,并且每个特征带一个阈值和两个分支值,每级子分类器带一个总的阈值。识别物体的时候,同样计算积分图像为后面计算haar特征做准备,然后采用与训练的时候有物体的窗口同样大小的窗口遍历整幅图像,以后逐渐放大窗口,同样做遍历搜索物体;每当窗口移动到一个位置,即计算该窗口内的haar特征,加权后与分类器中haar特征的阈值比较从而选择左或者右分支值,累加一个级的分支值与相应级的阈值比较,大于该阈值才可以通过进入下一轮筛选。当通过分类器所有级的时候说明这个物体以大概率被识别。
如果单纯是对功能进行实现,有了官方自带的例程做参考,移植实现并不是很难,几乎不用费太大的功夫,自带例程对照着OpenCV参考手册还是比较好理解,这部分例程已经成功在Linux(Ubuntu和嵌入式Linux)以及Windows下实现,后面实验室基于Qt设计的实验软件,也整合了进去。
程序参考本文后面给出的参考程序,当然最权威的还是软件自带例程,实现人脸检测的另外一个关键就是训练文件,基于Haar和LBP特征的人脸检测可以自动的对大量数据图片进行训练,训练结果存储在XML文件中以供使用,这些级联分类器一般需要训练上千幅人脸图片和上万幅非人脸图片,这些训练过程往往需要很长的时间(LBP特征需要几个小时,Harr特征可能甚至需要一个星期)不过OpenCV已经提供了不同种类的训练好的文件,因此我们可以方便的通过载入这些训练好的级联分类器XML文件来实现人脸、眼睛、鼻子等检测。
OpenCV的训练文件在源码目录的data文件夹下,里面包含haarcascades、hogcascades、lbpcascades,在haarcascades文件下包含大量的针对不同目标的训练文件,如下图所示:
文件名已经体现了文件的功能,因此只需要载入对应的文件即可。
到这里还只是进行了一个非常初步的研究,下一步的学习和识别还需要多多积累。
根据自带例程,将人脸检测算法加入我所做的实验软件中,分别选择不同的分类器进行实验,下图为实验结果。
人脸检测实验
实现特定的器官检测右眼、鼻子等检测
TARGET = cvcap
+= console
-= app_bundle
TEMPLATE = app
SOURCES += main.cpp
INCLUDEPATH+=D:\OpenCV\build\include
INCLUDEPATH+=D:\OpenCV\build\include\opencv
LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_core243.dll.a
LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_highgui243.dll.a
LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_imgproc243.dll.a
LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_video243.dll.a
LIBS+=D:\OpenCV\build\x86\mingw\lib\libopencv_objdetect243.dll.a
主程序,具体地方都已经注释。这里是打开摄像头读取数据,同样可以自己打开图片。
#include &opencv2/highgui/highgui.hpp&
#include &opencv2/imgproc/imgproc.hpp&
#include &opencv2/core/core.hpp&
#include &opencv2/objdetect/objdetect.hpp&
#include &QDebug&
using namespace
void detectAndDraw( Mat& img, CascadeClassifier& cascade,
CascadeClassifier& nestedCascade,
double scale, bool tryflip );
int main()
VideoCapture cap(<span style="color: #);
//打开默认摄像头
if(!cap.isOpened())
return -<span style="color: #;
CascadeClassifier cascade, nestedC
bool stop = false;
//训练好的文件名称,放置在可执行文件同目录下
cascade.load(&haarcascade_frontalface_alt.xml&);
nestedCascade.load(&haarcascade_eye_tree_eyeglasses.xml&);
while(!stop)
detectAndDraw( frame, cascade, nestedCascade,<span style="color: #,<span style="color: # );
if(waitKey(<span style="color: #) &=<span style="color: #)
stop = true;
return <span style="color: #;
void detectAndDraw( Mat& img, CascadeClassifier& cascade,
CascadeClassifier& nestedCascade,
double scale, bool tryflip )
int i = <span style="color: #;
double t = <span style="color: #;
//建立用于存放人脸的向量容器
vector&Rect& faces, faces2;
//定义一些颜色,用来标示不同的人脸
const static Scalar colors[] =
{ CV_RGB(<span style="color: #,<span style="color: #,<span style="color: #5),
CV_RGB(<span style="color: #,<span style="color: #8,<span style="color: #5),
CV_RGB(<span style="color: #,<span style="color: #5,<span style="color: #5),
CV_RGB(<span style="color: #,<span style="color: #5,<span style="color: #),
CV_RGB(<span style="color: #5,<span style="color: #8,<span style="color: #),
CV_RGB(<span style="color: #5,<span style="color: #5,<span style="color: #),
CV_RGB(<span style="color: #5,<span style="color: #,<span style="color: #),
CV_RGB(<span style="color: #5,<span style="color: #,<span style="color: #5)} ;
//建立缩小的图片,加快检测速度
//nt cvRound (double value) 对一个double型的数进行四舍五入,并返回一个整型数!
Mat gray, smallImg( cvRound (img.rows/scale), cvRound(img.cols/scale), CV_8UC1 );
//转成灰度图像,Harr特征基于灰度图
cvtColor( img, gray, CV_BGR2GRAY );
//改变图像大小,使用双线性差值
resize( gray, smallImg, smallImg.size(), <span style="color: #, <span style="color: #, INTER_LINEAR );
//变换后的图像进行直方图均值化处理
equalizeHist( smallImg, smallImg );
//程序开始和结束插入此函数获取时间,经过计算求得算法执行时间
t = (double)cvGetTickCount();
//检测人脸
//detectMultiScale函数中smallImg表示的是要检测的输入图像为smallImg,faces表示检测到的人脸目标序列,1.1表示
//每次图像尺寸减小的比例为1.1,2表示每一个目标至少要被检测到3次才算是真的目标(因为周围的像素和不同的窗口大
//小都可以检测到人脸),CV_HAAR_SCALE_IMAGE表示不是缩放分类器来检测,而是缩放图像,Size(30, 30)为目标的
//最小最大尺寸
cascade.detectMultiScale( smallImg, faces,
<span style="color: #.1, <span style="color: #, <span style="color: #
//|CV_HAAR_FIND_BIGGEST_OBJECT
//|CV_HAAR_DO_ROUGH_SEARCH
|CV_HAAR_SCALE_IMAGE
Size(<span style="color: #, <span style="color: #));
//如果使能,翻转图像继续检测
if( tryflip )
flip(smallImg, smallImg, <span style="color: #);
cascade.detectMultiScale( smallImg, faces2,
<span style="color: #.1, <span style="color: #, <span style="color: #
//|CV_HAAR_FIND_BIGGEST_OBJECT
//|CV_HAAR_DO_ROUGH_SEARCH
|CV_HAAR_SCALE_IMAGE
Size(<span style="color: #, <span style="color: #) );
for( vector&Rect&::const_iterator r = faces2.begin(); r != faces2.end(); r++ )
faces.push_back(Rect(smallImg.cols - r-&x - r-&width, r-&y, r-&width, r-&height));
t = (double)cvGetTickCount() -
qDebug( &detection time = %g ms\n&, t/((double)cvGetTickFrequency()*1000.) );
for( vector&Rect&::const_iterator r = faces.begin(); r != faces.end(); r++, i++ )
Mat smallImgROI;
vector&Rect& nestedO
Scalar color = colors[i%<span style="color: #];
double aspect_ratio = (double)r-&width/r-&
if( <span style="color: #.75 & aspect_ratio && aspect_ratio & <span style="color: #.3 )
//标示人脸时在缩小之前的图像上标示,所以这里根据缩放比例换算回去
center.x = cvRound((r-&x + r-&width*<span style="color: #.5)*scale);
center.y = cvRound((r-&y + r-&height*<span style="color: #.5)*scale);
radius = cvRound((r-&width + r-&height)*<span style="color: #.25*scale);
circle( img, center, radius, color, <span style="color: #, <span style="color: #, <span style="color: # );
rectangle( img, cvPoint(cvRound(r-&x*scale), cvRound(r-&y*scale)),
cvPoint(cvRound((r-&x + r-&width-<span style="color: #)*scale), cvRound((r-&y + r-&height-<span style="color: #)*scale)),
color, <span style="color: #, <span style="color: #, <span style="color: #);
if( nestedCascade.empty() )
smallImgROI = smallImg(*r);
//同样方法检测人眼
nestedCascade.detectMultiScale( smallImgROI, nestedObjects,
<span style="color: #.1, <span style="color: #, <span style="color: #
//|CV_HAAR_FIND_BIGGEST_OBJECT
//|CV_HAAR_DO_ROUGH_SEARCH
//|CV_HAAR_DO_CANNY_PRUNING
|CV_HAAR_SCALE_IMAGE
Size(<span style="color: #, <span style="color: #) );
for( vector&Rect&::const_iterator nr = nestedObjects.begin(); nr != nestedObjects.end(); nr++ )
center.x = cvRound((r-&x + nr-&x + nr-&width*<span style="color: #.5)*scale);
center.y = cvRound((r-&y + nr-&y + nr-&height*<span style="color: #.5)*scale);
radius = cvRound((nr-&width + nr-&height)*<span style="color: #.25*scale);
circle( img, center, radius, color, <span style="color: #, <span style="color: #, <span style="color: # );
cv::imshow( &result&, img );
阅读(...) 评论()3016人阅读
OpenCV中使用Eigenfaces&或&Fisherfaces进行人脸识别
Translate&by&Dawn
~~~~~~~~~~~~~~~~~~~~~~翻译的好辛苦~~~~~~~~~~~~~~~~~~
本文将介绍中人脸检测和人脸识别的概念,并提供一个项目,检测人脸,认出他们,当它再次看到他们。人脸识别是一种流行和困难的题目,和许多研究者投入年到人脸识别的字段。所以这里将会解释简单方法的人脸识别,给读者一个良好的开端,如果他们想要探索更复杂的方法。在本文中,我们包括下述内容:&
o&人脸检测&
o&人脸预处理培训从收集到的面孔&
o&人脸识别&
o&收尾介绍
人脸识别和人脸检测的脸识别的机器学习算法是把已知的脸一个标签的过程。就像是人类学会认清他们的家庭、&朋友和名人仅仅通过看看他们的脸上,有一台电脑,学会如何识别已知的脸的很多技巧。这通常包括四个主要步骤︰&
1.人脸检测︰&是&(在下面的屏幕快照的中心附近的大矩形)&图像中定位人脸区域的过程。这一步不关心谁的人,只是,它是人的脸。
2.脸预处理︰&它是调整对人脸图像,看起来更清晰,类&#20284;于其他的面孔&(在下面的屏幕快照的顶部中心的小灰度脸)&的过程。
3.收集和了解的面孔︰&这是&(为每一个人应该承认),节省许多预处理过的面孔,然后学习如何识别它们的过程。
4.人脸识别︰&它是检查哪一种收集人的过程是最类&#20284;于在相机&(右上角的下面的截图上的一个小矩形)&的脸。
请注意,短语人脸识别常常被公众发现人脸&(那就是,人脸检测,作为所述第&1&步),位置,但这本书将使用的人脸识别指的步骤&4&的正式定义和面对检测指第&1&步。
下面的截图说明最后的&WebcamFaceRec&项目,包括一个小的矩形在右上角突出的公认的人。此外注意到旁边的预处理的脸&(一张小脸在标记脸矩形的顶部中心),在这种情况下显示大约&70%的信任栏,它已认识到正确的人的信心。
当前的脸检测技术是相当可靠的现实世界的情况,而当前脸识别技术是在现实世界的情况中使用时不太可靠。例如,很容易找到研究论文显示脸识别准确率&95%以上,但在那些相同的算法测试自己时,你可能经常发现精度是低于&50%。这来自于当前的脸识别技术是非常敏感的确切的条件,在图像中,如照明、&照明和阴影的方向,准确定位的脸,表情的脸和目前的心情的人的类型的事实。如果他们都保持不变,当培训&(采集图像),以及当测试&(从摄像机图像),然后人脸识别应该是行得通,但如果人站到左手边的时候训练,,然后站到右手边的相机在测试时房间里的灯,它可能会很糟糕的结果。所以用于培训的数据集是非常重要的。
预处理&(步骤&2)&旨在减少这些问题,如通过确保的脸总是&#20284;乎有类&#20284;的亮度和对比度,和也许可以确保的面部特征的脸总是会在相同的位置&(如对齐的&#30524;睛和/或某些职位的鼻子)。预处理阶段好脸将有助于提高可靠性的整个人脸识别系统,所以这一章将一些强调预处理方法的脸上。
尽管在关于人脸识别在媒体中的安全有很大的需求,不太可能独自当前脸识别方法是足够可靠,任何真正的安全系统,但它们可以用于不需要高可靠性,如对不同的人进入一个房间或一个机器人,它说你的名字,当它看见你打个性化的音乐的目的。也有各种实际扩展到性别识别、&年龄识别等情感识别的人脸识别。
第&1&步︰&人脸检测
直到&2000&年,有许多不同的技术,用于查找的面孔,但他们都是要么很慢,很不可靠,或两者。主要的变化来当中提琴和&#29756;斯发明基于&Haar&的级联分类器为检测对象,2001&年和&2002&年当它提高了&Lienhart&和&Maydt。其结果是既快速对象探测器&(可以在典型的桌面与&VGA&摄像头实时检测人脸)&和可靠&(大约&95%的正面的面孔能够正确检测)。此对象探测器革命性脸识别&(以及一般的机器人和计算机视觉的),作为它最后允许实时人脸检测和人脸识别的领域特别是&Lienhart&自己写来自由与&OpenCV&对象探测器&!它的工作不仅为正面脸但也侧面脸&(称为配置文件的面孔)、&&#30524;、&嘴、&鼻、&公司徽标、&和许多其他的物体。
此对象探测器被延长在&OpenCV&v2.0&也使用&LBP&特征检测基于工作由阿、&哈迪德和&Pietik?inen&2006&年,基于&LBP&的探测器可能多次快比基于&Haar&的探测器,并没有许多&Haar&探测器的发牌问题。基于&Haar&的人脸检测的基本思想是,如果你看起来最正面的脸,用&#30524;睛区域应当比的额头和脸颊,暗和用嘴区域应该是暗比脸颊,等等。它通常执行比较像这样来决定,是否它是一张脸,或不是,但它必须做这在每个可能的位置,在图像中,每个可能的脸上,大小,所以事实上它经常数以千计的检查每个图像约&20&的阶段。基于&LBP&的人脸检测的基本思想是类&#20284;于基于&Haar&的但它使用直方图的像素强度比较,例如边缘、&角落和平坦的区域。
而不是决定一个人的比较将最好地定义一张脸,这两个哈尔和&LBP-基于人脸检测器可以自动训练来找到从图像,一大套的面孔与信息存储为&XML&文件,以便以后使用。这些级联分类器探测器通常被训练使用至少&1000&独特的脸幅图像和&10,000&非人脸图像&(例如,照片的树木、&汽车和文本),和训练的过程中可以需要很长时间,甚至在多核心桌面上&(通常为&LBP&几个小时&!),但一周为哈尔。幸运的是,OpenCV&带有一些&pretrained&的哈尔和&LBP&探测器,供您使用&!事实上可以检测正面脸、&配置文件&(侧视图)&脸、&&#30524;睛或鼻子,只是通过加载不同的级联分类器&XML&文件到目标探测器,并选择之间&Haar&或&LBP&探测器,根据您选择的&XML&文件。OpenCV&v2.4&执行使用&OpenCV&作为前面提到的人脸检测,带有各种&pretrained&XML&探测器,可用于不同的目的。下表列出了一些最受欢迎的&XML&文件︰
基于&Haar&的探测器都存储在文件夹&data\haarcascades&和基于&LBP&的探测器都存储在文件夹&data\lbpcascades&OpenCV&根文件夹下,如&C:\opencv\data\lbpcascades\。
我们要为我们的脸识别项目,检测正面人脸,所以让我们使用&LBP&人脸检测,因为它是最快的没有专利的许可问题。请注意这个&pretrained&的&LBP&脸检测器附带&OpenCV&v2.x&不调谐以及&pretrained&Haar&人脸检测器,所以如果你想更可靠的人脸检测你可能想要训练你自己&LBP&脸检测器或使用&Haar&人脸检测方法。
加载&Haar&或&LBP&detector&来执行人脸检测器
首先你必须加载&pretrained&的&XML&文件使用&OpenCV&的&CascadeClassifier&类,如下所示︰
CascadeClassifier&faceD
faceDetector.load(faceCascadeFilename);&
只是给一个不同的文件名,这就可以加载&Haar&或&LBP&探测器。一个很常见的错误时使用这是提供错误的文件夹或文件名,但根据您的生成环境,load&()&方法将要么返回&false&或生成&c&&#43;&&#43;&异常&(和退出程序,assert&错误)。所以,最好&load&()&方法与&try/catch&块包围,要向用户显示一条很好的错误消息,如果出了错。很多初学者跳过检查错误,但至关重要的是要向用户显示帮助消息,当事情未能正确加载,否则你可能会花很长的时间,最终实现东西未加载前调试代码的其他部分。一个简单的错误消息可以显示如下︰
CascadeClassifier&faceD&
faceDetector.load(faceCascadeFilename);&
}&catch&(cv::Exception&e)&{}&
if&(&faceDetector.empty()&)&{&&&&
cerr&&&&&ERROR:&Couldn't&load&Face&Detector&(&;&&&&
cerr&&&&faceCascadeFilename&&&&&)!&&&&&&&&&
访问摄像头
从电脑摄像头或者从一个视频文件捕捉视频帧,你可以简单地调用&VideoCapture::open()&函数与相机数量或视频文件名,然后抓帧使用&c&&#43;&&#43;&流的运算符,如访问章&1、&Cartoonifier&和皮肤改变摄像头的&Android&一节中提到。
使用&Haar&或&LBP&分类器检测对象
现在我们已经加载了&(只是一次在初始化过程中)&的分类,我们可以使用它在每个新的相机帧中检测人脸。但首先我们应该做一些初始图像处理的相机只是为人脸检测,通过执行以下步骤︰&
1.灰度颜色转换︰&仅适用于灰度图像的人脸检测。所以我们应该将彩色相机帧转换为灰度。
2.摄像机图像收缩︰&人脸检测的速度取决于输入的图像&(它是非常慢的大图像但快速小图像),大小和尚未检测仍然是相当可靠的即使在低分辨率。所以我们应相机图像缩小到一个更合理的大小&(或使用较大的&#20540;在探测器,minFeatureSize,如稍后再作解释)。
3.直方图均衡化︰&是不可靠在低光照条件下的人脸检测。所以我们应该执行直方图均衡化。
提高对比度和亮度
我们可以轻松地将&RGB&彩色图像转换为灰度使用&cvtColor()&函数的灰度颜色转换。但当我们知道我们有一种彩色图像,我们才应该做这&(那就是,它不是一个灰度相机),和我们必须在手机上指定的&(在桌面上通常&3&通道&BGR)&或&4&通道&BGRA&我们输入图像&#26684;式。所以,我们应让三种不同的输入的颜色&#26684;式,如下面的代码所示︰
if&(img.channels()&==&3)&{&&&&
cvtColor(img,&gray,&CV_BGR2GRAY);&
}&else&if&(img.channels()&==&4)&{&&&&
cvtColor(img,&gray,&CV_BGRA2GRAY);&
}&else&{&&&&
//&Access&the&grayscale&input&image&directly.&&&&
摄像机图像收缩
我们可以使用&resize()&函数将图像缩小到一定大小或规模的因素。人脸的检测通常很好适合任何图像大小大于&240&×&240&像素&(除非您需要检测人脸,相机离太远了),因为它将查找任何面孔大于&minFeatureSize&(通常&20&x&20&像素为单位)。因此,让我们缩小相机图像为&320&像素宽;如果输入是一个&VGA&摄像头或&5&万像素高清摄像头都没关系。它也是重要的是记住和扩大检测结果,因为如果缩小图像中检测人脸然后结果也会被缩小。请注意,而不是减少输入的图像,你可以使用一个大型&minFeatureSize&&#20540;在探测器相反。我们还必须确保图像不会成为胖或瘦。例如,当收缩到&300&x&200&宽屏&800&x&400&图像会让人看起来瘦。所以我们必须保持纵横比&(宽度与高度的比&#20540;)&的输出作为输入相同。让我们计算多少要缩小图像宽度,然后适用高度相同的比例因子,以及,具体如下︰
const&int&DETECTION_WIDTH&=&320;&
//&Possibly&shrink&the&image,&to&run&much&faster.&
Mat&smallI&
loat&scale&=&img.cols&/&(float)&DETECTION_WIDTH;&
if&(img.cols&&&DETECTION_WIDTH)&{&&&&
//&Shrink&the&image&while&keeping&the&same&aspect&ratio.&&&&
int&scaledHeight&=&cvRound(img.rows&/&scale);&&&&
resize(img,&smallImg,&Size(DETECTION_WIDTH,&scaledHeight));&
}&else&{&&&&
//&Access&the&input&directly&since&it&is&already&small.&&&&
smallImg&=&&
直方图均衡化
我们可以轻松地执行直方图均衡化,提高对比度和亮度的图像,使用&equalizeHist()&函数的直方图均衡化&(学习&OpenCV&所述︰&计算机视觉与&OpenCV&库)。有时这会使图像看起来很奇怪,但在一般情况下它应该提高亮度和对比度和帮助的人脸检测。EqualizeHist()&函数使用方法如下︰
//&Standardize&the&brightness&&&contrast,&such&as&
//&to&improve&dark&images.&
Mat&equalizedI&
equalizeHist(inputImg,&equalizedImg);
现在,我们已经将图像转换为灰度,缩小图像和直方图将比分扳平,我们准备使用&CascadeClassi&fier::detectMultiScale()&函数的脸检测&!有许多我们传递给此函数的参数:&
o&minFeatureSize︰&此参数确定最小脸大小,我们关心的事情,通常&20&×&20&或&30&×&30&像素,但这取决于你使用案例和图像大小。如果您正在执行的人脸检测对摄像头或智能手机,脸上总是将非常接近于相机,你可以放大,这对&80&x&80&有更快的检测,或如果您想要远脸检测,如在海滩上与朋友,然后离开这为&20&×&20。
o&searchScaleFactor:&参数确定如何许多不同大小的面孔去寻找;通常它会很好的检测,为&1.1&或&1.2&经常找不到脸的快速检测。
o&minNeighbors︰&此参数确定如何确保探测器应已检测到一张脸,通常&#20540;为&3,但您可以设置它高如果你想更可靠的面孔,即使许多面孔不检测。
o&标志︰&此参数允许您指定是否要为所有的脸&(默认&#20540;),或只看最大的脸&(CASCADE_FIND_BIGGEST_OBJECT)。如果你只是看的大脸,它应该跑得快。有几个其他参数,您可以添加要检测约&1%或&2%速度更快,如&CASCADE_DO_ROUGH_SEARCH&或&CASCADE_SCALE_IMAGE。DetectMultiScale()&函数的输出将&std::vector&cv::Rect&类型对象。例如,如果它检测到两个脸然后它将存储两个矩形的数组在输出中。DetectMultiScale()&函数使用方法如下︰
int&flags&=&CASCADE_SCALE_IMAGE;&//&Search&for&many&faces.&
Size&minFeatureSize(20,&20);&&&&&//&Smallest&face&
size.&float&searchScaleFactor&=&1.1f;&&//&How&many&sizes&to&search.&
int&minNeighbors&=&4;&&&&&&&&&&&&//&Reliability&vs&many&faces.
//&Detect&objects&in&the&small&grayscale&image.&
std::vector&Rect&&&
faceDetector.detectMultiScale(img,&faces,&searchScaleFactor,&&&&&&&&&&&&&&&&&
minNeighbors,&flags,&minFeatureSize);
我们可以看到是否任何面孔被检测到存储在我们的矩形的向量中元素的数量来看,这是通过使用&objects.size()&函数。
正如提到的早些时候,如果我们能缩小的图像给人脸检测,结果会也被缩小,所以我们需要将其放大,如果我们想要知道对原始图像的人脸区域。我们还需要扮&#39740;脸确定图像的边界上完全保持在范围内的图像,如&OpenCV&将现在引发异常如果发生这种情况,下面的代码所示︰
//&Enlarge&the&results&if&the&image&was&temporarily&shrunk.&
if&(img.cols&&&scaledWidth)&{&&&&
for&(int&i&=&0;&i&&&(int)objects.size();&i&#43;&#43;&)&{&&&&&&&&
objects[i].x&=&cvRound(objects[i].x&*&scale);&&&&&&&&
objects[i].y&=&cvRound(objects[i].y&*&scale);&&&&&&&&
objects[i].width&=&cvRound(objects[i].width&*&scale);&&&&&&&&
objects[i].height&=&cvRound(objects[i].height&*&scale);&&&&
}&//&If&the&object&is&on&a&border,&keep&it&in&the&image.&
for&(int&i&=&0;&i&&&(int)objects.size();&i&#43;&#43;&)&{&&&&
if&(objects[i].x&&&0)&&&&&&&&
objects[i].x&=&0;&&&&
if&(objects[i].y&&&0)&&&&&&&&
objects[i].y&=&0;&&&&
if&(objects[i].x&&#43;&objects[i].width&&&img.cols)&&&&&&&&
objects[i].x&=&img.cols&-&objects[i].&&&&
if&(objects[i].y&&#43;&objects[i].height&&&img.rows)&&&&&&&&
objects[i].y&=&img.rows&-&objects[i].&
请注意,上面的代码将寻找所有的脸在图像中,但如果你只在乎约一张脸,然后你可以更改标志变量,如下所示︰
Int&flags&=&CASCADE_FIND_BIGGEST_OBJECT&|&
CASCADE_DO_ROUGH_SEARCH;&
WebcamFaceRec&项目包括&OpenCV&的&Haar&或&LBP&探测器,能容易地找到脸上或&#30524;睛内的图像。例如︰
Rect&faceR&&&&//&Stores&the&result&of&the&detection,&or&-1.&
int&scaledWidth&=&320;&//&Shrink&the&image&before&detection.&
detectLargestObject(cameraImg,&faceDetector,&faceRect,&&&&&&&&&&&&&&&&
scaledWidth);&
if&(faceRect.width&&&0)&&&&
cout&&&&&We&detected&a&face!&&&&&
现在,我们有一个脸矩形,我们可以使用它在许多方面,如要提取或作物从原始图像的人脸图像。下面的代码允许我们访问的脸︰
//&Access&just&the&face&within&the&camera&image.&
Mat&faceImg&=&cameraImg(faceRect);
下图显示了典型的矩形区域,给出了通过人脸检测︰
第&2&步︰&脸预处理
作为前面提到的人脸识别是极易受到照明条件、&所面对的方位、&面部表情和等等的变化,所以它是非常重要的是减少这些差异尽可能多地。否则人脸识别算法往往会认为之间面临的两个不同的人在同样的条件比同一个人的两个面之间有更多的相&#20284;性。
&&&&脸预处理的最简单形式只是将使用&equalizeHist()&函数,像我们只是做了人脸检测的直方图均衡化。这可能是足够的一些项目在哪里的照明和位置的条件不会改变太多。但在现实条件下的可靠性,我们需要很多先进的技术,包括人脸特征检测&(例如,检测&#30524;睛、&鼻子、&嘴和眉毛)。为简单起见,本章将只需使用&#30524;检测,并忽略其他面部的功能,如嘴和鼻子,这是不太有用。下图显示了典型的预处理脸,使用本节中将覆盖技术放大的视图︰
&#30524;检测
&#30524;检测可以是非常有用的脸预处理,因为对正面人脸,你总是可以假定一个人的&#30524;睛应该是水平和在相反位置的脸上,应该有一个相当标准的位置和大小在脸上,尽管表情、&光照条件、&距离到相机,相机属性的变化,等等。它也是有用放弃误报,当人脸检测说它已检测到脸上和实际上是别的东西。它是罕见的人脸检测和两&#30524;探测器将所有上当在相同的时间,所以如果你只处理与检测的人脸图像和两个检测到的&#30524;睛然后它不会有很多误报&(但亦会更少的脸进行处理,如&#30524;检测器不能用作经常作为人脸检测)。
一些跟&OpenCV&v2.4&pretrained&的&#30524;探测器可以检测&#30524;是否打开或关闭,而有些人只能检测睁开&#30524;睛。
检测到开放或闭合的&#30524;睛的&#30524;探测器如下所示︰
o&haarcascade_lefteye_2splits.xml&(and&haarcascade_&righteye_2splits.xml)&
仅检测睁开&#30524;睛的&#30524;探测器如下︰
如开或闭&#30524;探测器指定哪只&#30524;睛,他们受过培训,您需要使用不同的检测器为左和右&#30524;,而探测器睁开的双&#30524;可以使用相同的探测器为左或右的&#30524;睛。如该人戴&#30524;镜,但并不是可靠的如果他们不戴&#30524;镜,探测器&haarcascade_eye_tree_eyeglasses.xml&可以检测到&#30524;睛。如果&XML&文件名说&左的&#30524;&,它意味着实际的左的&#30524;的人,所以在相机的形象它通常出现在右手边的脸上,不在左手边&!列表中提到的四个&#30524;探测器被排名近&#20284;从到的顺序最可靠最不可靠的因此如果你知道你不需要去找戴着&#30524;镜的人然后第一个探测器可能是最好的选择。
&#30524;搜索区域
对于&#30524;睛检测,它是重要作物对输入的图像只是显示近&#20284;&#30524;区域,就像做的人脸检测,然后裁剪到只是一个小的矩形的左的&#30524;应该在的地方&(如果你使用的左的&#30524;探测器)&和右侧的矩形右&#30524;探测器相同。如果你只是做&#30524;检测整张脸或整个照片上的,然后将速度更慢且不可靠。不同&#30524;探测器是更适合于不同地区的脸上,例如,haarcascade_eye.xml&探测器工作最好如果它仅搜索在很紧的区域,在真正的&#30524;睛,而&haarcascade_mcs_lefteye.xml&和&haarcascade_lefteye_2splits.xml&
探测器最佳工作时&#30524;部周围的一个大区域。下表列出了使用相对坐标检测人脸矩形内的不同&#30524;探测器&(当使用&LBP&脸检测器)&的时候,脸上一些好的搜索区域︰
在这里是要提取检测人脸的左&#30524;和右&#30524;的地区的源代码︰
int&leftX&=&cvRound(face.cols&*&EYE_SX);&
int&topY&=&cvRound(face.rows&*&EYE_SY);&
int&widthX&=&cvRound(face.cols&*&EYE_SW);&
int&heightY&=&cvRound(face.rows&*&EYE_SH);&
int&rightX&=&cvRound(face.cols&*&(1.0-EYE_SX-EYE_SW));
Mat&topLeftOfFace&=&faceImg(Rect(leftX,&topY,&widthX,&&&&&&&&&&&&&&&&
heightY));&
Mat&topRightOfFace&=&faceImg(Rect(rightX,&topY,&widthX,&&&&&&&&&&&&&&&&&
heightY));
下图显示了不同&#30524;探测器,haarcascade_eye.xml&和&haarcascade_eye_tree_eyeglasses.xml&在哪里最好与小小的搜索区域,而&haarcascade_mcs_*eye.xml&和&haarcascade_*eye_2splits.xml&是最好的与更大的搜索区域的理想的搜索区域。请注意,检测人脸矩形也显示,以便了解如何大&#30524;搜索区域被比作检测人脸矩形︰
当使用上表中给出的&#30524;搜索区域,这里是不同&#30524;探测器的近&#20284;检测属性︰
*&可靠性&#20540;表明多久两只&#30524;睛会检测后&LBP&正面人脸检测时没有&#30524;镜的磨损和两只&#30524;睛都开放。如果闭上&#30524;睛然后可靠性可能会下降,或如果戴&#30524;镜那么可靠性和速度会降低。
*&*&速度&#20540;是以毫秒为单位的图像缩放到的英特尔酷睿&i7&处理器&320&x&240&像素大小&2.2&GHz&(平均&#2&张照片)。速度通常快很多时如果未发现的&#30524;睛,因为它必须扫描整个图像,但&haarcascade_mcs_lefteye.xml&是仍远低于其他&#30524;探测器比发现的&#30524;睛。
例如,如果你收缩到&320&x&240&像素的照片,对它进行直方图均衡化,使用&LBP&正面人脸检测器可以得到一张脸,然后提取的左&#30524;部和右&#30524;部从脸上使用&haarcascade_mcs_&lefteye.xml&的&#20540;,然后对每个&#30524;区域执行直方图均衡化。然后如果你在左边&haarcascade_mcs_lefteye.xml&探测器&#30524;&(这实际上是对您的图像的顶部右侧)&和使用&haarcascade_mcs_&righteye.xml&探测器上右&#30524;&(你图像的顶部左侧部分),每个&#30524;探测器应该工作在大约&90%的照片&LBP&检测正面面。所以如果你想发现的两只&#30524;睛然后它应该工作在大约&80%的&LBP&检测正面脸的照片。
注意,虽然它建议缩小相机图像检测的脸前,你应检测在充分摄像机分辨率的&#30524;睛,因为&#30524;睛显然会比脸,小得多,所以你仍然需要尽可能大的分辨率,你可以得到。
基于表&#26684;,&#20284;乎当选择&#30524;检测器使用,你应该决定是否要检测闭着&#30524;睛或只睁开&#30524;睛。并请记住,您甚至可以使用一个&#30524;检测器,如果它不能检测&#30524;然后你就可以尝试用另一个。对于很多任务,它是有助于发现的&#30524;睛,无论它们是打开还是关闭,所以如果速度不是关键的最好用&mcs_&搜索&*&首先,&#30524;探测器,如果它失败然后搜索&eye_2splits&检测器。但人脸识别,人会出现完全不同的如果他们的&#30524;睛闭着,所以,最好首先,搜索与平原&haarcascade_eye&探测器,如果它失败,然后搜索&haarcascade_eye_&tree_eyeglasses&检测器。
我们可以使用相同的&detectLargestObject()函数,我们的人脸检测用于搜索的&#30524;睛,但而不是要缩小图像前&#30524;检测,我们指定全&#30524;区域宽度以获得更好的&#30524;睛检测。它很容易使用一个探测器,左&#30524;搜索和如果它失败然后尝试另一种探测器&(为右&#30524;相同)。&#30524;睛检测完成,如下所示︰
CascadeClassifier&eyeDetector1(&haarcascade_eye.xml&);&
CascadeClassifier&&&&&&&&&&
eyeDetector2(&haarcascade_eye_tree_eyeglasses.xml&);&
Rect&leftEyeR&&&&//&Stores&the&detected&eye.&
//&Search&the&left&region&using&the&1st&eye&detector.&
detectLargestObject(topLeftOfFace,&eyeDetector1,&leftEyeRect,&&&&&&&&&&&&
topLeftOfFace.cols);&//&If&it&failed,&search&the&left&region&using&the&2nd&eye&
//&detector.&
if&(leftEyeRect.width&&=&0)
&&&&detectLargestObject(topLeftOfFace,&eyeDetector2,&&&&&&&&&&&&&&&&&
leftEyeRect,&topLeftOfFace.cols);&
//&Get&the&left&eye&center&if&one&of&the&eye&detectors&worked.&
Point&leftEye&=&Point(-1,-1);&
if&(leftEyeRect.width&&=&0)&{&&&&
leftEye.x&=&leftEyeRect.x&&#43;&leftEyeRect.width/2&&#43;&leftX;&&&&
leftEye.y&=&leftEyeRect.y&&#43;&leftEyeRect.height/2&&#43;&topY;
//&Do&the&same&for&the&right-eye&...
//&Check&if&both&eyes&were&detected.&
if&(leftEye.x&&=&0&&&&rightEye.x&&=&0)&{&&&&
面对与检测到的两只&#30524;睛,我们会执行脸预处理相结合:&
o&几何变换和裁剪︰&这一进程包括缩放、&旋转,和翻译图像,&#30524;睛一致的后删除的额头、下巴、&耳朵和从人脸图像的背景。
o&单独直方图均衡化的左、&右两边︰&这一进程独立标准化的亮度和对比度两边左边和右边的脸。
o&平滑︰&此过程可以减少使用双边滤波图像噪声。
o&椭圆掩码︰&椭圆掩码从人脸图像中移除一些剩余的头发和背景。下图显示了预处理步骤&1&到&4&适用于检测人脸的脸。而原来不,请注意最终的图像如何面对,两侧有了好的亮度和对比度:
它是重要的脸上都被整合在一起,否则人脸识别算法可能会比较鼻子与&#30524;睛和等等的一部分的一部分。刚才见到的人脸检测的输出会对齐的面孔在某种程度上,但它不是很准确&(即,脸矩形不总是会开始从额头上的同一点)。为了有更好的协调我们将使用&#30524;检测要对齐的脸,所以两个检测到&#30524;睛的位置完全在排队所需的位置。我们会使用&warpAffine()&的几何变换函数,是一种单一的操作,将做四件事:&
o&旋转的脸,两&#30524;的水平。
o&扩展的脸,两只&#30524;睛之间的距离始终不变。
o&翻译的脸,&#30524;睛总是居中水平,所需的高度。
o&作物外部分的脸上,因为我们想要去裁剪图像背景,头发、&额头、&耳朵和下巴。
仿射翘曲将仿射矩阵变换到两个所需的&#30524;睛位置,两个检测到的&#30524;睛位置,然后作物带到所需的大小和位置。若要生成此仿射矩阵,我们将得到中心两&#30524;之间,计算的角的两个检测到的&#30524;睛出现,并看看他们之间的距离,如下所示︰
//&Get&the&center&between&the&2&eyes.&
Point2f&eyesCenter;
eyesCenter.x&=&(leftEye.x&&#43;&rightEye.x)&*&0.5f;&
eyesCenter.y&=&(leftEye.y&&#43;&rightEye.y)&*&0.5f;
//&Get&the&angle&between&the&2&eyes.&
double&dy&=&(rightEye.y&-&leftEye.y);
&double&dx&=&(rightEye.x&-&leftEye.x);&
double&len&=&sqrt(dx*dx&&#43;&dy*dy);&
//&Convert&Radians&to&Degrees.&
double&angle&=&atan2(dy,&dx)&*&180.0/CV_PI;
//&Hand&measurements&shown&that&the&left&eye&center&should&
//&ideally&be&roughly&at&(0.16,&0.14)&of&a&scaled&face&image.&
const&double&DESIRED_LEFT_EYE_X&=&0.16;&
const&double&DESIRED_RIGHT_EYE_X&=&(1.0f&–&
&//&Get&the&amount&we&need&to&scale&the&image&to&be&the&desired
&//&fixed&size&we&want.
const&int&DESIRED_FACE_WIDTH&=&70;&
const&int&DESIRED_FACE_HEIGHT&=&70;&
double&desiredLen&=&(DESIRED_RIGHT_EYE_X&–&
double&scale&=&desiredLen&*&DESIRED_FACE_WIDTH&/&&
现在我们可以face&(rotate,&scale,&and&translate),拿这两个检测到&#30524;睛要一张理想的脸所需的&#30524;职位如下︰
//&Get&the&transformation&matrix&for&the&desired&angle&&&size.&
Mat&rot_mat&=&getRotationMatrix2D(eyesCenter,&angle,&scale);&
//&Shift&the&center&of&the&eyes&to&be&the&desired&center.&
double&ex&=&DESIRED_FACE_WIDTH&*&0.5f&-&eyesCenter.x;&
double&ey&=&DESIRED_FACE_HEIGHT&*&DESIRED_LEFT_EYE_Y&–&&&&&&&&&&&&&&&&&&&&&
eyesCenter.y;&
rot_mat.at&double&(0,&2)&&#43;=&&
rot_mat.at&double&(1,&2)&&#43;=&
&//&Transform&the&face&image&to&the&desired&angle&&&size&&
&//&position!&Also&clear&the&transformed&image&background&to&a&
//&default&grey.&
Mat&warped&=&Mat(DESIRED_FACE_HEIGHT,&DESIRED_FACE_WIDTH,&&&&&&&&&&&&&&&&&&&&
CV_8U,&Scalar(128));&
warpAffine(gray,&warped,&rot_mat,&warped.size());
单独的直方图均衡化的左、&右两边在现实世界的情况,是很常见的一个上具有较强的照明的脸和另一方面的微弱灯光照明的一半。这已极大地影响了人脸识别算法,为同一张脸的左边和右边双方将看起来像非常不同的人。所以我们会在脸上,在每一边的脸上有标准化的亮度和对比度的左、&右半球分别执行直方图均衡化。如果我们简单地应用直方图均衡化的左半部分,然后再对右半部分,我们会看到非常明显的优势,在中间因为平均亮度很可能是在左边和右边都不同,所以要删除这方面的优势,我们将逐渐从左边两个直方图均衡-或右手边向中心和混合与整个脸直方图均衡化这样远左手边将使用左的直方图均衡化、&远右侧将使用正确的直方图均衡化、&和中心将向左或向右&#20540;和整个脸均衡的&#20540;光滑混合使用。
下图显示了如何的左均衡、&整体均衡和权利均衡的图像混合在一起︰
要执行该操作,我们需要整张脸以及一半将比分扳平的左和右半部分将比分扳平,将比分扳平,如下所示完成的副本︰
int&w&=&faceImg.&int&h&=&faceImg.&Mat&wholeF&equalizeHist(faceImg,&
wholeFace);&
int&midX&=&w/2;&
Mat&leftSide&=&faceImg(Rect(0,0,&midX,h));&
Mat&rightSide&=&faceImg(Rect(midX,0,&w-midX,h));&
equalizeHist(leftSide,&leftSide);&
equalizeHist(rightSide,&rightSide);
现在我们结合在一起的三幅图像。图像被小,我们可以轻松地访问像素直接使用&image.at&uchar&(y,x)&函数,即使它是缓慢;&所以让我们将这三个图像合并通过直接访问的三个输入的图像和输出图像中的像素,如下所示︰
for&(int&y=0;&y&h;&y&#43;&#43;)&{&&&&
for&(int&x=0;&x&w;&x&#43;&#43;)&{&&&&&&&
int&v;&&&&&&&&
if&(x&&&w/4)&{&&&&&&&&&&&&
//&Left&25%:&just&use&the&left&face.&&&&&&&&&&&&
v&=&leftSide.at&uchar&(y,x);&&&&&&&&
}&&else&if&(x&&&w*2/4)&{&&&&&&&&&&&&
//&Mid-left&25%:&blend&the&left&face&&&whole&face.&&&&&&&&&&&&
int&lv&=&leftSide.at&uchar&(y,x);&&&&&&&&&&&&
int&wv&=&wholeFace.at&uchar&(y,x);&&&&&&&&&&&&
//&Blend&more&of&the&whole&face&as&it&moves&&&&&&&&&&&&
//&further&right&along&the&face.&&&&&&&&&&&&
float&f&=&(x&-&w*1/4)&/&(float)(w/4);&&&&&&&&&&&&
v&=&cvRound((1.0f&-&f)&*&lv&&#43;&(f)&*&wv);&&&&&&&&
}&&else&if&(x&&&w*3/4)&{&&&&&&&&&&&&
//&Mid-right&25%:&blend&right&face&&&whole&face.&&&&&&&&&&&&
int&rv&=&rightSide.at&uchar&(y,x-midX);&&&&&&&&&&&&
int&wv&=&wholeFace.at&uchar&(y,x);&&&&&&&&&&&&
//&Blend&more&of&the&right-side&face&as&it&moves&&&&&&&&&&&&
//&further&right&along&the&face.&&&&&&&&&&&&
float&f&=&(x&-&w*2/4)&/&(float)(w/4);&&&&&&&&&&&&
v&=&cvRound((1.0f&-&f)&*&wv&&#43;&(f)&*&rv);&&&&&&&&
}&&else&{&&&&&&&&&&&&
//&Right&25%:&just&use&the&right&face.&&&&&&&&&&&&
v&=&rightSide.at&uchar&(y,x-midX);&&&&&&&&
faceImg.at&uchar&(y,x)&=&v;&&&&
}//&end&x&loop&
}//end&y&loop
这分离的直方图均衡化应该大大帮助降低不同的照明在脸上,左,右手边的影响,但我们必须明白它不会完全删除的片面的照明设备,因为脸上是一个复杂的三维形状与许多阴影效果。
为了减少像素噪声的影响,我们将使用双边滤波在脸上,双边滤波是非常擅长同时保持锋利的边缘平滑图像的大部分。直方图均衡化算法能显著提高像素噪声,所以我们会使过滤强度&20&盖重像素噪声,但使用的只是两个像素邻域,因为我们想要大量光滑小像素噪声但不是大的图像区域,如下︰
Mat&filtered&=&Mat(warped.size(),&CV_8U);&bilateralFilter(warped,&filtered,&0,&20.0,&2.0);&
椭圆面具虽然我们已删除大多数图像背景和前额和头发当我们做的几何变换。
我们可以应用椭圆的蒙版要去掉一些拐角区域如的脖子,可能是在脸上,阴影,特别是如果脸上并不是非常直对着镜头。若要创建蒙版,我们将绘制到白色的图像上黑填充椭圆。要执行该操作的一个椭圆的水平半径为&0.5&(即,它覆盖面宽完美),0.8&垂直半径&(通常比脸正他们是宽),和中心点坐标&0.5,0.4,如下面的图像,椭圆面具已删除一些不需要的角落项,从脸上所示︰
我们可以应用蒙版时调用&cv::setTo()&函数,通常会设置整个图像为某些像素&#20540;,但是,我们会给一个屏蔽图像,它将只设置部分为给定的像素&#20540;。所以,它应该有降低对比度到其余的脸上,我们将填补中灰色的图像︰
//&Draw&a&black-filled&ellipse&in&the&middle&of&the&image.&
//&First&we&initialize&the&mask&image&to&white&(255).&
Mat&mask&=&Mat(warped.size(),&CV_8UC1,&Scalar(255));&
double&dw&=&DESIRED_FACE_WIDTH;&
double&dh&=&DESIRED_FACE_HEIGHT;&
Point&faceCenter&=&Point(&cvRound(dw&*&0.5),&&&&&&&&&&&&&&&&&&&&&
cvRound(dh&*&0.4)&);&
Size&size&=&Size(&cvRound(dw&*&0.5),&cvRound(dh&*&0.8)&);&
ellipse(mask,&faceCenter,&size,&0,&0,&360,&Scalar(0),&&&
CV_FILLED);
//&Apply&the&elliptical&mask&on&the&face,&to&remove&corners.&
//&Sets&corners&to&gray,&without&touching&the&inner&face.&
filtered.setTo(Scalar(128),&mask);
以下的放大的图像显示从预处理阶段的所有脸上的样例结果。注意到它是在不同亮度,脸的轮换,从摄像机、&背景、&位置灯、&等等角度的人脸识别更加一致。这个预处理的脸将用作输入到脸识别阶段,收集培训,脸和时试图输入的面孔识别︰
第&3&步︰&收集的面孔和从中学习
收集的面孔可以从相机,将每个新预处理的脸投入数组预处理过的面孔,以及投入使用数组&(来指定哪一个人的脸取自)&标签一样简单。例如,您可以使用&10&预处理后的面临的第一人和&10&预处理后的面临的第二人,所以人脸识别算法的输入会&(第一次&10&个号码均为&0,接下来&10&个号码是&1)&的&20&个整数的数组和数组的&20&预处理过的脸。人脸识别算法,然后将学习如何区分不同人的面孔。这指训练阶段和收集的面孔统称为训练集。人脸识别算法完成培训后,你可以然后保存到文件或内存中产生的知识和以后使用它来识别哪些人看到在镜头前。这称为测试阶段。如果你使用它直接从相机的输入然后预处理的脸会被称为测试图像,和如果你测试有许多图片&(如图像文件的文件夹),它会被称为测试集。
为你提供良好的训练集是很重要的,涵盖的希望在您的测试套件中发生的变化类型。例如,如果您只将测试正在寻找完美正前方照片&(如&ID)&的面孔,然后你只需要提供训练图像看着完美前方的面孔。但如果可能找人,向左或向上,然后您应该确保训练集还将包括该这样做,否则人脸识别算法的人脸会有烦恼认识他们,他们的脸就会出现完全不同。这也适用于其他因素,例如面部表情&(例如,如果人是在训练集总是微笑笑着,而不是在测试集)&或照明方向&(例如,强烈的光线在训练集的左手边,而测试中右侧设置),然后将人脸识别算法都认识到他们的困难。预处理步骤,我们刚才看到的脸会帮助减少这些问题,但它肯定不会删除这些因素,特别是在这张脸在看,方向,因为它有很大的影响,对所有元素在脸上的位置。
获取将涵盖许多不同的现实世界条件良好的训练集的一种方法是每个人要旋转他们头从向左,达权下来然后直接直盯着看。然后人倾斜其头偏向一侧,然后向上和向下,同时也在改变自己的面部表情,例如微笑,看不生气,有中性的脸之间交替。如果每一个人跟随一个例程如这收集的面孔时,则承认每个人都在现实世界的情况更好的机会。为更好的结果,它应重新执行与一个或两个位置或方向,如由相机&180&度转身走在相反的方向,然后重复整个例程,所以的相机,训练集将包括很多不同的照明条件。
所以一般情况下,每人有&100&培训面孔很可能给更好的结果,比只是&10&培训面临的每一个人,但如果所有的&100&张脸看起来几乎一模一样然后它将仍然表现不佳因为它是更重要的是训练集有足够的品种覆盖测试集,而不只是有一大批的面孔。因此,请确保在训练集所面临的不是都太相&#20284;,我们应该添加之间每个收集的脸明显的延迟。例如,如果相机运行在&30&帧&/&秒,那么它可能只是几秒钟后,当这个人已经不在收集&100&张脸还没来得及左右移动,所以它是更好的收集只是一脸每秒,而人在附近移动,他们的脸。另一种简单的方法来提高训练集的变化是只收集一张脸,如果它是从以前收集的脸上明显不同。
收集预处理培训的面孔
以确保收集新面孔是存在至少一秒差距的预处理的面孔,需要我们加以衡量多少时间过去了。这是完成的如下所示︰
//&Check&how&long&since&the&previous&face&was&added.&
double&current_time&=&(double)getTickCount();&
double&timeDiff_seconds&=&(current_time&–&&&&&&&&&&&&&&&&
old_time)&/&getTickFrequency();
若要比较两个图像的相&#20284;性,逐个像素,你能找到相对的&L2&误差,只是从另一个图像中减去,总结它的平方的&#20540;,然后获取它的平方根。所以如果人不是在所有提出,减去当前面对以前脸上的应该在每个像素,给人数很少,但他们刚搬略在任何方向,减去像素会给大量和如此的&L2&误差会很高。作为结果相加的所有像素,&#20540;将取决于图像分辨率。所以,得到平均误差我们应该除以此&#20540;的图像中的像素总数。让我们把这个方便的功能,getSimilarity(),如下︰
double&getSimilarity(const&Mat&A,&const&Mat&B)&{&&&&
//&Calculate&the&L2&relative&error&between&the&2&images.&&&&
double&errorL2&=&norm(A,&B,&CV_L2);&&&&
//&Scale&the&value&since&L2&is&summed&across&all&pixels.&&&&
double&similarity&=&errorL2&/&(double)(A.rows&*&A.cols);&&&&
//&Check&if&this&face&looks&different&from&the&previous&face.&
double&imageDiff&=&MAX_DBL;&
if&(old_prepreprocessedFaceprepreprocessedFace.data)&{&&&&
ImageDiff&=&getSimilarity(preprocessedFace,&&&&&&&&&&&&&&&&&&&&&&&&&&&
old_prepreprocessedFace);&
这种相&#20284;性往往会低于&0.2&如果图像没有移动很多,和高于&0.4&如果图像做移动,因此,让我们使用&0.3&作为我们的阈&#20540;来收集一张新面孔。
有很多的技巧,我们可以玩来获得更多的培训资料,如使用镜像的面孔、&添加随机噪声、&转移由几个像素的脸上,按百分比缩放脸上或脸上旋转几度,(即使我们专门试图消除这些影响,当预处理脸&!)让我们添加镜像的脸训练集,所以,我们有两个,较大的训练集和减少问题中的不对称的脸,或用户在训练,但不是测试期间一直是面向略向左或向右。这是完成的如下所示︰
//&Only&process&the&face&if&it's&noticeably&different&from&the&
//&previous&frame&and&there&has&been&a&noticeable&time&gap.&
if&((imageDiff&&&0.3)&&&&(timeDiff_seconds&&&1.0))&{&&&&
//&Also&add&the&mirror&image&to&the&training&set.&&&&
Mat&mirroredF&&&&
flip(preprocessedFace,&mirroredFace,&1);
//&Add&the&face&&&mirrored&face&to&the&detected&face&lists.&&&&
preprocessedFaces.push_back(preprocessedFace);&&&&
preprocessedFaces.push_back(mirroredFace);&&&&
faceLabels.push_back(m_selectedPerson);&&&&
faceLabels.push_back(m_selectedPerson);
//&Keep&a&copy&of&the&processed&face,&&&&
//&to&compare&on&next&iteration.&&&&
old_prepreprocessedFace&=&preprocessedF&&&&
old_time&=&current_&
这将收集&std::vector&阵列&preprocessedFaces&和&faceLabels&为预处理过的脸,以及标签或&ID&号的那个人&(假设它整数&m_selectedPerson&变量中)。要对我们有向集合添加他们当前的脸上的用户进行更明显,您可以提供视觉通知通过任一对整个图像显示一个大的白色矩形或只显示他们的脸上只是一小部分的第二次,使他们意识到一张照片。利用&OpenCV&的&c&&#43;&&#43;&接口,您可以使用&&#43;&重载&cv::Mat&运算符将&#20540;添加到图像中的每个像素,并有它夹到&255&之间&(使用&saturate_cast,所以它不会溢出到黑从白回来&!)假设&displayedFrame&将复印件应显示彩色相机帧,插入这脸收集到上述代码之后︰
//&Get&access&to&the&face&region-of-interest.&
Mat&displayedFaceRegion&=&displayedFrame(faceRect);&
//&Add&some&brightness&to&each&pixel&of&the&face&region.&
displayedFaceRegion&&#43;=&CV_RGB(90,90,90);
培训人脸识别系统
从收集到的面孔之后您已收集足够让每个人认识的面孔,你必须训练系统学会使用一种适合于人脸识别的机器学习算法的数据。在文献中,最简单的方法是特征脸和人工神经网络有很多不同的人脸识别算法。特征脸往往更好的工作,比人工神经网络,尽管它的简单性,它往往几乎和许多更复杂的人脸识别算法,工作所以它已成为非常流行的作为初学者以及基本的人脸识别算法对于新算法可以相比。任何一位读者如欲工作进一步脸上识别推荐阅读背后的理论:&
o&Eigenfaces&&(也被称为主成分分析&(PCA)&
o&Fisherfaces&(也被称为线性判别分析&(LDA)&
o&其他经典的人脸识别算法(许多是发售在&&
http://www.&
o&Newer&face对最近的计算机视觉研究论文(如视觉和&ICCV&在&&识别算法那里数以百计的脸识别论文每年出版然而,你不需要去了解这些算法的理论内容,使用它们,在这本书中所示。由于&OpenCV&团队和菲利普·瓦&#26684;纳&libfacerec&贡献,OpenCV&v2.4.1&提供&cv::Algorithm&作为一种简单通用的方法来执行使用几种不同的算法(在运行时甚至可选)&之一不一定理解它们如何付诸实施的人脸识别。通过算法︰&getList()&功能,如使用此代码,可以在&OpenCV&你版本中找到可用算法︰
vector&string&&&
Algorithm::getList(algorithms);&
cout&&&&&Algorithms:&&&&&&algorithms.size()&&&&&
for&(int&i=0;&i&algorithms.size();&i&#43;&#43;)&{&&&&
cout&&&&algorithms[i]&&&&&
这里有三种人脸识别算法在&OpenCV&v2.4.1:&
o&FaceRecognizer.Eigenfaces︰&&Eigenfaces,也被称为&PCA,特克和彭特兰在&1991&年第一次使用。
o&FaceRecognizer.Fisherfaces:&Fisherfaces,也被称为&LDA,1997&年发明的&Belhumeur、&Hespanha&和&Kriegman。
o&FaceRecognizer.LBPH︰&本地二进制模式直方图,2004&年由阿、&哈迪德和&Pietik?inen&发明。
这些脸识别算法实现的详细信息可以发现与文档、&示例和&Python&等价物为他们每个人都在菲利普&·&瓦&#26684;纳网站上的博客&&和&&等。
这些人脸识别算法都可以通过&OpenCV&的&contrib&模块中的&FaceRecognizer&类。由于动态链接,则可能您的程序相连&contrib&模块,但不是在运行时实际加载&(如果它被认为是为不需要)。因此它建议在尝试访问&FaceRecognizer&算法之前调用&cv::initModule_&contrib()&函数。此函数只是索取&OpenCV&v2.4.1,所以它还可以确保人脸识别算法至少提供给您在编译时︰
//&Load&the&&contrib&&module&is&dynamically&at&runtime.&
bool&haveContribModule&=&initModule_contrib();&
if&(!haveContribModule)&{&&&&
cerr&&&&&ERROR:&The&'contrib'&module&is&needed&for&&;&&&&
cerr&&&&&FaceRecognizer&but&hasn't&been&loaded&to&OpenCV!&;&&&&
cerr&&&&&&&&exit(1);&
若要使用人脸识别算法之一,我们必须创建一个&FaceRecognizer&对象,使用&cv::Algorithm::create&FaceRecognizer&()&函数。我们传递我们想要使用,对此创建函数的字符串作为人脸识别算法的名称。这将给我们访问到该算法如果它是在&OpenCV&版本中可用。因此它可能是用作运行时错误检查,以确保用户有&OpenCV&v2.4.1&或更高版本。例如︰
string&facerecAlgorithm&=&&FaceRecognizer.Fisherfaces&;&
Ptr&FaceRecognizer&&&
//&Use&OpenCV's&new&FaceRecognizer&in&the&&contrib&&module:&
model&=&Algorithm::create&FaceRecognizer&(facerecAlgorithm);&
if&(model.empty())&{
&&&&cerr&&&&&ERROR:&The&FaceRecognizer&[&&&&&facerecA&&&&
cerr&&&&&]&is&not&available&in&your&version&of&OpenCV.&&;&&&&
cerr&&&&&Please&update&to&OpenCV&v2.4.1&or&newer.&&&&&&&&&
一旦我们已加载&FaceRecognizer&算法,我们只是调用&FaceRecognizer::train()&函数与我们收集的脸数据,如下所示︰
//&Do&the&actual&training&from&the&collected&faces.&model-&train(preprocessedFaces,&faceLabels);
这行代码将运行整个人脸识别培训你选定的算法&(例如,脸,Fisherfaces&或其他可能的算法)。如果你有只是少数人的小于&20&的面孔,然后这种培训应该返回速度非常快,但如果你有很多人有很多面孔,它是可能&train()&函数将几秒或甚至几分钟来处理所有数据。
查看所学的知识
虽然它不是必要的它是相当有用,查看时学习培训数据生成人脸识别算法的内部数据结构,特别是如果你理解算法背后的理论你选定并想要验证是否它工作或找到为什么它不工作作为你希望。内部数据结构可以有不同的不同的算法,但幸运的是他们有相同的特征脸和&Fisherfaces,让我们来看看这两种。他们都基于一维特征向量矩阵看来有点像查看作为&2D&图像时的面孔,因此它是共同的请参阅特征向量作为特征脸脸算法在使用时或&fisherfaces&时使用此算法。
简单来说,特征脸的基本原理是它会计算一组特殊图像&(脸)&和混合比率&(特征&#20540;),当结合在不同的方式可以生成每个图像中训练集,但也可以用来区分训练集从彼此对很多人脸图像。例如,如果在训练集的面孔有小胡子和一些则没有,然后会有至少一个的脸显示着胡子,所以培训面临着胡子会有很高混纺比为那脸来表明它有小胡子,和没有胡子的脸会有低配比的特征向量。如果训练集每人有&5&人&20&面,然后会有&100&个特征脸和特征&#20540;来区分训练集,总面临的&100,事实上这些会获得解决,因此第一几特征脸和特征&#20540;会最关键的优势,而且最后几脸与特征&#20540;都只是不能真正帮助区分数据的随机像素噪声。所以它是脸的常见的做法,放弃一些最后并只保留第&50&名左右的脸。
相比较而言,Fisherfaces&的基本原理是,而不是计算特殊特征向量和特征&#20540;在训练集的每个图像,它仅计算一个特殊特征向量和特征&#20540;的每一个人。所以在前面的示例,每人有&5&人具有&20&脸,脸算法将使用&100&个特征脸和特征&#20540;而&Fisherfaces&算法将使用只&5&fisherfaces&和特征&#20540;。
若要访问特征脸和&Fisherfaces&算法的内部数据结构,我们必须使用&cv::Algorithm::get()&函数获取它们在运行时,没有访问到它们在编译时是。所以它们通常存储为浮点数通常介于&0.0&和&1.0,而不是从&0&到&255,类&#20284;于常规图像中像素的&8&位&uchar&像素在内部作为一部分的数学计算,而不是进行图像处理时,使用的数据结构。他们也往往不是&1&D&行或列矩阵或他们弥补许多&1&D&行之一或一个较大的矩阵列。所以你可以显示许多这些内部数据结构之前,你必须重塑他们能够正确的矩形形状,并将它们转换为&8&位&uchar&0&和&255&之间的像素。如矩阵数据可能范围从&0.0&到&1.0&或-1.0&到&1.0&或别的什么,你可以使用&cv::normalize()&函数与&cv::NORM_MINMAX&选项以确保它输出数据介于&0&和&255&无论什么的输入的范围。让我们创建一个函数来执行到一个矩形和转换为&8&位像素为我们重塑,如下所示︰
//&Convert&the&matrix&row&or&column&(float&matrix)&to&a&
//&rectangular&8-bit&image&that&can&be&displayed&or&saved.&
//&Scales&the&values&to&be&between&0&to&255.&
Mat&getImageFrom1DFloatMat(const&Mat&matrixRow,&int&height)&{&&&&
//&Make&a&rectangular&shaped&image&instead&of&a&single&row.&&&&
Mat&rectangularMat&=&matrixRow.reshape(1,&height);&&&&
//&Scale&the&values&to&be&between&0&to&255&and&store&them&&&&&
//&as&a&regular&8-bit&uchar&image.&&&&
normalize(rectangularMat,&dst,&0,&255,&NORM_MINMAX,&&&&&&&&&&&&&&&&&&&&&
CV_8UC1);&&&&
为了便于调试&OpenCV&代码,甚至更多,所以当内部调试&cv::Algorithm&数据结构,我们可以使用的&ImageUtils.cpp&和&ImageUtils.h&文件来显示有关&cv::Mat&结构的信息很容易,如下所示︰
Mat&img&=&...;&printMatInfo(img,&&My&Image&);
您将看到类&#20284;于以下内容打印到您的控制台︰
My&Image:&640w480h&3ch&8bpp,&range[79,253][20,58][18,87]&
这告诉你,它是&640&元素宽和&480&高&(就是&640&x&480&图像或&480&×&640&矩阵,取决于你如何看待它),每个像素均为&8&位每个&(即,定期&BGR&的图像)&的三个频道和它显示图像中每个颜色通道的&min&和&max&的&#20540;。
它也是可以通过使用&printMat()&函数而不&printMatInfo()&函数打印图像或矩阵的实际内容。这是用于查看矩阵和多通道浮点矩阵,因为这些都是很难查看对于初学者来说非常方便。ImageUtils&代码大多为&OpenCV&的&C&接口,而且随着时间的推移逐渐包括更多的&c&&#43;&&#43;&接口。最新的版本总是可以在&&上出现。
Average&face
首先计算这两个特征脸和&Fisherfaces&算术平均,所有培训映像,所以他们可以减去平均图像区域从每个面部更好面对识别结果的平均脸。因此,让我们查看从我们的训练集的平均脸。平均的脸被命名意味着在脸和&Fisherfaces&实现中,如下所示︰
Mat&averageFace&=&model-&get&Mat&(&mean&);&
printMatInfo(averageFace,&&averageFace&(row)&);
&//&Convert&a&1D&float&row&matrix&to&a&regular&8-bit&image.&
averageFace&=&getImageFrom1DFloatMat(averageFace,&faceHeight);&
printMatInfo(averageFace,&&averageFace&);&
imshow(&averageFace&,&averageFace);
现在,您应该看到平均人脸图像您屏幕上类&#20284;于以下的&(放大)&图像相结合的一个男人,一个女人和一个婴儿。你也应该看到类&#20284;的文本显示在控制台上︰
averageFace&(row):&ch&64bpp,&range[5.21,251.47]
averageFace:&70w70h&1ch&8bpp,&range[0,255]
该图像会显示下面的屏幕快照所示︰
而&averageFace&是一个矩形图像覆盖全面的范围从&0&到&255&的&8&位像素,注意到那averageFace&(row)&是&64&位浮点数,单列矩阵。
特征&#20540;、&特征脸,和&Fisherfaces&
让我们查看实际的组件&#20540;中特征&#20540;&(作为文本)︰
Mat&eigenvalues&=&model-&get&Mat&(&eigenvalues&);&
printMat(eigenvalues,&&eigenvalues&);&
在这个特征脸中,每一张脸有一个特征&#20540;,因此,如果我们有三人与四张脸,我们得到一个列向量与&12&特征&#20540;排序,从最佳和最坏的如下所示︰
eigenvalues:&1w18h&1ch&64bpp,&range[4.52e&#43;04,2.02836e&#43;06]&
2.03e&#43;06&
1.09e&#43;06&
5.23e&#43;05&
4.04e&#43;05&
2.66e&#43;05&
2.31e&#43;05
1.85e&#43;05&
1.23e&#43;05&
9.18e&#43;04&
7.61e&#43;04&
6.91e&#43;04&
4.52e&#43;04
对于Fisherfaces,只有一个特征&#20540;为每个额外的人,所以如果有四个面每个的我们只是得到三人都是行向量的两个特征&#20540;,如下所示︰
eigenvalues:&2w1h&1ch&64bpp,&range[152.4,316.6]&
若要查看的特征向量&(作为脸或歇的图像),我们必须提取他们作为大特征向量矩阵中的列。由于&OpenCV&和&C/c&&#43;&&#43;&中的数据通常存储在矩阵使用行优先的顺序,这意味着,要提取的列,我们应该使用Mat::clone()函数来确保将连续的数据,否则我们不能重塑数据到一个矩形。一旦我们有一个连续列垫,我们可以显示使用&getImageFrom1DFloatMat()&函数,就像我们做平均脸的特征向量︰
//&Get&the&eigenvectors&
Mat&eigenvectors&=&model-&get&Mat&(&eigenvectors&);&printMatInfo(eigenvectors,&&eigenvectors&);
//&Show&the&best&20&eigenfaces&
for&(int&i&=&0;&i&&&min(20,&eigenvectors.cols);&i&#43;&#43;)&{&&&&
//&Create&a&continuous&column&vector&from&eigenvector&#i.&&&&
Mat&eigenvector&=&eigenvectors.col(i).clone();
Mat&eigenface&=&getImageFrom1DFloatMat(eigenvector,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
faceHeight);&&&&
imshow(format(&Eigenface%d&,&i),&eigenface);&
下图显示了作为图像的特征向量。你可以看到,有四个脸面的三人,有&12&脸&(图左侧)&或两个&Fisherfaces&(右侧)。
注意到Eigenfaces和&Fisherfaces&&#20284;乎有一些面部特征的相&#20284;之处,但他们看起来不像的面孔。这只是因为平均脸被减去他们,所以他们只是显示的差异为每个从平均脸上的脸。编号显示它是,因为他们总是从最显著的特征脸订购到最不重要的脸,如果你有&50&或更多特征脸然后以后的脸经常将只显示随机图像噪声,因此应该被丢弃的脸。
第&4&步︰面孔识别
现在,我们已经培训了特征脸或&Fisherfaces&的机器学习算法与我们一整套训练图像和脸标签的人脸识别,我们准备最后想出一个人是谁,只是从人脸图像&!这最后一步称为人脸识别和人脸识别。
面孔识别︰&从他们的脸中认识到人们
由于&OpenCV&的&FaceRecognizer&类,我们可以确定在一张照片的人只是通过调用&FaceRecognizer::predict()&函数对人脸图像,如下所示︰
int&identity&=&model-&predict(preprocessedFace);
此标识&#20540;将是我们最初使用时收集培训面临的标签数量。例如,0&为第一人称,1&为第二人称,等等。带有此标识的问题是人的,它总是会预测给定的人,即使输入的照片是人的一个未知或一辆车。它还会告诉你哪些人是最有可能的人在那张照片,所以就很难信任结果&!解决的办法是获得信心的度量,所以我们可以判断结果是如何可靠,和如果它&#20284;乎信心是太低的然后我们假设它是一个未知的人。
面对验证︰&验证它要求的人
要确认如果预测的结果是可靠的或是否应为不详之人,我们执行脸验证&(也称为人脸验证),获得信任度量显示单幅人脸图像是否相&#20284;的索赔人&(而不是人脸识别,我们只是进行,比较单幅人脸图像与很多人)。
OpenCV&的&FaceRecognizer&类可以返回信心指标。我们将使用的方法是使用特征向量和特征&#20540;,面部图像重建和比较这重建的图像与输入图像。如果人有很多他们列入训练集的脸,然后重建应该工作得很好从学特征向量和特征&#20540;,但是如果人没有任何的面孔在训练集&(或没有任何包含类&#20284;照明和面部表情作为测试图像),然后重建的脸会看非常不同于输入的脸信号,它可能不熟悉的面孔。
还记得我们刚才所说的特征脸和&Fisherfaces&算法基于图像可以大致为代表,作为一套&(特别人脸图像)&的特征向量和特征&#20540;&(混合比率)&的概念。所以假如我们结合的训练集的面孔之一特征&#20540;的特征向量,我们就应获得相当密切那原始的训练图像的副本。这同样适用与其他类&#20284;的训练集的图像&—&—&如果我们结合从类&#20284;的测试图像特征&#20540;的受过训练的特征向量,我们应该能够重建图像的某种程度上对测试图像副本。
再次,OpenCV&的&FaceRecognizer&类使得很容易生成重构的脸从任何输入的图像,通过使用&subspaceProject()&函数到特征空间投射和&subspaceReconstruct()&函数去从特征空间到图像空间。诀窍就是,我们需要将其从一个浮点行矩阵转换为矩形&8&位图像&(像我们一样显示平均脸和脸时),但我们不想规范化数据,因为它已经是理想的规模,以与原始图像进行比较。如果我们归一化的数据,它会有不同的亮度和对比度从输入图像,它会变得困难,只是通过使用&L2&相对误差比较图像相&#20284;性。这是完成的如下所示︰
//&Get&some&required&data&from&the&FaceRecognizer&model.&
Mat&eigenvectors&=&model-&get&Mat&(&eigenvectors&);&
Mat&averageFaceRow&=&model-&get&Mat&(&mean&);
//&Project&the&input&image&onto&the&eigenspace.&
Mat&projection&=&subspaceProject(eigenvectors,&averageFaceRow,&&&&&&&&&&&&&&&&&&&&
preprocessedFace.reshape(1,1));
//&Generate&the&reconstructed&face&back&from&the&eigenspace.&
Mat&reconstructionRow&=&subspaceReconstruct(eigenvectors,&&&&&&&&&&&&&&&&&&&&
averageFaceRow,&projection);
//&Make&it&a&rectangular&shaped&image&instead&of&a&single&row.
Mat&reconstructionMat&=&reconstructionRow.reshape(1,&&&&&&&&&&&&&&&&&&&&&
faceHeight);
&//&Convert&the&floating-point&pixels&to&regular&8-bit&uchar.&Mat&reconstructedFace&=&
Mat(reconstructionMat.size(),&CV_8U);&
reconstructionMat.convertTo(reconstructedFace,&CV_8U,&1,&0);
下图显示了两个典型重建的面孔。在左手边的脸被重建很好,因为它是从一个已知的人,而在右边脸被严重重建,因为它是从一个未知的人或知道的人,但未知的照明条件︰&面部表情&/&面对的方向。
我们现在可以计算如何类&#20284;此重构的脸是在输入的脸上使用相同的&getSimilarity()&函数,我们先前创建用于比较两个图像,凡小于&0.3&的&#20540;意味着这两个图像都非常相&#20284;。脸,还有一个特征向量,每一张脸,所以重建往往很好地工作,因此我们通常可以使用阈&#20540;为&0.5,但&Fisherfaces&有一个特征向量为每一个人,所以重建不会以及工作,因此它需要一个更高的门槛,说&0.7。这是完成的如下所示︰
similarity&=&getSimilarity(preprocessedFace,&&&&&&&&&&&&&&&&&&&&&&&&&&
reconstructedFace);&
if&(similarity&&&UNKNOWN_PERSON_THRESHOLD)&{&&&&
identity&=&-1;&&&&
//&Unknown&person.&
现在你可以只打印到控制台,身份或用于任何你能想象到你&!记住,这人脸识别方法和此人脸验证方法仅在一定条件下,你训练它的可靠。所以要获得较好的识别精度,您将需要确保每个人的训练集的照明条件、&面部表情和你希望测试的角度涵盖全部。预处理阶段的脸有助于减少一些差异与光照条件下,平面中旋转&(如果人倾斜他们头朝左或右肩膀),但其他差异如出&ofplane&旋转&(如果人变成他们头朝左侧或右侧),它将只工作如果盖在你的训练集。
结尾︰&保存和加载文件
你可能添加命令行方法来处理输入的文件并保存到磁盘,甚至执行人脸检测、&脸预处理和/或作为&web&服务,人脸识别等等。对于这些类型的项目,它是很容易添加所需的功能,通过使用保存和加载&FaceRecognizer&类的函数。你可能还想要保存训练有素的数据,然后将其加载在程序的开始了。
将训练好的模型保存到&XML&或YML文件是很容易的︰
model-&save(&trainedModel.yml&);&
可能也要保存数组预处理的面孔和标签,如果你会想要将更多数据添加到稍后设置的培训。例如,下面是一些示例代码为从文件中加载的受过训练的模型。请注意,您必须指定最初用来创建训练的模型人脸识别算法&(例如&FaceRecognizer.Eigenfaces&或&FaceRecognizer.Fisherfaces)︰
string&facerecAlgorithm&=&&FaceRecognizer.Fisherfaces&;&
model&=&Algorithm::create&FaceRecognizer&(facerecAlgorithm);&
model-&load(&trainedModel.yml&);&&&&
labels&=&model-&get&Mat&(&labels&);&
}&catch&(cv::Exception&&e)&{}&
if&(labels.rows&&=&0)&{&&&&
cerr&&&&&ERROR:&Couldn't&load&trained&data&from&&&&&&&&&&&&&&
&[trainedModel.yml]!&&&&&&&&&
结尾︰&制作好和交互式&GUI&
虽然到目前为止在给出的代码这一章是足够为整个人脸识别系统,仍然需要有方式放置到系统的数据和使用它的方式。很多人脸识别系统的研究将选择理想输入文本文件清单的静态图像文件在计算机上,以及其他重要数据如真正名称或有关的人的身份,也许真正的像素坐标的地区&(如地面真实的脸和&#30524;睛的中心实际上在哪里)&脸上的存储位置。这将也收集手动通过另一种人脸识别系统。
理想的输出然后将识别结果与地面真相,对比一个文本文件中,以便统计数字可能结果的比较与其他人脸识别系统的人脸识别系统。然而,作为脸识别系统在这一章专为学习,以及实际的乐趣宗旨,而不是竞争与最新的研究方法,它是有用的易于使用的&GUI&使脸集合,培训和测试,以交互方式从实时摄像头。因此,本节将提供交互式&GUI&提供这些功能。读者预计使用此提供&GUI&来,拿着这本书,或者要为他们自己的目的,修改&GUI&或忽略此&GUI&并设计自己的&GUI&来执行脸识别的技术讨论了为止。因为我们需要&GUI&来执行多个任务,让我们创建一组模式或状态,GUI&会与按钮或用户更改模式的鼠标点击次数:&
o&启动︰&这种状态加载并初始化数据和网络摄像头。
o&检测︰&这种状态检测的面孔和显示他们与预处理,直到用户单击添加人员按钮。
o&收集︰&这种状态收集面临当前的人,直到用户在窗口中单击任何位置。这也表明每个人的最新面貌。用户单击某一现有人或添加人员按钮,为不同的人收集的面孔。
o&培训︰&在此状态下,所有收集的脸,所有收集到的人的帮助训练系统。
o&识别︰&这包括突出的公认的人和显示信心米。在用户单击的人之一或添加人员按钮,以返回到模式&2&(集合)。
要退出,用户可以打任何时间窗口中的逃出。让我们也添加重新启动新的人脸识别系统,删除所有模式和调试按钮,切换显示额外的调试信息。我们可以创建一个枚举的模式变量来显示当前的模式。
绘图的&GUI&元素
为了在屏幕上显示当前的模式,让我们创建一个函数来轻松地绘制文本。OpenCV&附带&cv::putText()&函数与几个字体和抗锯齿,但它是很难将文本放在你想要的正确位置。幸运的是,也是一个&cv::getTextSize()&函数来计算文本周围的边界框,这样我们就可以创建一个包装函数,以使它更易于放置文本。我们希望能够沿窗口的任何边缘放置文本,请确保它是完全可见,同时还允许放置多个线条或文字而不会相互覆盖彼此相邻文本。所以这里是一个包装器函数,以允许您指定左对齐或右对齐,以及指定顶对齐或底对齐,和返回的边界框,所以我们很容易得出任何角落或窗口的边缘上的多行文本︰
//&Draw&text&into&an&image.&Defaults&to&top-left-justified&
//&text,&so&give&negative&x&coords&for&right-justified&text,&
//&and/or&negative&y&coords&for&bottom-justified&text.&
//&Returns&the&bounding&rect&around&the&drawn&text.&
Rect&drawString(Mat&img,&string&text,&Point&coord,&Scalar&&&&&&&&&&&&&
color,&float&fontScale&=&0.6f,&int&thickness&=&1,&&&&&&&&&&&&
int&fontFace&=&FONT_HERSHEY_COMPLEX);&
现在作为窗口的背景将相机饲料的&GUI&上显示当前的模式,它是很可能,如果我们只是在相机饲料绘制文本,它可能是相机背景相同的颜色&!因此,让我们只是画是除了我们想要绘制的前景文本只是&1&个像素的文本黑色阴影。让我们也画一条线的它,下面的帮助文本,这样用户就知道需要遵循的步骤。这里是如何绘制一些文本,使用&drawString()&函数的示例︰
string&msg&=&&Click&[Add&Person]&when&ready&to&collect&faces.&;&
//&Draw&it&as&black&shadow&&&again&as&white&text.&float&txtSize&=&0.4;&
int&BORDER&=&10;&
drawString(displayedFrame,&msg,&Point(BORDER,&-BORDER-2),&&&&&&&&&&&&
CV_RGB(0,0,0),&txtSize);&
Rect&rcHelp&=&drawString(displayedFrame,&msg,&Point(BORDER&#43;1,&&&&&&&&&&&&
-BORDER-1),&CV_RGB(255,255,255),&txtSize);
以下部分屏幕截图显示模式和信息,在&GUI&窗口,在摄像机图像上叠加的底部︰
我们提到我们要几个&GUI&按钮,所以让我们创建一个函数来绘制一个&GUI&按钮容易,如下所示︰
//&Draw&a&GUI&button&into&the&image,&using&drawString().
//&Can&give&a&minWidth&to&have&several&buttons&of&same&width.&
//&Returns&the&bounding&rect&around&the&drawn&button.&
Rect&drawButton(Mat&img,&string&text,&Point&coord,&&&&&&&&&&&&&&&&
int&minWidth&=&0)&
const&int&B&=&10;&&&&
Point&textCoord&=&Point(coord.x&&#43;&B,&coord.y&&#43;&B);&&&&
//&Get&the&bounding&box&around&the&text.&&&&
Rect&rcText&=&drawString(img,&text,&textCoord,&&&&&&&&&&&&&&&&&&&&&CV_RGB(0,0,0));&&&&
//&Draw&a&filled&rectangle&around&the&text.&&&&
Rect&rcButton&=&Rect(rcText.x&-&B,&rcText.y&–&
rcText.width&&#43;&2*B,&rcText.height&&#43;&2*B);&&&&
//&Set&a&minimum&button&width.&&&&
if&(rcButton.width&&&minWidth)&&&&&&&&
rcButton.width&=&minW&&&&
//&Make&a&semi-transparent&white&rectangle.&&&&
Mat&matButton&=&img(rcButton);&&&&
matButton&&#43;=&CV_RGB(90,&90,&90);&&&&
//&Draw&a&non-transparent&white&border.&&&&
rectangle(img,&rcButton,&CV_RGB(200,200,200),&1,&CV_AA);
&&&&//&Draw&the&actual&text&that&will&be&displayed.&&&&
drawString(img,&text,&textCoord,&CV_RGB(10,55,20));
&&&&return&rcB&
现在,我们创建了几个可点击的&GUI&按钮使用&drawButton()&函数,将始终显示在左上方的图形用户界面,下面的部分屏幕快照中所示︰
正如我们所提到的&GUI&程序有一些它&(作为有限状态机)&之间切换的模式开头的启动模式。我们将为应用变量存储的&m_mode。
启动模式中,我们只需要加载&XML&探测器文件检测的脸和&#30524;睛和初始化网络摄像头,我们已经介绍过。让我们也创建主&GUI&窗口与&OpenCV&将调用每当用户移动或单击他们在我们窗口的鼠标鼠标回调函数。如果相机支持它,也可能需要将相机分辨率设置为东西合理,例如,640&x&480。这是完成的如下所示︰
//&Create&a&GUI&window&for&display&on&the&screen.&
namedWindow(windowName);&
//&Call&&onMouse()&&when&the&user&clicks&in&the&window.&
setMouseCallback(windowName,&onMouse,&0);
//&Set&the&camera&resolution.&Only&works&for&some&systems.&
videoCapture.set(CV_CAP_PROP_FRAME_WIDTH,&640);&
videoCapture.set(CV_CAP_PROP_FRAME_HEIGHT,&480);
//&We're&already&initialized,&so&let's&start&in&Detection&mode.&
m_mode&=&MODE_DETECTION;&
在检测模式中我们要不断检测脸和&#30524;睛,绘制矩形或圆形周围显示检测结果,并显示当前的预处理的脸。事实上,我们希望这些要显示无论哪种模式,我们都在。唯一特别的检测模式是在用户单击添加人员按钮时,它才会改变第二种模式&(集合)。
如果你还记得从检测步骤以前在这一章中,我们检测阶段的产出将是:&
o&Mat&preprocessedFace︰&预处理的脸&(如果检测到的脸和&#30524;睛)&
o&Rect&faceRect︰&检测到人脸区域坐标&
o&点拍摄,rightEye︰&检测到左、&右&#30524;中心的坐标
所以我们应该检查如果预处理的脸被返回并绘制矩形和圆形脸和&#30524;睛周围,如果他们被检测到,如下所示:
bool&gotFaceAndEyes&=&&
if&(preprocessedFace.data)&&&&
gotFaceAndEyes&=&
if&(faceRect.width&&&0)&{&&&&
//&Draw&an&anti-aliased&rectangle&around&the&detected&face.&&&&rectangle(displayedFrame,&faceRect,&CV_RGB(255,&255,&0),&2,&&&&&&&&&&&&&&&&CV_AA);
//&Draw&light-blue&anti-aliased&circles&for&the&2&eyes.&&&&
Scalar&eyeColor&=&CV_RGB(0,255,255);&&&&
if&(leftEye.x&&=&0)&{&&&
//&Check&if&the&eye&was&detected&&&&&&&&
circle(displayedFrame,&Point(faceRect.x&&#43;&leftEye.x,&&&&&&&&&&&&&&&&&&&&
faceRect.y&&#43;&leftEye.y),&6,&eyeColor,&1,&CV_AA);&&&&
}&if&(rightEye.x&&=&0)&{&&&
//&Check&if&the&eye&was&detected&&&&&&&&
circle(displayedFrame,&Point(faceRect.x&&#43;&rightEye.x,&&&&&&&&&&&&&&&&&&&&
faceRect.y&&#43;&rightEye.y),&6,&eyeColor,&1,&CV_AA);&&&&
我们将覆盖当前的预处理的脸在窗口的顶部中心,如下所示︰
int&cx&=&(displayedFrame.cols&-&faceWidth)&/&2;&
if&(preprocessedFace.data)&{&&&&
//&Get&a&BGR&version&of&the&face,&since&the&output&is&BGR.&&&&
Mat&srcBGR&=&Mat(preprocessedFace.size(),&CV_8UC3);&&&&
cvtColor(preprocessedFace,&srcBGR,&CV_GRAY2BGR);
//&Get&the&destination&ROI.&&&&
Rect&dstRC&=&Rect(cx,&BORDER,&faceWidth,&faceHeight);
Mat&dstROI&=&displayedFrame(dstRC);
//&Copy&the&pixels&from&src&to&dst.&&&&
srcBGR.copyTo(dstROI);&
//&Draw&an&anti-aliased&border&around&the&face.&
rectangle(displayedFrame,&Rect(cx-1,&BORDER-1,&faceWidth&#43;2,&&&&&&&&&&&&&&&
faceHeight&#43;2),&CV_RGB(200,200,200),&1,&CV_AA);&
下面的屏幕快照显示处于检测模式时显示的图形用户界面。&预处理的脸显示在顶部居中,并检测人脸和&#30524;睛都标明︰
当用户单击&Add&Person按钮时我们进入收集模式,以发出他们希望开始收集为一个新的人脸的采集模式的信号。正如前面提到的我们只有有限的脸收藏到每秒一张脸,然后只有当它从以前收集的脸上明显改变。而且要记住,我们决定收集不仅是预处理过的脸,也是镜像的预处理过的脸。在收集模式下,我们想要显示每个已知的人最新的脸,让用户通过单击一个人要向其中添加更多的面孔,或单击添加人员按钮要向集合中添加一个新的人。用户必须单击某处中间窗口继续到下一个(训练)模式。
因此,首先我们需要保持在最新的脸上,为每个人收集的引用。通过更新&m_latestFaces&整数数组,它只是存储数组中的每一个人,从大&preprocessedFaces&数组&(那就是,所有的所有的人的脸的集合),我们会这样做。当我们还在该数组中存储镜像的脸上,我们要引用第二最后一脸,不过去。此代码应将追加到添加一张新面孔&(和镜像的脸)&的代码到&preprocessedFaces&数组,如下所示︰
//&Keep&a&reference&to&the&latest&face&of&each&person.&
m_latestFaces[m_selectedPerson]&=&preprocessedFaces.size()&-&2;&
我们只需要记住总是放大或缩小&m_latestFaces&数组,每当一个新的人是添加或删除(例如,由于用户点击&Add&Person按钮)。现在让我们显示最近脸上每个收集的人,在右手边的窗口(都在收集模式和识别模式后),如下所示︰
m_gui_faces_left&=&displayedFrame.cols&-&BORDER&-&faceW&
m_gui_faces_top&=&BORDER;&for&(int&i=0;&i&m_numP&i&#43;&#43;)&{&&&&
int&index&=&m_latestFaces[i];&&&&
if&(index&&=&0&&&&index&&&(int)preprocessedFaces.size())&{&&&&&&&&
Mat&srcGray&=&preprocessedFaces[index];&&&&&&&&
if&(srcGray.data)&{&&&&&&&&&&&&
//&Get&a&BGR&face,&since&the&output&is&BGR.&&&&&&&&&&&&
Mat&srcBGR&=&Mat(srcGray.size(),&CV_8UC3);&&&&&&&&&&&&
cvtColor(srcGray,&srcBGR,&CV_GRAY2BGR);
//&Get&the&destination&ROI&&&&&&&&&&&&
int&y&=&min(m_gui_faces_top&&#43;&i&*&faceHeight,&&&&&&&&&&&&&&&&&&&&}

我要回帖

更多关于 opencv lbp 人脸检测 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信