OpenCV中使用Eigenfaces 或 Fisherfaces进行人脸识别
Translate by Dawn
~~~~~~~~~~~~~~~~~~~~~~翻译的好辛苦~~~~~~~~~~~~~~~~~~
本文将介绍中人脸检测和人脸识别的概念,并提供一个项目,检测人脸,认出他们,当它再次看到他们。人脸识别是一种流行和困难的题目,和许多研究者投入年到人脸识别的字段。所以这里将会解释简单方法的人脸识别,给读者一个良好的开端,如果他们想要探索更复杂的方法。在本文中,我们包括下述内容:
• 人脸检测
• 人脸预处理培训从收集到的面孔
• 人脸识别
• 收尾介绍
人脸识别和人脸检测的脸识别的机器学习算法是把已知的脸一个标签的过程。就像是人类学会认清他们的家庭、 朋友和名人仅仅通过看看他们的脸上,有一台电脑,学会如何识别已知的脸的很多技巧。这通常包括四个主要步骤︰
1.人脸检测︰ 是 (在下面的屏幕快照的中心附近的大矩形) 图像中定位人脸区域的过程。这一步不关心谁的人,只是,它是人的脸。
2.脸预处理︰ 它是调整对人脸图像,看起来更清晰,类似于其他的面孔 (在下面的屏幕快照的顶部中心的小灰度脸) 的过程。
3.收集和了解的面孔︰ 这是 (为每一个人应该承认),节省许多预处理过的面孔,然后学习如何识别它们的过程。
4.人脸识别︰ 它是检查哪一种收集人的过程是最类似于在相机 (右上角的下面的截图上的一个小矩形) 的脸。
请注意,短语人脸识别常常被公众发现人脸 (那就是,人脸检测,作为所述第 1 步),位置,但这本书将使用的人脸识别指的步骤 4 的正式定义和面对检测指第 1 步。
下面的截图说明最后的 WebcamFaceRec 项目,包括一个小的矩形在右上角突出的公认的人。此外注意到旁边的预处理的脸 (一张小脸在标记脸矩形的顶部中心),在这种情况下显示大约 70%的信任栏,它已认识到正确的人的信心。
当前的脸检测技术是相当可靠的现实世界的情况,而当前脸识别技术是在现实世界的情况中使用时不太可靠。例如,很容易找到研究论文显示脸识别准确率 95%以上,但在那些相同的算法测试自己时,你可能经常发现精度是低于 50%。这来自于当前的脸识别技术是非常敏感的确切的条件,在图像中,如照明、 照明和阴影的方向,准确定位的脸,表情的脸和目前的心情的人的类型的事实。如果他们都保持不变,当培训 (采集图像),以及当测试 (从摄像机图像),然后人脸识别应该是行得通,但如果人站到左手边的时候训练,,然后站到右手边的相机在测试时房间里的灯,它可能会很糟糕的结果。所以用于培训的数据集是非常重要的。
预处理 (步骤 2) 旨在减少这些问题,如通过确保的脸总是似乎有类似的亮度和对比度,和也许可以确保的面部特征的脸总是会在相同的位置 (如对齐的眼睛和/或某些职位的鼻子)。预处理阶段好脸将有助于提高可靠性的整个人脸识别系统,所以这一章将一些强调预处理方法的脸上。
尽管在关于人脸识别在媒体中的安全有很大的需求,不太可能独自当前脸识别方法是足够可靠,任何真正的安全系统,但它们可以用于不需要高可靠性,如对不同的人进入一个房间或一个机器人,它说你的名字,当它看见你打个性化的音乐的目的。也有各种实际扩展到性别识别、 年龄识别等情感识别的人脸识别。
第 1 步︰ 人脸检测
直到 2000 年,有许多不同的技术,用于查找的面孔,但他们都是要么很慢,很不可靠,或两者。主要的变化来当中提琴和琼斯发明基于 Haar 的级联分类器为检测对象,2001 年和 2002 年当它提高了 Lienhart 和 Maydt。其结果是既快速对象探测器 (可以在典型的桌面与 VGA 摄像头实时检测人脸) 和可靠 (大约 95%的正面的面孔能够正确检测)。此对象探测器革命性脸识别 (以及一般的机器人和计算机视觉的),作为它最后允许实时人脸检测和人脸识别的领域特别是 Lienhart 自己写来自由与 OpenCV 对象探测器 !它的工作不仅为正面脸但也侧面脸 (称为配置文件的面孔)、 眼、 嘴、 鼻、 公司徽标、 和许多其他的物体。
此对象探测器被延长在 OpenCV v2.0 也使用 LBP 特征检测基于工作由阿、 哈迪德和 Pietikäinen 2006 年,基于 LBP 的探测器可能多次快比基于 Haar 的探测器,并没有许多 Haar 探测器的发牌问题。基于 Haar 的人脸检测的基本思想是,如果你看起来最正面的脸,用眼睛区域应当比的额头和脸颊,暗和用嘴区域应该是暗比脸颊,等等。它通常执行比较像这样来决定,是否它是一张脸,或不是,但它必须做这在每个可能的位置,在图像中,每个可能的脸上,大小,所以事实上它经常数以千计的检查每个图像约 20 的阶段。基于 LBP 的人脸检测的基本思想是类似于基于 Haar 的但它使用直方图的像素强度比较,例如边缘、 角落和平坦的区域。
而不是决定一个人的比较将最好地定义一张脸,这两个哈尔和 LBP-基于人脸检测器可以自动训练来找到从图像,一大套的面孔与信息存储为 XML 文件,以便以后使用。这些级联分类器探测器通常被训练使用至少 1000 独特的脸幅图像和 10,000 非人脸图像 (例如,照片的树木、 汽车和文本),和训练的过程中可以需要很长时间,甚至在多核心桌面上 (通常为 LBP 几个小时 !),但一周为哈尔。幸运的是,OpenCV 带有一些 pretrained 的哈尔和 LBP 探测器,供您使用 !事实上可以检测正面脸、 配置文件 (侧视图) 脸、 眼睛或鼻子,只是通过加载不同的级联分类器 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 faceDetector;
faceDetector.load(faceCascadeFilename);
只是给一个不同的文件名,这就可以加载 Haar 或 LBP 探测器。一个很常见的错误时使用这是提供错误的文件夹或文件名,但根据您的生成环境,load () 方法将要么返回 false 或生成 c + + 异常 (和退出程序,assert 错误)。所以,最好 load () 方法与 try/catch 块包围,要向用户显示一条很好的错误消息,如果出了错。很多初学者跳过检查错误,但至关重要的是要向用户显示帮助消息,当事情未能正确加载,否则你可能会花很长的时间,最终实现东西未加载前调试代码的其他部分。一个简单的错误消息可以显示如下︰
CascadeClassifier faceDetector;
try {
faceDetector.load(faceCascadeFilename);
} catch (cv::Exception e) {}
if ( faceDetector.empty() ) {
cerr << "ERROR: Couldn't load Face Detector (";
cerr << faceCascadeFilename << ")!" << endl;
exit(1);
}
访问摄像头
从电脑摄像头或者从一个视频文件捕捉视频帧,你可以简单地调用 VideoCapture::open() 函数与相机数量或视频文件名,然后抓帧使用 c + + 流的运算符,如访问章 1、 Cartoonifier 和皮肤改变摄像头的 Android 一节中提到。
使用 Haar 或 LBP 分类器检测对象
现在我们已经加载了 (只是一次在初始化过程中) 的分类,我们可以使用它在每个新的相机帧中检测人脸。但首先我们应该做一些初始图像处理的相机只是为人脸检测,通过执行以下步骤︰
1.灰度颜色转换︰ 仅适用于灰度图像的人脸检测。所以我们应该将彩色相机帧转换为灰度。
2.摄像机图像收缩︰ 人脸检测的速度取决于输入的图像 (它是非常慢的大图像但快速小图像),大小和尚未检测仍然是相当可靠的即使在低分辨率。所以我们应相机图像缩小到一个更合理的大小 (或使用较大的值在探测器,minFeatureSize,如稍后再作解释)。
3.直方图均衡化︰ 是不可靠在低光照条件下的人脸检测。所以我们应该执行直方图均衡化。
提高对比度和亮度
我们可以轻松地将 RGB 彩色图像转换为灰度使用 cvtColor() 函数的灰度颜色转换。但当我们知道我们有一种彩色图像,我们才应该做这 (那就是,它不是一个灰度相机),和我们必须在手机上指定的 (在桌面上通常 3 通道 BGR) 或 4 通道 BGRA 我们输入图像格式。所以,我们应让三种不同的输入的颜色格式,如下面的代码所示︰
Mat gray;
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.
gray = img;
}
摄像机图像收缩
我们可以使用 resize() 函数将图像缩小到一定大小或规模的因素。人脸的检测通常很好适合任何图像大小大于 240 × 240 像素 (除非您需要检测人脸,相机离太远了),因为它将查找任何面孔大于 minFeatureSize (通常 20 x 20 像素为单位)。因此,让我们缩小相机图像为 320 像素宽;如果输入是一个 VGA 摄像头或 5 万像素高清摄像头都没关系。它也是重要的是记住和扩大检测结果,因为如果缩小图像中检测人脸然后结果也会被缩小。请注意,而不是减少输入的图像,你可以使用一个大型 minFeatureSize 值在探测器相反。我们还必须确保图像不会成为胖或瘦。例如,当收缩到 300 x 200 宽屏 800 x 400 图像会让人看起来瘦。所以我们必须保持纵横比 (宽度与高度的比值) 的输出作为输入相同。让我们计算多少要缩小图像宽度,然后适用高度相同的比例因子,以及,具体如下︰
const int DETECTION_WIDTH = 320;
// Possibly shrink the image, to run much faster.
Mat smallImg;
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 = img;
}
直方图均衡化
我们可以轻松地执行直方图均衡化,提高对比度和亮度的图像,使用 equalizeHist() 函数的直方图均衡化 (学习 OpenCV 所述︰ 计算机视觉与 OpenCV 库)。有时这会使图像看起来很奇怪,但在一般情况下它应该提高亮度和对比度和帮助的人脸检测。EqualizeHist() 函数使用方法如下︰
// Standardize the brightness & contrast, such as
// to improve dark images.
Mat equalizedImg;
equalizeHist(inputImg, equalizedImg);
检测人脸
现在,我们已经将图像转换为灰度,缩小图像和直方图将比分扳平,我们准备使用 CascadeClassi fier::detectMultiScale() 函数的脸检测 !有许多我们传递给此函数的参数:
• minFeatureSize︰ 此参数确定最小脸大小,我们关心的事情,通常 20 × 20 或 30 × 30 像素,但这取决于你使用案例和图像大小。如果您正在执行的人脸检测对摄像头或智能手机,脸上总是将非常接近于相机,你可以放大,这对 80 x 80 有更快的检测,或如果您想要远脸检测,如在海滩上与朋友,然后离开这为 20 × 20。
• searchScaleFactor: 参数确定如何许多不同大小的面孔去寻找;通常它会很好的检测,为 1.1 或 1.2 经常找不到脸的快速检测。
• minNeighbors︰ 此参数确定如何确保探测器应已检测到一张脸,通常值为 3,但您可以设置它高如果你想更可靠的面孔,即使许多面孔不检测。
• 标志︰ 此参数允许您指定是否要为所有的脸 (默认值),或只看最大的脸 (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
faceDetector.detectMultiScale(img, faces, searchScaleFactor,
minNeighbors, flags, minFeatureSize);
我们可以看到是否任何面孔被检测到存储在我们的矩形的向量中元素的数量来看,这是通过使用 objects.size() 函数。
正如提到的早些时候,如果我们能缩小的图像给人脸检测,结果会也被缩小,所以我们需要将其放大,如果我们想要知道对原始图像的人脸区域。我们还需要扮鬼脸确定图像的边界上完全保持在范围内的图像,如 OpenCV 将现在引发异常如果发生这种情况,下面的代码所示︰
// Enlarge the results if the image was temporarily shrunk.
if (img.cols > scaledWidth) {
for (int i = 0; i < (int)objects.size(); i++ ) {
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++ ) {
if (objects[i].x < 0)
objects[i].x = 0;
if (objects[i].y < 0)
objects[i].y = 0;
if (objects[i].x + objects[i].width > img.cols)
objects[i].x = img.cols - objects[i].width;
if (objects[i].y + objects[i].height > img.rows)
objects[i].y = img.rows - objects[i].height;
}
请注意,上面的代码将寻找所有的脸在图像中,但如果你只在乎约一张脸,然后你可以更改标志变量,如下所示︰
Int flags = CASCADE_FIND_BIGGEST_OBJECT |
CASCADE_DO_ROUGH_SEARCH;
WebcamFaceRec 项目包括 OpenCV 的 Haar 或 LBP 探测器,能容易地找到脸上或眼睛内的图像。例如︰
Rect faceRect; // 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!" << endl;
现在,我们有一个脸矩形,我们可以使用它在许多方面,如要提取或作物从原始图像的人脸图像。下面的代码允许我们访问的脸︰
// Access just the face within the camera image.
Mat faceImg = cameraImg(faceRect);
下图显示了典型的矩形区域,给出了通过人脸检测︰
第 2 步︰ 脸预处理
作为前面提到的人脸识别是极易受到照明条件、 所面对的方位、 面部表情和等等的变化,所以它是非常重要的是减少这些差异尽可能多地。否则人脸识别算法往往会认为之间面临的两个不同的人在同样的条件比同一个人的两个面之间有更多的相似性。
脸预处理的最简单形式只是将使用 equalizeHist() 函数,像我们只是做了人脸检测的直方图均衡化。这可能是足够的一些项目在哪里的照明和位置的条件不会改变太多。但在现实条件下的可靠性,我们需要很多先进的技术,包括人脸特征检测 (例如,检测眼睛、 鼻子、 嘴和眉毛)。为简单起见,本章将只需使用眼检测,并忽略其他面部的功能,如嘴和鼻子,这是不太有用。下图显示了典型的预处理脸,使用本节中将覆盖技术放大的视图︰
眼检测
眼检测可以是非常有用的脸预处理,因为对正面人脸,你总是可以假定一个人的眼睛应该是水平和在相反位置的脸上,应该有一个相当标准的位置和大小在脸上,尽管表情、 光照条件、 距离到相机,相机属性的变化,等等。它也是有用放弃误报,当人脸检测说它已检测到脸上和实际上是别的东西。它是罕见的人脸检测和两眼探测器将所有上当在相同的时间,所以如果你只处理与检测的人脸图像和两个检测到的眼睛然后它不会有很多误报 (但亦会更少的脸进行处理,如眼检测器不能用作经常作为人脸检测)。
一些跟 OpenCV v2.4 pretrained 的眼探测器可以检测眼是否打开或关闭,而有些人只能检测睁开眼睛。
检测到开放或闭合的眼睛的眼探测器如下所示︰
• haarcascade_mcs_lefteye.xml (and haarcascade_mcs_righteye.xml)
• haarcascade_lefteye_2splits.xml (and haarcascade_ righteye_2splits.xml)
仅检测睁开眼睛的眼探测器如下︰
• haarcascade_eye.xml
• haarcascade_eye_tree_eyeglasses.xml
如开或闭眼探测器指定哪只眼睛,他们受过培训,您需要使用不同的检测器为左和右眼,而探测器睁开的双眼可以使用相同的探测器为左或右的眼睛。如该人戴眼镜,但并不是可靠的如果他们不戴眼镜,探测器 haarcascade_eye_tree_eyeglasses.xml 可以检测到眼睛。如果 XML 文件名说"左的眼",它意味着实际的左的眼的人,所以在相机的形象它通常出现在右手边的脸上,不在左手边 !列表中提到的四个眼探测器被排名近似从到的顺序最可靠最不可靠的因此如果你知道你不需要去找戴着眼镜的人然后第一个探测器可能是最好的选择。
眼搜索区域
对于眼睛检测,它是重要作物对输入的图像只是显示近似眼区域,就像做的人脸检测,然后裁剪到只是一个小的矩形的左的眼应该在的地方 (如果你使用的左的眼探测器) 和右侧的矩形右眼探测器相同。如果你只是做眼检测整张脸或整个照片上的,然后将速度更慢且不可靠。不同眼探测器是更适合于不同地区的脸上,例如,haarcascade_eye.xml 探测器工作最好如果它仅搜索在很紧的区域,在真正的眼睛,而 haarcascade_mcs_lefteye.xml 和 haarcascade_lefteye_2splits.xml
探测器最佳工作时眼部周围的一个大区域。下表列出了使用相对坐标检测人脸矩形内的不同眼探测器 (当使用 LBP 脸检测器) 的时候,脸上一些好的搜索区域︰
在这里是要提取检测人脸的左眼和右眼的地区的源代码︰
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));
下图显示了不同眼探测器,haarcascade_eye.xml 和 haarcascade_eye_tree_eyeglasses.xml 在哪里最好与小小的搜索区域,而 haarcascade_mcs_*eye.xml 和 haarcascade_*eye_2splits.xml 是最好的与更大的搜索区域的理想的搜索区域。请注意,检测人脸矩形也显示,以便了解如何大眼搜索区域被比作检测人脸矩形︰
当使用上表中给出的眼搜索区域,这里是不同眼探测器的近似检测属性︰
* 可靠性值表明多久两只眼睛会检测后 LBP 正面人脸检测时没有眼镜的磨损和两只眼睛都开放。如果闭上眼睛然后可靠性可能会下降,或如果戴眼镜那么可靠性和速度会降低。
* * 速度值是以毫秒为单位的图像缩放到的英特尔酷睿 i7 处理器 320 x 240 像素大小 2.2 GHz (平均值 1000 张照片)。速度通常快很多时如果未发现的眼睛,因为它必须扫描整个图像,但 haarcascade_mcs_lefteye.xml 是仍远低于其他眼探测器比发现的眼睛。
例如,如果你收缩到 320 x 240 像素的照片,对它进行直方图均衡化,使用 LBP 正面人脸检测器可以得到一张脸,然后提取的左眼部和右眼部从脸上使用 haarcascade_mcs_ lefteye.xml 的值,然后对每个眼区域执行直方图均衡化。然后如果你在左边 haarcascade_mcs_lefteye.xml 探测器眼 (这实际上是对您的图像的顶部右侧) 和使用 haarcascade_mcs_ righteye.xml 探测器上右眼 (你图像的顶部左侧部分),每个眼探测器应该工作在大约 90%的照片 LBP 检测正面面。所以如果你想发现的两只眼睛然后它应该工作在大约 80%的 LBP 检测正面脸的照片。
注意,虽然它建议缩小相机图像检测的脸前,你应检测在充分摄像机分辨率的眼睛,因为眼睛显然会比脸,小得多,所以你仍然需要尽可能大的分辨率,你可以得到。
基于表格,似乎当选择眼检测器使用,你应该决定是否要检测闭着眼睛或只睁开眼睛。并请记住,您甚至可以使用一个眼检测器,如果它不能检测眼然后你就可以尝试用另一个。对于很多任务,它是有助于发现的眼睛,无论它们是打开还是关闭,所以如果速度不是关键的最好用 mcs_ 搜索 * 首先,眼探测器,如果它失败然后搜索 eye_2splits 检测器。但人脸识别,人会出现完全不同的如果他们的眼睛闭着,所以,最好首先,搜索与平原 haarcascade_eye 探测器,如果它失败,然后搜索 haarcascade_eye_ tree_eyeglasses 检测器。
我们可以使用相同的 detectLargestObject()函数,我们的人脸检测用于搜索的眼睛,但而不是要缩小图像前眼检测,我们指定全眼区域宽度以获得更好的眼睛检测。它很容易使用一个探测器,左眼搜索和如果它失败然后尝试另一种探测器 (为右眼相同)。眼睛检测完成,如下所示︰
CascadeClassifier eyeDetector1("haarcascade_eye.xml");
CascadeClassifier
eyeDetector2("haarcascade_eye_tree_eyeglasses.xml");
...
Rect leftEyeRect; // 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 + leftEyeRect.width/2 + leftX;
leftEye.y = leftEyeRect.y + leftEyeRect.height/2 + topY;
}
// Do the same for the right-eye ...
// Check if both eyes were detected.
if (leftEye.x >= 0 && rightEye.x >= 0) {
...
}
面对与检测到的两只眼睛,我们会执行脸预处理相结合:
• 几何变换和裁剪︰ 这一进程包括缩放、 旋转,和翻译图像,眼睛一致的后删除的额头、下巴、 耳朵和从人脸图像的背景。
• 单独直方图均衡化的左、 右两边︰ 这一进程独立标准化的亮度和对比度两边左边和右边的脸。
• 平滑︰ 此过程可以减少使用双边滤波图像噪声。
• 椭圆掩码︰ 椭圆掩码从人脸图像中移除一些剩余的头发和背景。下图显示了预处理步骤 1 到 4 适用于检测人脸的脸。而原来不,请注意最终的图像如何面对,两侧有了好的亮度和对比度:
几何变换
它是重要的脸上都被整合在一起,否则人脸识别算法可能会比较鼻子与眼睛和等等的一部分的一部分。刚才见到的人脸检测的输出会对齐的面孔在某种程度上,但它不是很准确 (即,脸矩形不总是会开始从额头上的同一点)。为了有更好的协调我们将使用眼检测要对齐的脸,所以两个检测到眼睛的位置完全在排队所需的位置。我们会使用 warpAffine() 的几何变换函数,是一种单一的操作,将做四件事:
• 旋转的脸,两眼的水平。
• 扩展的脸,两只眼睛之间的距离始终不变。
• 翻译的脸,眼睛总是居中水平,所需的高度。
• 作物外部分的脸上,因为我们想要去裁剪图像背景,头发、 额头、 耳朵和下巴。
仿射翘曲将仿射矩阵变换到两个所需的眼睛位置,两个检测到的眼睛位置,然后作物带到所需的大小和位置。若要生成此仿射矩阵,我们将得到中心两眼之间,计算的角的两个检测到的眼睛出现,并看看他们之间的距离,如下所示︰
// Get the center between the 2 eyes.
Point2f eyesCenter;
eyesCenter.x = (leftEye.x + rightEye.x) * 0.5f;
eyesCenter.y = (leftEye.y + 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 + 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 – 0.16);
// 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 – 0.16);
double scale = desiredLen * DESIRED_FACE_WIDTH / len;
现在我们可以face (rotate, scale, and translate),拿这两个检测到眼睛要一张理想的脸所需的眼职位如下︰
// 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
rot_mat.at
// 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());
单独的直方图均衡化的左、 右两边在现实世界的情况,是很常见的一个上具有较强的照明的脸和另一方面的微弱灯光照明的一半。这已极大地影响了人脸识别算法,为同一张脸的左边和右边双方将看起来像非常不同的人。所以我们会在脸上,在每一边的脸上有标准化的亮度和对比度的左、 右半球分别执行直方图均衡化。如果我们简单地应用直方图均衡化的左半部分,然后再对右半部分,我们会看到非常明显的优势,在中间因为平均亮度很可能是在左边和右边都不同,所以要删除这方面的优势,我们将逐渐从左边两个直方图均衡-或右手边向中心和混合与整个脸直方图均衡化这样远左手边将使用左的直方图均衡化、 远右侧将使用正确的直方图均衡化、 和中心将向左或向右值和整个脸均衡的值光滑混合使用。
下图显示了如何的左均衡、 整体均衡和权利均衡的图像混合在一起︰
要执行该操作,我们需要整张脸以及一半将比分扳平的左和右半部分将比分扳平,将比分扳平,如下所示完成的副本︰
int w = faceImg.cols; int h = faceImg.rows; Mat wholeFace; 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
for (int y=0; y
for (int x=0; x
int v;
if (x < w/4) {
// Left 25%: just use the left face.
v = leftSide.at
} else if (x < w*2/4) {
// Mid-left 25%: blend the left face & whole face.
int lv = leftSide.at
int wv = wholeFace.at
// 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 + (f) * wv);
} else if (x < w*3/4) {
// Mid-right 25%: blend right face & whole face.
int rv = rightSide.at
int wv = wholeFace.at
// 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 + (f) * rv);
} else {
// Right 25%: just use the right face.
v = rightSide.at
}
faceImg.at
}// 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() 函数,通常会设置整个图像为某些像素值,但是,我们会给一个屏蔽图像,它将只设置部分为给定的像素值。所以,它应该有降低对比度到其余的脸上,我们将填补中灰色的图像︰
// 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 张脸看起来几乎一模一样然后它将仍然表现不佳因为它是更重要的是训练集有足够的品种覆盖测试集,而不只是有一大批的面孔。因此,请确保在训练集所面临的不是都太相似,我们应该添加之间每个收集的脸明显的延迟。例如,如果相机运行在 30 帧 / 秒,那么它可能只是几秒钟后,当这个人已经不在收集 100 张脸还没来得及左右移动,所以它是更好的收集只是一脸每秒,而人在附近移动,他们的脸。另一种简单的方法来提高训练集的变化是只收集一张脸,如果它是从以前收集的脸上明显不同。
收集预处理培训的面孔
以确保收集新面孔是存在至少一秒差距的预处理的面孔,需要我们加以衡量多少时间过去了。这是完成的如下所示︰
// Check how long since the previous face was added.
double current_time = (double)getTickCount();
double timeDiff_seconds = (current_time –
old_time) / getTickFrequency();
若要比较两个图像的相似性,逐个像素,你能找到相对的 L2 误差,只是从另一个图像中减去,总结它的平方的值,然后获取它的平方根。所以如果人不是在所有提出,减去当前面对以前脸上的应该在每个像素,给人数很少,但他们刚搬略在任何方向,减去像素会给大量和如此的 L2 误差会很高。作为结果相加的所有像素,值将取决于图像分辨率。所以,得到平均误差我们应该除以此值的图像中的像素总数。让我们把这个方便的功能,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);
return similarity;
}
...
// Check if this face looks different from the previous face.
double imageDiff = MAX_DBL;
if (old_prepreprocessedFaceprepreprocessedFace.data) {
ImageDiff = getSimilarity(preprocessedFace, old_prepreprocessedFace);
}
这种相似性往往会低于 0.2 如果图像没有移动很多,和高于 0.4 如果图像做移动,因此,让我们使用 0.3 作为我们的阈值来收集一张新面孔。
有很多的技巧,我们可以玩来获得更多的培训资料,如使用镜像的面孔、 添加随机噪声、 转移由几个像素的脸上,按百分比缩放脸上或脸上旋转几度,(即使我们专门试图消除这些影响,当预处理脸 !)让我们添加镜像的脸训练集,所以,我们有两个,较大的训练集和减少问题中的不对称的脸,或用户在训练,但不是测试期间一直是面向略向左或向右。这是完成的如下所示︰
// 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 mirroredFace;
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 = preprocessedFace;
old_time = current_time;
}
这将收集 std::vector 阵列 preprocessedFaces 和 faceLabels 为预处理过的脸,以及标签或 ID 号的那个人 (假设它整数 m_selectedPerson 变量中)。要对我们有向集合添加他们当前的脸上的用户进行更明显,您可以提供视觉通知通过任一对整个图像显示一个大的白色矩形或只显示他们的脸上只是一小部分的第二次,使他们意识到一张照片。利用 OpenCV 的 c + + 接口,您可以使用 + 重载 cv::Mat 运算符将值添加到图像中的每个像素,并有它夹到 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 += CV_RGB(90,90,90);
培训人脸识别系统
从收集到的面孔之后您已收集足够让每个人认识的面孔,你必须训练系统学会使用一种适合于人脸识别的机器学习算法的数据。在文献中,最简单的方法是特征脸和人工神经网络有很多不同的人脸识别算法。特征脸往往更好的工作,比人工神经网络,尽管它的简单性,它往往几乎和许多更复杂的人脸识别算法,工作所以它已成为非常流行的作为初学者以及基本的人脸识别算法对于新算法可以相比。任何一位读者如欲工作进一步脸上识别推荐阅读背后的理论:
• Eigenfaces (也被称为主成分分析 (PCA)
• Fisherfaces (也被称为线性判别分析 (LDA)
• 其他经典的人脸识别算法(许多是发售在
http://www.face-rec.org/algorithms/)
• Newer face对最近的计算机视觉研究论文(如视觉和 ICCV 在 http://www.cvpapers.com/) 识别算法那里数以百计的脸识别论文每年出版然而,你不需要去了解这些算法的理论内容,使用它们,在这本书中所示。由于 OpenCV 团队和菲利普·瓦格纳 libfacerec 贡献,OpenCV v2.4.1 提供 cv::Algorithm 作为一种简单通用的方法来执行使用几种不同的算法(在运行时甚至可选) 之一不一定理解它们如何付诸实施的人脸识别。通过算法︰ getList() 功能,如使用此代码,可以在 OpenCV 你版本中找到可用算法︰
vector
Algorithm::getList(algorithms);
cout << "Algorithms: " << algorithms.size() << endl;
for (int i=0; i
cout << algorithms[i] << endl;
}
这里有三种人脸识别算法在 OpenCV v2.4.1:
• FaceRecognizer.Eigenfaces︰ Eigenfaces,也被称为 PCA,特克和彭特兰在 1991 年第一次使用。
• FaceRecognizer.Fisherfaces: Fisherfaces,也被称为 LDA,1997 年发明的 Belhumeur、 Hespanha 和 Kriegman。
• FaceRecognizer.LBPH︰ 本地二进制模式直方图,2004 年由阿、 哈迪德和 Pietikäinen 发明。
这些脸识别算法实现的详细信息可以发现与文档、 示例和 Python 等价物为他们每个人都在菲利普 · 瓦格纳网站上的博客 http://bytefish.de/ 和 http://bytefish.de/dev/libfacerec/ 等。
这些人脸识别算法都可以通过 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 << endl; exit(1);
}
若要使用人脸识别算法之一,我们必须创建一个 FaceRecognizer 对象,使用 cv::Algorithm::create
string facerecAlgorithm = "FaceRecognizer.Fisherfaces";
Ptr
// Use OpenCV's new FaceRecognizer in the "contrib" module:
model = Algorithm::create
if (model.empty()) {
cerr << "ERROR: The FaceRecognizer [" << facerecAlgorithm;
cerr << "] is not available in your version of OpenCV. ";
cerr << "Please update to OpenCV v2.4.1 or newer." << endl;
exit(1);
}
一旦我们已加载 FaceRecognizer 算法,我们只是调用 FaceRecognizer::train() 函数与我们收集的脸数据,如下所示︰
// Do the actual training from the collected faces. model->train(preprocessedFaces, faceLabels);
这行代码将运行整个人脸识别培训你选定的算法 (例如,脸,Fisherfaces 或其他可能的算法)。如果你有只是少数人的小于 20 的面孔,然后这种培训应该返回速度非常快,但如果你有很多人有很多面孔,它是可能 train() 函数将几秒或甚至几分钟来处理所有数据。
查看所学的知识
虽然它不是必要的它是相当有用,查看时学习培训数据生成人脸识别算法的内部数据结构,特别是如果你理解算法背后的理论你选定并想要验证是否它工作或找到为什么它不工作作为你希望。内部数据结构可以有不同的不同的算法,但幸运的是他们有相同的特征脸和 Fisherfaces,让我们来看看这两种。他们都基于一维特征向量矩阵看来有点像查看作为 2D 图像时的面孔,因此它是共同的请参阅特征向量作为特征脸脸算法在使用时或 fisherfaces 时使用此算法。
简单来说,特征脸的基本原理是它会计算一组特殊图像 (脸) 和混合比率 (特征值),当结合在不同的方式可以生成每个图像中训练集,但也可以用来区分训练集从彼此对很多人脸图像。例如,如果在训练集的面孔有小胡子和一些则没有,然后会有至少一个的脸显示着胡子,所以培训面临着胡子会有很高混纺比为那脸来表明它有小胡子,和没有胡子的脸会有低配比的特征向量。如果训练集每人有 5 人 20 面,然后会有 100 个特征脸和特征值来区分训练集,总面临的 100,事实上这些会获得解决,因此第一几特征脸和特征值会最关键的优势,而且最后几脸与特征值都只是不能真正帮助区分数据的随机像素噪声。所以它是脸的常见的做法,放弃一些最后并只保留第 50 名左右的脸。
相比较而言,Fisherfaces 的基本原理是,而不是计算特殊特征向量和特征值在训练集的每个图像,它仅计算一个特殊特征向量和特征值的每一个人。所以在前面的示例,每人有 5 人具有 20 脸,脸算法将使用 100 个特征脸和特征值而 Fisherfaces 算法将使用只 5 fisherfaces 和特征值。
若要访问特征脸和 Fisherfaces 算法的内部数据结构,我们必须使用 cv::Algorithm::get() 函数获取它们在运行时,没有访问到它们在编译时是。所以它们通常存储为浮点数通常介于 0.0 和 1.0,而不是从 0 到 255,类似于常规图像中像素的 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.
Mat dst;
normalize(rectangularMat, dst, 0, 255, NORM_MINMAX, CV_8UC1);
return dst;
}
为了便于调试 OpenCV 代码,甚至更多,所以当内部调试 cv::Algorithm 数据结构,我们可以使用的 ImageUtils.cpp 和 ImageUtils.h 文件来显示有关 cv::Mat 结构的信息很容易,如下所示︰
Mat img = ...; printMatInfo(img, "My Image");
您将看到类似于以下内容打印到您的控制台︰
My Image: 640w480h 3ch 8bpp, range[79,253][20,58][18,87]
这告诉你,它是 640 元素宽和 480 高 (就是 640 x 480 图像或 480 × 640 矩阵,取决于你如何看待它),每个像素均为 8 位每个 (即,定期 BGR 的图像) 的三个频道和它显示图像中每个颜色通道的 min 和 max 的值。
它也是可以通过使用 printMat() 函数而不 printMatInfo() 函数打印图像或矩阵的实际内容。这是用于查看矩阵和多通道浮点矩阵,因为这些都是很难查看对于初学者来说非常方便。ImageUtils 代码大多为 OpenCV 的 C 接口,而且随着时间的推移逐渐包括更多的 c + + 接口。最新的版本总是可以在 http://shervinemami.info/openCV.html 上出现。
Average face
首先计算这两个特征脸和 Fisherfaces 算术平均,所有培训映像,所以他们可以减去平均图像区域从每个面部更好面对识别结果的平均脸。因此,让我们查看从我们的训练集的平均脸。平均的脸被命名意味着在脸和 Fisherfaces 实现中,如下所示︰
Mat averageFace = model->get
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);
现在,您应该看到平均人脸图像您屏幕上类似于以下的 (放大) 图像相结合的一个男人,一个女人和一个婴儿。你也应该看到类似的文本显示在控制台上︰
averageFace (row): 4900w1h 1ch 64bpp, range[5.21,251.47]
averageFace: 70w70h 1ch 8bpp, range[0,255]
该图像会显示下面的屏幕快照所示︰
而 averageFace 是一个矩形图像覆盖全面的范围从 0 到 255 的 8 位像素,注意到那averageFace (row) 是 64 位浮点数,单列矩阵。
特征值、 特征脸,和 Fisherfaces
让我们查看实际的组件值中特征值 (作为文本)︰
Mat eigenvalues = model->get
printMat(eigenvalues, "eigenvalues");
在这个特征脸中,每一张脸有一个特征值,因此,如果我们有三人与四张脸,我们得到一个列向量与 12 特征值排序,从最佳和最坏的如下所示︰
eigenvalues: 1w18h 1ch 64bpp, range[4.52e+04,2.02836e+06]
2.03e+06
1.09e+06
5.23e+05
4.04e+05
2.66e+05
2.31e+05
1.85e+05
1.23e+05
9.18e+04
7.61e+04
6.91e+04
4.52e+04
对于Fisherfaces,只有一个特征值为每个额外的人,所以如果有四个面每个的我们只是得到三人都是行向量的两个特征值,如下所示︰
eigenvalues: 2w1h 1ch 64bpp, range[152.4,316.6]
317, 152
若要查看的特征向量 (作为脸或歇的图像),我们必须提取他们作为大特征向量矩阵中的列。由于 OpenCV 和 C/c + + 中的数据通常存储在矩阵使用行优先的顺序,这意味着,要提取的列,我们应该使用Mat::clone()函数来确保将连续的数据,否则我们不能重塑数据到一个矩形。一旦我们有一个连续列垫,我们可以显示使用 getImageFrom1DFloatMat() 函数,就像我们做平均脸的特征向量︰
// Get the eigenvectors
Mat eigenvectors = model->get
// Show the best 20 eigenfaces
for (int i = 0; i < min(20, eigenvectors.cols); i++) {
// 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 似乎有一些面部特征的相似之处,但他们看起来不像的面孔。这只是因为平均脸被减去他们,所以他们只是显示的差异为每个从平均脸上的脸。编号显示它是,因为他们总是从最显著的特征脸订购到最不重要的脸,如果你有 50 或更多特征脸然后以后的脸经常将只显示随机图像噪声,因此应该被丢弃的脸。
第 4 步︰面孔识别
现在,我们已经培训了特征脸或 Fisherfaces 的机器学习算法与我们一整套训练图像和脸标签的人脸识别,我们准备最后想出一个人是谁,只是从人脸图像 !这最后一步称为人脸识别和人脸识别。
面孔识别︰ 从他们的脸中认识到人们
由于 OpenCV 的 FaceRecognizer 类,我们可以确定在一张照片的人只是通过调用 FaceRecognizer::predict() 函数对人脸图像,如下所示︰
int identity = model->predict(preprocessedFace);
此标识值将是我们最初使用时收集培训面临的标签数量。例如,0 为第一人称,1 为第二人称,等等。带有此标识的问题是人的,它总是会预测给定的人,即使输入的照片是人的一个未知或一辆车。它还会告诉你哪些人是最有可能的人在那张照片,所以就很难信任结果 !解决的办法是获得信心的度量,所以我们可以判断结果是如何可靠,和如果它似乎信心是太低的然后我们假设它是一个未知的人。
面对验证︰ 验证它要求的人
要确认如果预测的结果是可靠的或是否应为不详之人,我们执行脸验证 (也称为人脸验证),获得信任度量显示单幅人脸图像是否相似的索赔人 (而不是人脸识别,我们只是进行,比较单幅人脸图像与很多人)。
OpenCV 的 FaceRecognizer 类可以返回信心指标。我们将使用的方法是使用特征向量和特征值,面部图像重建和比较这重建的图像与输入图像。如果人有很多他们列入训练集的脸,然后重建应该工作得很好从学特征向量和特征值,但是如果人没有任何的面孔在训练集 (或没有任何包含类似照明和面部表情作为测试图像),然后重建的脸会看非常不同于输入的脸信号,它可能不熟悉的面孔。 还记得我们刚才所说的特征脸和 Fisherfaces 算法基于图像可以大致为代表,作为一套 (特别人脸图像) 的特征向量和特征值 (混合比率) 的概念。所以假如我们结合的训练集的面孔之一特征值的特征向量,我们就应获得相当密切那原始的训练图像的副本。这同样适用与其他类似的训练集的图像 — — 如果我们结合从类似的测试图像特征值的受过训练的特征向量,我们应该能够重建图像的某种程度上对测试图像副本。
再次,OpenCV 的 FaceRecognizer 类使得很容易生成重构的脸从任何输入的图像,通过使用 subspaceProject() 函数到特征空间投射和 subspaceReconstruct() 函数去从特征空间到图像空间。诀窍就是,我们需要将其从一个浮点行矩阵转换为矩形 8 位图像 (像我们一样显示平均脸和脸时),但我们不想规范化数据,因为它已经是理想的规模,以与原始图像进行比较。如果我们归一化的数据,它会有不同的亮度和对比度从输入图像,它会变得困难,只是通过使用 L2 相对误差比较图像相似性。这是完成的如下所示︰
// Get some required data from the FaceRecognizer model.
Mat eigenvectors = model->get
Mat averageFaceRow = model->get
// 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);
下图显示了两个典型重建的面孔。在左手边的脸被重建很好,因为它是从一个已知的人,而在右边脸被严重重建,因为它是从一个未知的人或知道的人,但未知的照明条件︰ 面部表情 / 面对的方向。
我们现在可以计算如何类似此重构的脸是在输入的脸上使用相同的 getSimilarity() 函数,我们先前创建用于比较两个图像,凡小于 0.3 的值意味着这两个图像都非常相似。脸,还有一个特征向量,每一张脸,所以重建往往很好地工作,因此我们通常可以使用阈值为 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
Mat labels;
try {
model->load("trainedModel.yml");
labels = model->get
} catch (cv::Exception &e) {}
if (labels.rows <= 0) {
cerr << "ERROR: Couldn't load trained data from "
"[trainedModel.yml]!" << endl;
exit(1);
}
结尾︰ 制作好和交互式 GUI
虽然到目前为止在给出的代码这一章是足够为整个人脸识别系统,仍然需要有方式放置到系统的数据和使用它的方式。很多人脸识别系统的研究将选择理想输入文本文件清单的静态图像文件在计算机上,以及其他重要数据如真正名称或有关的人的身份,也许真正的像素坐标的地区 (如地面真实的脸和眼睛的中心实际上在哪里) 脸上的存储位置。这将也收集手动通过另一种人脸识别系统。
理想的输出然后将识别结果与地面真相,对比一个文本文件中,以便统计数字可能结果的比较与其他人脸识别系统的人脸识别系统。然而,作为脸识别系统在这一章专为学习,以及实际的乐趣宗旨,而不是竞争与最新的研究方法,它是有用的易于使用的 GUI 使脸集合,培训和测试,以交互方式从实时摄像头。因此,本节将提供交互式 GUI 提供这些功能。读者预计使用此提供 GUI 来,拿着这本书,或者要为他们自己的目的,修改 GUI 或忽略此 GUI 并设计自己的 GUI 来执行脸识别的技术讨论了为止。因为我们需要 GUI 来执行多个任务,让我们创建一组模式或状态,GUI 会与按钮或用户更改模式的鼠标点击次数:
• 启动︰ 这种状态加载并初始化数据和网络摄像头。
• 检测︰ 这种状态检测的面孔和显示他们与预处理,直到用户单击添加人员按钮。
• 收集︰ 这种状态收集面临当前的人,直到用户在窗口中单击任何位置。这也表明每个人的最新面貌。用户单击某一现有人或添加人员按钮,为不同的人收集的面孔。
• 培训︰ 在此状态下,所有收集的脸,所有收集到的人的帮助训练系统。
• 识别︰ 这包括突出的公认的人和显示信心米。在用户单击的人之一或添加人员按钮,以返回到模式 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+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 + B, coord.y + 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 – B,
rcText.width + 2*B, rcText.height + 2*B);
// Set a minimum button width.
if (rcButton.width < minWidth)
rcButton.width = minWidth;
// Make a semi-transparent white rectangle.
Mat matButton = img(rcButton);
matButton += 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 rcButton;
}
现在,我们创建了几个可点击的 GUI 按钮使用 drawButton() 函数,将始终显示在左上方的图形用户界面,下面的部分屏幕快照中所示︰
正如我们所提到的 GUI 程序有一些它 (作为有限状态机) 之间切换的模式开头的启动模式。我们将为应用变量存储的 m_mode。
启动模式
启动模式中,我们只需要加载 XML 探测器文件检测的脸和眼睛和初始化网络摄像头,我们已经介绍过。让我们也创建主 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;
检测模式
在检测模式中我们要不断检测脸和眼睛,绘制矩形或圆形周围显示检测结果,并显示当前的预处理的脸。事实上,我们希望这些要显示无论哪种模式,我们都在。唯一特别的检测模式是在用户单击添加人员按钮时,它才会改变第二种模式 (集合)。
如果你还记得从检测步骤以前在这一章中,我们检测阶段的产出将是:
• Mat preprocessedFace︰ 预处理的脸 (如果检测到的脸和眼睛)
• Rect faceRect︰ 检测到人脸区域坐标
• 点拍摄,rightEye︰ 检测到左、 右眼中心的坐标
所以我们应该检查如果预处理的脸被返回并绘制矩形和圆形脸和眼睛周围,如果他们被检测到,如下所示:
bool gotFaceAndEyes = false;
if (preprocessedFace.data)
gotFaceAndEyes = true;
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 + leftEye.x, faceRect.y + leftEye.y), 6, eyeColor, 1, CV_AA);
} if (rightEye.x >= 0) {
// Check if the eye was detected
circle(displayedFrame, Point(faceRect.x + rightEye.x, faceRect.y + 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+2, faceHeight+2), CV_RGB(200,200,200), 1, CV_AA);
下面的屏幕快照显示处于检测模式时显示的图形用户界面。 预处理的脸显示在顶部居中,并检测人脸和眼睛都标明︰
收集模式
当用户单击 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 - faceWidth;
m_gui_faces_top = BORDER; for (int i=0; i
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 + i * faceHeight, displayedFrame.rows - faceHeight);
Rect dstRC = Rect(m_gui_faces_left, y, faceWidth, faceHeight);
Mat dstROI = displayedFrame(dstRC);
// Copy the pixels from src to dst.
srcBGR.copyTo(dstROI);
}
}
}
我们也想要突出显示的当前人员正在收集,使用红色粗边框围绕他们的脸。这是完成的如下所示︰
if (m_mode == MODE_COLLECT_FACES) {
if (m_selectedPerson >= 0 && m_selectedPerson < m_numPersons) {
int y = min(m_gui_faces_top + m_selectedPerson * faceHeight,
displayedFrame.rows – faceHeight);
Rect rc = Rect(m_gui_faces_left, y, faceWidth, faceHeight); rectangle(displayedFrame, rc, CV_RGB(255, 0, 0), 3, CV_AA);
}
}
下面的部分截图说明典型显示时收集了几人的面孔。用户可以单击任何人在右上角的那个人收集更多的面孔。
培训模式
当最终用户单击窗口中间时,人脸识别算法将开始培训的收集的脸上。但它是重要的是确保有足够的面孔或人收集,否则程序可能会崩溃。一般情况下,这只是需要确保有至少一张脸在训练集 (这意味着至少一人)。但的 Fisherfaces 算法进行比较看起来人,所以如果有少于两人,训练集,它也会崩溃。所以我们必须检查是否选定的人脸识别算法 Fisherfaces。如果是,那么我们需要至少两人的面孔,否则我们需要至少一个人的脸。如果没有足够的数据,然后该程序要追溯到集合模式使用户可以添加更多的面孔,在训练之前。
要检查是否有至少两人收集的面孔,我们可以确保,当用户点击添加人员按钮,一个新的人也只是补充说︰ 如果没有任何空人 (那就是,一个补充说︰ 但不任何尚未收集的面孔的人)。我们可以然后也确保如果有只是两个人,我们正在使用的 Fisherfaces 算法,那么我们必须确保 m_latestFaces 引用集合模式期间成立的最后一个人。m_latestFaces [i] 初始化为-1 时仍没有任何添加到该人的面孔,然后它一旦 0 或更高的那个人的脸上已添加。这是完成的如下所示︰
// Check if there is enough data to train from.
bool haveEnoughData = true;
if (!strcmp(facerecAlgorithm, "FaceRecognizer.Fisherfaces")) {
if ((m_numPersons < 2) || (m_numPersons == 2 && m_latestFaces[1] < 0) ) {
cout << "Fisherfaces needs >= 2 people!" << endl;
haveEnoughData = false;
}
} if (m_numPersons < 1 || preprocessedFaces.size() <= 0 || preprocessedFaces.size() != faceLabels.size()) {
cout << "Need data before it can be learnt!" << endl; haveEnoughData = false; }
if (haveEnoughData) {
// Train collected faces using Eigenfaces or Fisherfaces.
model = learnCollectedFaces(preprocessedFaces, faceLabels, facerecAlgorithm);
// Now that training is over, we can start recognizing!
m_mode = MODE_RECOGNITION;
} else {
// Not enough training data, go back to Collection mode!
m_mode = MODE_COLLECT_FACES;
}
培训可以考虑几分之一秒或它可能需要几秒或甚至几分钟,具体取决于有多少数据收集。一旦完成培训的收集的脸,人脸识别系统会自动输入识别模式。
识别模式
在识别模式中,以便用户知道如何可靠的识别是旁预处理过的脸,显示信心米。如果置信水平高于未知阈值,它将周围绘制一个绿色矩形公认人轻松地显示结果。如果他们点击添加人员按钮或现有的人,会导致程序返回到集合模式之一,用户可以添加更多的面孔,为进一步的培训。现在我们已经获得认可的身分证明和刚才提到作为重构的脸相似之处。若要显示信心米,我们知道 L2 相似度值一般是 0 到 0.5 高信心与 0.5 至 1.0 低对前景充满信心,所以,我们可以只是减去它从 1.0 去 0.0 到 1.0 之间的信任级别。然后我们只是绘制实心的矩形使用置信水平作为比例如下所示︰
int cx = (displayedFrame.cols - faceWidth) / 2;
Point ptBottomRight = Point(cx - 5, BORDER + faceHeight);
Point ptTopLeft = Point(cx - 15, BORDER);
// Draw a gray line showing the threshold for "unknown" people.
Point ptThreshold = Point(ptTopLeft.x, ptBottomRight.y(1.0 - UNKNOWN_PERSON_THRESHOLD) * faceHeight);
rectangle(displayedFrame, ptThreshold, Point(ptBottomRight.x, ptThreshold.y), CV_RGB(200,200,200), 1, CV_AA);
// Crop the confidence rating between 0 to 1 to fit in the bar.
double confidenceRatio = 1.0 - min(max(similarity, 0.0), 1.0);
Point ptConfidence = Point(ptTopLeft.x, ptBottomRight.y confidenceRatio * faceHeight);
// Show the light-blue confidence bar.
rectangle(displayedFrame, ptConfidence, ptBottomRight, CV_RGB(0,255,255), CV_FILLED, CV_AA);
// Show the gray border of the bar.
rectangle(displayedFrame, ptTopLeft, ptBottomRight, CV_RGB(200,200,200), 1, CV_AA);
为了突出的识别的人,我们周围绘制一个绿色矩形他们的脸上,如下所示︰
if (identity >= 0 && identity < 1000) {
int y = min(m_gui_faces_top + identity * faceHeight, displayedFrame.rows - faceHeight);
Rect rc = Rect(m_gui_faces_left, y, faceWidth, faceHeight);
rectangle(displayedFrame, rc, CV_RGB(0,255,0), 3, CV_AA);
}
在顶部中心显示信心米旁边的预处理过的脸和突出显示在右上角的认可的人的识别模式中运行时,以下部分屏幕截图显示典型。
检查和处理鼠标单击
现在,我们有我们绘制的所有 GUI 元素,我们只被需要处理鼠标事件。当我们初始化显示窗口,我们告诉 OpenCV,我们想我们 onMouse 功能鼠标事件回调。我们不在乎鼠标移动,只有鼠标点击,所以首先我们跳过的鼠标事件,不是为鼠标左键单击,如下所示︰
void onMouse(int event, int x, int y, int, void*) {
if (event != CV_EVENT_LBUTTONDOWN)
return;
Point pt = Point(x,y);
... (handle mouse clicks) ...
}
由于我们获得按钮的绘制的矩形绘制它们时,我们只是检查是否鼠标单击的位置是在任何我们按钮区域通过调用 OpenCV 的 inside() 函数。现在我们可以检查我们已经创建的每个按钮。
当用户点击添加人员按钮时,我们只是将 1 添加到 m_numPersons 变量、 分配更多空间在 m_latestFaces 变量中的,选择新的人的集合,和开始收集模式 (无论哪种模式我们原先在)。
但还有一个并发症;为了确保我们每人有至少一张脸,在训练时,我们将只分配空间为一个新的人如果已经没有与零的面孔的人。这将确保我们可以经常检查 m_latestFaces [m_numPersons-1] 来看看是否脸上收集到的每一个人的价值。这是完成的如下所示︰
if (pt.inside(m_btnAddPerson)) {
// Ensure there isn't a person without collected faces.
if ((m_numPersons==0) || (m_latestFaces[m_numPersons-1] >= 0)) {
// Add a new person.
m_numPersons++;
m_latestFaces.push_back(-1);
}
m_selectedPerson = m_numPersons - 1;
m_mode = MODE_COLLECT_FACES;
}
此方法可用于测试其他按钮单击,如切换调试标记,如下所示︰
else if (pt.inside(m_btnDebug)) {
m_debug = !m_debug;
}
但还有一个并发症;为了确保我们每人有至少一张脸,在训练时,我们将只分配空间为一个新的人如果已经没有与零的面孔的人。这将确保我们可以经常检查 m_latestFaces [m_numPersons-1] 来看看是否脸上收集到的每一个人的价值。这是完成的如下所示︰
if (pt.inside(m_btnAddPerson)) {
// Ensure there isn't a person without collected faces.
if ((m_numPersons==0) || (m_latestFaces[m_numPersons-1] >= 0)) {
// Add a new person.
m_numPersons++;
m_latestFaces.push_back(-1);
}
m_selectedPerson = m_numPersons - 1;
m_mode = MODE_COLLECT_FACES;
}
此方法可用于测试其他按钮单击,如切换调试标记,如下所示︰
else if (pt.inside(m_btnDebug)) {
m_debug = !m_debug;
}
要处理的全部删除按钮,我们需要空是我们主回路的本地的各种数据结构 (即,从鼠标事件回调函数不能访问),所以我们更改为删除所有模式,然后我们可以删除里面的主循环的情况下,从一切。我们还必须处理用户单击主窗口 (即,不是一个按钮)。如果他们点击右侧的人之一,然后我们就想要选择该人并更改为集合模式。或者如果他们在主窗口内集合模式中单击,然后我们想要更改为培训模式。这是完成的如下所示︰
else {
// Check if the user clicked on a face from the list.
int clickedPerson = -1;
for (int i=0; i
if (m_gui_faces_top >= 0) {
Rect rcFace = Rect(m_gui_faces_left,
m_gui_faces_top + i * faceHeight, faceWidth, faceHeight);
if (pt.inside(rcFace)) {
clickedPerson = i;
break;
}
}
}
// Change the selected person, if the user clicked a face.
if (clickedPerson >= 0) {
// Change the current person & collect more photos.
m_selectedPerson = clickedPerson;
m_mode = MODE_COLLECT_FACES;
}
// Otherwise they clicked in the center.
else {
// Change to training mode if it was collecting faces.
if (m_mode == MODE_COLLECT_FACES) {
m_mode = MODE_TRAINING;
}
}
}
摘要
本章为您展示了创建一个实时的脸识别应用程序,以足够的预处理,使培训之间的一些差异设置条件和测试设置的条件,只使用基本的算法所需的所有步骤。我们用于人脸检测发现相机图像内,紧接着几种形式的预处理,减少影响不同照明条件下,相机和脸的发展方向和面部表情的脸上一脸的位置。我们然后训练特征脸或 Fisherfaces 的机器学习系统的预处理过的面孔,我们收集和最后我们进行人脸识别,看看谁的人是与提供信心的度量,万一它是一个未知的人脸验证。
而不是提供一个以脱机方式处理图像文件的命令行工具,我们结合成自包含的实时 GUI 程序,以允许立即使用人脸识别系统的所有前面的步骤。你应该能够修改系统的行为为自己的目的,如允许您的计算机,自动登录或如果你有兴趣提高识别可靠性,然后你可以读会议论文关于人脸识别,潜在地提高程序的每一步,直到它是足够可靠,为您的特定需求的最新进展。例如,你可以改善脸预处理阶段,或使用更先进的机器学习算法或甚至更好的脸验证算法,基于方法在 http://www.face-rec.org/和 http://www.cvpapers.com。
引用
• Rapid Object Detection using a Boosted Cascade of Simple Features, P. Viola and M.J. Jones, Proceedings of the IEEE Transactions on CVPR 2001, Vol. 1, pp. 511-518
• An Extended Set of Haar-like Features for Rapid Object Detection, R. Lienhart and J. Maydt, Proceedings of the IEEE Transactions on ICIP 2002, Vol. 1, pp. 900-903
• Face Description with Local Binary Patterns: Application to Face Recognition, T. Ahonen, A. Hadid and M. Pietikäinen, Proceedings of the IEEE Transactions on PAMI 2006, Vol. 28, Issue 12, pp. 2037-2041
• Learning OpenCV: Computer Vision with the OpenCV Library, G. Bradski and A. Kaehler, pp. 186-190, O'Reilly Media.
• Eigenfaces for recognition, M. Turk and A. Pentland, Journal of Cognitive Neuroscience 3, pp. 71-86
• Eigenfaces vs. Fisherfaces: Recognition using class specific linear projection, P.N. Belhumeur, J. Hespanha and D. Kriegman, Proceedings of the IEEE Transactions on PAMI 1997, Vol. 19, Issue 7, pp. 711–720
• Face Recognition with Local Binary Patterns, T. Ahonen, A. Hadid and M. Pietikäinen, Computer Vision - ECCV 2004, pp. 469–48
附:原书名:Mastering OpenCV with Practical Computer Vision Projects