赞
踩
计算机视觉使很多任务成为现实,其中两项任务就是人脸检测
(在图像中定位人脸)和人脸识别(将人脸识别为特定的人)。
OpenCV实现了一些人脸检测和识别的算法。从安全到娱乐,这些技术
在现实环境中都有应用。
本章介绍OpenCV的一些人脸检测和识别功能,并定义特定类型的
可跟踪物体的数据文件。具体来说,将研究Haar级联分类器,通过分
析相邻图像区域之间的对比度,确定给定图像或子图像是否与已知类
型匹配。我们来考虑如何在层次结构中组合多个Haar级联分类器,以
便用一个分类器识别父区域(就我们的目标而言,是一张人脸),用
其他分类器识别子区域(比如眼睛)。
我们还介绍了“矩形”这个不起眼但却很重要的主体。通过绘
制、复制及调整矩形图像区域的大小,我们可以对正在跟踪的图像区
域执行简单的操作。
本章将介绍以下主题:
·理解Haar级联。
·找到OpenCV自带的预训练Haar级联,包括了一些人脸检测器。
·利用Haar级联检测静态图像和视频中的人脸。
·采集图像训练和测试人脸检测器。
·使用多种不同的人脸识别算法:Eigenface、Fisherface以及局部
二值模式直方图(Local Binary Pattern Histogram,LBPH)。
·将矩形区域从一幅图像复制到另一幅图像,即可包括也可不包
括掩模。
·使用深度摄像头基于深度区分人脸和背景。
·在交互式应用程序中交换两个人的脸。
本章结束时,我们将人脸跟踪和矩形操作集成到我们在前几章中
开发的交互式应用程序Cameo中。最后,我们将会有一些人脸到人脸的
交互!
5.1 技术需求
本章使用了Python、OpenCV以及NumPy。作为OpenCV的一部分,使
用了可选的opencv_contrib模块,其中包括人脸识别功能。本章的某
些部分使用了OpenCV对OpenNI 2的可选支持来从深度摄像头捕捉图
像。安装说明请参阅第1章。
本章的完整代码可以在本书的GitHub库
(https://github.com/PacktPublishing/Learning-OpenCV-4-
Computer-Vision-with-Python-Third-Edition)的chapter05文件夹
中找到。示例图像在本书GitHub库的images文件夹中。
5.2 Haar级联的概念化
在谈到分类物体并跟踪其位置时,我们到底想要探究什么呢?构
成物体的可识别部分的是什么?
即使来自网络摄像头的摄影图像,也可能包含很多赏心悦目的细
节。但是,因为光线、视角、视觉距离、摄像头抖动和数字噪声的变
化,图像细节往往不稳定。此外,即使物理细节上的真实差异也不可
能会引起我们对分类的兴趣。约瑟夫·豪斯(本书作者之一)在学校
学过,在显微镜下,没有两片雪花看起来是一样的。幸运的是,作为
一个加拿大的孩子,他已经学会了不用显微镜就能识别雪花,因为雪
花在整体上的相似之处更明显。
因此,抽象图像细节的一些方法有助于产生稳定的分类和跟踪结
果。这些抽象称为特征,据说是从图像数据中抽象的。尽管任何像素
都可能影响多个特征,但是特征应该比像素少。把一组特征表示为一
个向量,可以根据图像的对应特征向量之间的距离来度量两幅图像之
间的相似程度。
类Haar特征是应用于实时人脸检测的常用特征之一。在论文
“Robust Real-Time Face Detection”(International Journal of
Computer Vision 57(2),137–154,Kluwer Academic
Publishers,2001)中,作者Paul Viola和Michael Jones首次将类
Haar特征用于人脸检测。可以在
http://www.vision.caltech.edu/html-files/EE148-2005-
Spring/pprs/viola04ijcv.pdf处找到这篇论文的电子版。每个类Haar
特征描述了相邻图像区域之间的对比度模式。例如,边、顶点和细线
都生成了一种特征。有些特征是独特的,因为这些特征通常出现在某
一类对象(如人脸)上,而不会出现在其他对象上。可以把这些特征
组织成一个层次结构,称为级联,其中最高层包含最显著的特征,使
分类器能够快速拒绝缺乏这些特征的主体。
对于任意给定的主体,特征可能会根据图像大小和正在评估对比
度的邻域大小而有所不同。正在评估对比度的邻域大小称为窗口大
小。为使Haar级联分类器尺度不变,或者对尺度变化具有鲁棒性,窗
口大小应保持不变,但是将图像重新缩放多次,因此在某种程度上缩
放时对象(如人脸)大小可能匹配窗口的大小。原始图像和缩放图像
一起称为图像金字塔,图像金字塔中的每个连续的层都是一幅更小的
缩小图像。OpenCV提供了一个尺度不变的分类器,可以以一种特定的
格式从XML文件加载级联分类器。这个分类器在内部将任意给定的图像
转换为图像金字塔。
用OpenCV实现的Haar级联分类器对旋转角度或者透视图的变化并
不鲁棒。例如,认为倒立的人脸和正立的人脸不一样,认为侧面看的
脸和正面看的脸不一样。通过考虑图像的多种变换以及多个窗口大
小,更复杂、资源更密集的实现可以提升Haar级联对旋转角度的鲁棒
性。但是,我们将只介绍OpenCV的实现。
5.3 获取Haar级联数据
OpenCV 4源代码或者安装的OpenCV 4预包构建,应该包含名为
data/haarcascades的子文件夹。如果无法找到这个文件夹,请回到第
1章获取OpenCV 4的源代码说明。
data/haarcascades文件夹包含可以由名为
cv2.CascadeClassifier的OpenCV类加载的XML文件。该类的实例把给
定的XML文件解释为Haar级联,为某种类型的物体(如人脸)提供一个
检测模型。cv2.CascadeClassifier可以检测任意图像中的这种类型的
物体。通常,我们可以从文件中获取静态图像,或者从视频文件或视
频摄像头获取一系列帧。
找到data/haarcascades后,在其他地方为项目创建一个目录。在
这个文件夹中创建名为cascades的子文件夹,把下面的文件从
data/haarcascades复制到cascades:
顾名思义,这些级联就是用来跟踪脸和眼睛的。它们需要是观察
对象正面、直立的视图。稍后,在建立人脸检测器时将会用到这些级
联。
如果你想知道如何生成这些级联文件,请参阅约瑟夫·豪斯
的OpenCV 4 for Secret Agents [1] (原书于2019年由Packt出版社出版)中
第3章。只要有足够的耐心和一台强大的计算机,你就可以自己创建级
联,并针对各种类型的对象对创建的级联进行训练。
[1] 本书第2版的中文版《OpenCV项目开发实战(原书第2版)(ISBN
978-7-111-65234-2)已于2020年由机械工业出版社出版。——编辑注
5.4 使用OpenCV进行人脸检测
不论是在静态图像还是在视频回传信号上进行人脸检测,
cv2.CascadeClassifier几乎没有任何区别。视频只是连续的静态图
像:视频中的人脸检测只是将人脸检测应用于每一帧。自然,有了更
先进的技术,就可以在多帧中连续跟踪检测到的人脸,并确定每一帧
中的人脸是否相同。但是,最好知道基本的顺序方法也是有效的。
我们来检测一些人脸。
5.4.1 在静态图像上进行人脸检测
进行人脸检测的第一个也是最基本的方法是加载一幅图像并检测
其中的人脸。为了使结果在视觉上有意义,我们将在原始图像中的人
脸周围绘制矩形。请记住,人脸检测器是针对直立的正面人脸设计
的,我们将使用有多人(伐木工)站成一排的图像,他们肩并肩站
立、面对镜头。
将Haar级联XML文件复制到级联文件夹后,我们创建下列基本脚本
来执行人脸检测:
我们逐步浏览一下前面的代码。首先,使用必要的cv2导入,本书
的每个脚本都有这个导入。然后,声明一个face_cascade变量,这是
一个CascadeClassifier对象,用于加载人脸检测级联:
然后,用cv2.imread加载图像文件,将其转换成灰度图像,因为
Cascade Classifier需要灰度图像。下一步,用
face_cascade.detectMultiScale进行实际的人脸检测:
detectMultiScale的参数包括scaleFactor和minNeighbors。
scaleFactor参数应该大于1.0,确定人脸检测过程中每次迭代时图像
的降尺度比率。正如5.2节介绍的,这种降尺度的目的是通过把不同的
人脸与窗口大小匹配实现尺度不变性。minNeighbors参数是为了保留
检测结果所需要的最小重叠检测次数。通常,我们期望可以在多个重
叠窗口中检测到某人脸,更多的重叠检测使我们更加确信检测到的人
脸是一个真正的人脸。
从检测操作返回的值是一个表示人脸矩形的元组列表。OpenCV的
cv2.rectangle函数允许我们在指定的坐标处绘制矩形。x和y分别表示
左边坐标和顶部坐标,w和h分别表示人脸矩形的宽度和高度。通过循
环遍历faces变量,在所有人脸周围绘制蓝色矩形,请确保使用的是原
始图像,而不是灰度图像:
最后,调用cv2.imshow显示处理后的图像。通常,为了防止图像
窗口自动关闭,插入一个对waitKey的调用,当用户按下任意键时返
回:
好了,我们在图像中检测到一群伐木工,如图5-1所示。
这个例子中的照片是谢尔盖·普罗库金–戈尔斯基(Sergey
Prokudin-Gorsky)(1863—1944)的作品,普罗库金–戈尔斯基是彩色
摄影的先驱。沙皇尼古拉二世让普罗库金–戈尔斯基拍摄俄罗斯帝国
的人物和地点,将其作为一个庞大的纪录片项目。1909年,普罗库金–
戈尔斯基在俄罗斯西北部的丝薇尔河附近拍摄了这些伐木工人。
5.4.2 在视频上进行人脸检测
现在,我们了解了如何在静态图像上进行人脸检测。如前所述,
我们可以在视频(摄像头回传信号或者预先录制的视频文件)的每一
帧上重复人脸检测的过程。
下一个脚本将打开一个摄像头回传信号,读取一帧,检查该帧中
的人脸,并在检测到的人脸内扫描眼睛。最后,在人脸周围绘制蓝色
的矩形,在眼睛周围绘制绿色的矩形。以下是完整的脚本:
我们将上面的例子分解成更小、更容易理解的部分:
(1)像往常一样,导入cv2模块。之后,初始化两个
CascadeClassifier对象,一个用于人脸,另一个用于眼睛:
(2)就像大多数交互式脚本一样,打开一个摄像头回传信号,开
始迭代帧。继续,直到用户按下某个键。当成功捕捉到一帧时,将其
转换为灰度作为处理的第一步:
(3)利用人脸检测器的detectMultiScale方法对人脸进行检测。
正如之前所述,我们使用了scaleFactor和minNeighbors参数。我们还
使用minSize参数指定了人脸的最小尺寸,具体为120×120,因此不会
尝试去检测比这个尺寸小的脸。(假设用户坐在摄像头附近,可以有
把握地说,图像中用户的脸将大于120×120像素。)以下是对
detectMultiScale的调用:
(4)迭代检测到的人脸。在原始彩色图像的每个矩形周围绘制一
个蓝色边界。然后,在灰度图像的同一个矩形区域内进行眼睛检测:
眼睛检测器的准确率比人脸检测器要低一些。你可能会看到
阴影、部分镜框或其他被误认为是眼睛的人脸区域。为了改善结果,
可以尝试将roi_gray定义为人脸的一个较小区域,因为我们很容易猜测
到眼睛在直立人脸中的位置。还可以试着使用maxSize参数来避免那些
太大不可能是眼睛的误报。此外,可以调整minSize和maxSize,使尺寸
与w和h(即检测到的人脸大小)成比例。作为一个练习,你可以随意
更改这些参数和其他参数。
(5)循环遍历生成的眼睛矩形,并在其周围绘制绿色轮廓:
(6)最后,在窗口中显示生成的帧:
运行脚本。如果检测器产生了准确的结果,而且如果有任何人脸
在摄像头的视野内,应该会看到人脸周围有一个蓝色的矩形,眼睛周
围有一个绿色的矩形,如图5-2所示。
图5-2 脚本运行结果
用此脚本进行实验,研究人脸和眼睛检测器在不同条件下的表
现。试着在更亮或更暗的房间进行。如果戴着眼镜,试着摘掉眼镜再
进行一次。尝试在不同人脸和不同的表情下进行。调整脚本中的检测
参数,看看这些参数对结果的影响。当你感到满意时,我们再来考虑
在OpenCV中还可以做些什么。
5.4.3 进行人脸识别
人脸检测是OpenCV的一个非常棒的特性,也是构成更高级的操作
——人脸识别——的基础。什么是人脸识别?人脸识别是程序在给出
包含人脸的图像或视频时识别此人的能力。实现这一目标的方法之一
(也是OpenCV所采用的方法)是通过为程序提供一组分类图片(人脸
数据库)来训练程序,然后根据这些图片的特征进行识别。
OpenCV人脸识别模块的另一个重要特征是,每次识别都有一个置
信度,这允许我们在实际应用程序中设置阈值以限制错误识别的发生
率。
让我们从头开始。为了进行人脸识别,我们需要待识别的人脸。
人脸可以通过两种方式来获取:自己提供图像或者获取免费的人脸数
据库。在http://www.face-rec.org/databases/上有一个大的人脸数
据库在线可用。以下是其中几个著名的例子:
·耶鲁大学人脸数据库(Yalefaces),网址为
http://vision.ucsd.edu/content/yale-face-database。
·扩展的耶鲁大学人脸数据库B,网址为
http://vision.ucsd.edu/content/extended-yale-face-database-b-b。
·人脸数据库(来自剑桥AT&T实验室),网址为
http://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html。
为了对这些样本进行人脸识别,我们必须对包含被采样人的人脸
图像进行人脸识别。这个过程可能是有教学意义的,但是可能不如提
供我们自己的图像那样令人满意。许多计算机视觉学习者有同样的想
法:是否可以编写一个程序来识别自己的脸并且有一定的置信度。
1.生成人脸识别数据
我们来编写生成这些图像的脚本。包含不同表情的图像正是我们
所需要的,但是训练图像最好是正方形的,并且大小相同。我们的示
例脚本要求图像的大小为200×200,但是大多数免费可用的数据集的
图像都比这个尺寸小。
下面是脚本:
在这里,我们利用新掌握的如何在视频中检测人脸的知识来生成
样本图像。我们检测人脸,裁剪灰度转换帧的人脸区域,将其大小调
整为200×200像素,然后保存为PGM文件,并在特定文件夹中指定名称
(在本例中为jm,其中一个作者姓名的首字母,你可以使用自己姓名
的首字母)。与许多窗口应用程序一样,程序会一直运行,直到用户
按下某个键。
之所以出现count变量是因为我们需要图像的累加名称。运行脚本
几秒钟,更改几次脸部表情,并检查脚本中指定的目标文件夹。运行
脚本几秒钟,改变面部表情几次,检查你在脚本中指定的目标文件
夹。你会发现很多你的脸部图像,它们变为了灰度版本,调整了大
小,并用<count>.pgm格式命名。
修改output_folder变量,使其与你的名字匹配。例如,你可以选
择'../data/at/my_name'。运行脚本,等待它在许多帧(比如20帧或
更多)中检测到你的脸,然后按任意键退出。现在,再次修改
output_folder变量,使其与你想要识别的一个朋友的名字匹配。例
如,你可以选择'../data/at/name_of_my_friend'。不改变文件夹的
基本部分(本例中为'../data/at'),因为在后面“加载人脸识别的
训练数据”部分,我们将编写代码从这个基本文件夹的所有子文件夹
中加载训练图像。让你的朋友坐在摄像头前,再次运行脚本,让它在
许多帧中检测你朋友的脸,然后退出。对你想识别的其他任何人重复
这个过程。
现在,我们继续尝试在视频回传信号中识别用户的脸。这会很好
玩!
2.识别人脸
OpenCV 4实现了3种不同的人脸识别算法:特征脸
(Eigenface)、Fisherface和局部二值模式直方图(Local Binary
Pattern Histogram,LBPH)。特征脸和Fisherface来源于一个名为主
成分分析(Principal Component Analysis,PCA)的通用算法。有关
算法的详细描述,请参见以下链接:
·PCA:Jonathon Shlens在http://arxiv.org/pdf/1404.1100v1.pdf上提
供了一个直观的介绍。该算法是由卡尔·皮尔森(Karl Pearson)在
1901年发明的,并且原创论文“On Lines and Planes of Closest Fit to
Systems of Points in Space”网址为http://pca.narod.ru/pearson1901.pdf。
·Eigenface:见论文“Eigenfaces for Recognition”(1991)(作者
是Matthew Turk和Alex Pentland),网址为
http://www.cs.ucsb.edu/~mturk/Papers/jcn.pdf。
·Fisherface:开创性论文“The Use of Multiple Measurements in
Taxonomic Problems”(1936)(作者是R.A.Fisher),网址为
http://onlinelibrary.wiley.com/doi/10.1111/j.1469-
1809.1936.tb02137.x/pdf。
·局部二值模式:介绍该算法的第一篇论文是“Performance
evaluation of texture measures with classification based on Kullback
discrimination of distributions”(1994)(作者是T.Ojala、M.Pietikainen和
D.Harwood),网址为https://ieeexplore.ieee.org/document/576366。
就本书而言,我们对这些算法做一个概述。首先,这些算法都遵
循一个相似的过程:进行一组分类观察(我们的人脸数据库,每个人
都包含大量样本),在此基础上训练一个模型,对脸部图像(这可能
是我们在图像或视频中检测到的脸部区域)进行分析并确定两件事
——主体的身份以及对这个身份是正确的信心的度量。后者通常被称
为置信度。
特征脸算法执行主成分分析(PCA),识别某一组观察数据(同样
是人脸数据库)的主成分,计算当前观测值(图像或帧中检测到的人
脸)相对于数据集的散度,并产生一个值。该值越小,人脸数据库与
检测到的人脸之间的差异就越小,因此值为0表示精确匹配。
Fisherface也是从主成分分析(PCA)衍生的,并应用了更复杂的
逻辑。虽然计算更密集,但是产生的结果往往比特征脸算法更准确。
相反,LBPH将检测到的人脸分成小单元格,并为每个单元格建立
一个直方图,描述在比较给定方向的邻域像素时图像的亮度是否在增
加。这个单元格的直方图可以与模型中相应的单元格进行比较,从而
产生相似性度量。在OpenCV的人脸识别器中,LBPH的实现是唯一允许
模型样本人脸和检测到的人脸具有不同形状、不同大小的人脸识别
器。因此,它很方便,本书的作者发现该算法的准确度优于其他两个
算法。
3.加载人脸识别的训练数据
无论选择什么样的人脸识别算法,我们都可以用同样的方式加载
训练图像。在前面的“生成人脸识别数据”部分中,我们生成了训练
图像,并将它们保存在根据人名或人名首字母进行组织的文件夹中。
例如,下面的文件夹结构可以包含本书作者Joseph Howse(J.H.)和
Joe Minichino(J.M.)的脸部图像样本:
我们编写一个脚本加载这些图像,并以一种OpenCV的人脸识别器
能够理解的方式对它们进行标签。要处理文件系统和数据,我们将使
用Python标准库的os模块以及cv2和numpy模块。我们创建以下面
import语句开头的脚本:
我们添加以下read_images函数,该函数可以遍历目录的子目录,
加载图像,将这些图像调整为指定的大小,并将调整后的图像放入一
个列表中。同时,该函数还构建了另外两个列表:第一个是人名或人
名首字母列表(基于子文件夹名称),第二个是与加载的图像相关联
的标签列表或数字ID列表。例如,jh是一个名称,0可以是从jh子文件
夹加载的所有图像的标签。最后,该函数将图像和标签列表转换为
NumPy数组,并返回3个变量:名称列表、图像的NumPy数组和标签的
NumPy数组。以下是该函数的实现:
通过添加如下代码调用read_images函数:
编辑之前代码块中的path_to_training_images变量,以确保该变
量与之前“生成人脸识别数据”部分的代码中定义的output_folder变量
的基本文件夹相匹配。
到目前为止,我们已经有了有用格式的训练数据,但是还没有创
建人脸识别器,也没有进行任何训练。我们将在接下来的内容中完成
这些任务,同时将继续实现同一脚本。
4.基于特征脸进行人脸识别
既然有了训练图像数组及其标签数组,只用两行代码就可以创建
和训练一个人脸识别器:
我们在这里做了什么?我们使用OpenCV的
cv2.EigenFaceRecognizer_create函数创建特征脸人脸识别器,通过
传递图像数组和标签(数字ID)数组训练识别器。我们还可以选择将
两个参数传递给cv2.EigenFaceRecognizer_create:
·num_components:这是PCA需要保留的主成分量数。
·threshold:这是一个浮点值,指定置信度阈值。丢弃置信度低于
阈值的人脸。默认情况下,该阈值是最大浮点值,因此不会丢弃任何
人脸。
为了测试识别器,我们使用一个人脸检测器和一个摄像头的回传
视频信号。正如在前面的脚本中所做的那样,我们可以使用以下代码
来初始化人脸检测器:
下面的代码初始化摄像头回传信号,遍历帧(直到用户按下任意
键)并对每一帧进行人脸检测和识别:
我们来看看前面代码块中最重要的功能。对于每个检测到的人
脸,我们对它进行转换并调整它的大小,以便获得匹配预期大小的灰
度版本(在本例中,预期大小是“加载人脸识别的训练数据”小节中
training_image_size变量定义的200×200像素)。然后,将调整后的
灰度人脸传递给人脸识别器的predict函数。该函数将返回一个标签和
置信度。我们查找对应于这张脸的数字标签的人名。(请记住,我们
在“加载人脸识别的训练数据”中创建了names数组。)我们在识别的
人脸上方用蓝色文本给出姓名和置信度。在遍历所有检测到的人脸之
后,显示带注释的图像。
我们采用了一种简单的方法进行人脸检测和识别,其目的是
让你能够运行一个基本应用程序,并理解OpenCV 4中的人脸识别过
程。要想改进方法并使其更加鲁棒,可以采取进一步的步骤,例如,
正确地对准并旋转检测到的人脸从而最大限度地提高识别准确度。
运行脚本,你应该会看到类似于图5-3的效果。
接下来,我们将考虑如何调整这些脚本,用其他人脸识别算法来
替换特征脸。
5.基于Fisherface进行人脸识别
如何基于Fisherface进行人脸识别?这个过程与上述过程并无多
大变化,只需要实例化一个不同的算法。使用默认参数,model变量的
声明如下:
cv2.face.FisherFaceRecognizer_create接受与
cv2.createEigenFace-Recognizer_create相同的两个可选参数:要保
留的主成分数量和置信度阈值。
6.基于LBPH进行人脸识别
最后,我们快速了解一下LBPH算法。同样,该过程也类似上述过
程。可是,算法工厂接受以下可选参数(按顺序):
·radius:用于计算单元格直方图的邻域之间的像素距离(默认为
1)。
·neighbors:用于计算单元格直方图的邻域数(默认为8)。
·grid_x:水平分割人脸的单元格数量(默认为8)。
·grid_y:垂直分割人脸的单元格数量(默认为8)。
·confidence:置信度阈值(默认情况下,为可能的最高浮点值,
这样就不会丢弃任何结果)。
使用默认参数,model声明如下所示:
请注意,使用LBPH,我们不需要调整图像的大小,因为划分
为网格允许对每个单元格中识别的模式进行比较。
7.基于置信度丢弃结果
predict方法返回一个元组,其中第一个元素是识别到的个体的标
签,第二个元素是置信度。所有的算法都带有设置置信度阈值的选
项,该阈值测量识别到的人脸与原始模型的匹配程度,因此,分值为0
表示完全匹配。
在某些情况下,你可能宁愿保留所有的识别结果,再应用进一步
的处理,这样你就可以提出自己的算法来估计识别结果的置信度。例
如,如果你尝试着识别视频中的人,你可能希望分析随后帧中的置信
度,以确定是否识别成功。在这种情况下,你可以查看算法获得的置
信度,并得出自己的结论。
置信度典型的取值范围取决于算法。特征脸和Fishface产生的
值的范围(大致)在0~20 000,低于4000的所有分值都表示是一个相
当有信心的识别结果。对于LBPH,好的识别结果的参考值低于50,所
有超过80的值都被认为是糟糕的置信度。
常规的自定义方法是,直到有足够多的具有令人满意的任意置信
度的帧数时,才在识别到的人脸周围绘制矩形,但是你完全可以使用
OpenCV的人脸识别模块来根据需要定制应用程序。
5.5 在红外线下换脸
人脸检测和识别并不局限于可见光谱。在近红外(Near-
Infrared,NIR)摄像头和近红外光源下,即使在人眼看来是全黑的场
景中,人脸检测和识别也是可能的。这个功能在安全和监视应用程序
中非常有用。
在第4章中,我们研究了华硕Xtion PRO等近红外深度摄像头的基
本用法。我们扩展了交互式应用程序Cameo的面向对象代码。我们用深
度摄像头拍摄了一些画面。根据深度,我们将每一帧分割成一个主层
(如用户的脸)和其他层。把其他图层涂成黑色,这样就达到了隐藏
背景的效果,使得交互视频信号中只有主层(用户的脸)出现在屏幕
上。
现在,我们修改Cameo,练习之前的深度分割技能以及人脸检测的
新技能。我们来检测人脸,当同一帧中检测到至少两张人脸时,交换
这两张脸,使一个人的头出现在另一个人的身体上。我们不会复制检
测到的人脸矩形中的所有像素,而是只复制该矩形主深度层的一部分
像素。这应该实现了换脸的效果,而不交换脸周围的背景像素。
一旦完成修改,Cameo将能够产生类似图5-4的输出。
我们看到约瑟夫·豪斯的脸和他母亲珍妮特·豪斯(Janet
Howse)的脸互换了。尽管Cameo是从矩形区域复制像素(在前景中,
交换区域的底部清晰可见),一些背景像素没有交换,所以我们不会
看到所有地方都有矩形边缘。
你可以在https://github.com/PacktPublishing/Learning-
OpenCV-4-Computer-Vision-with-Python-Third-Edition处找到本书
库中对Cameo源代码的所有相关更改,特别是在chapter05/cameo文件
夹中的修改。为简单起见,我们不会在本书中讨论所有的修改,但是
将在接下来的两个小节中讨论一些重点内容。
5.5.1 修改应用程序的循环
为了支持换脸功能,Cameo项目有两个新模块,名为rects和
trackers。rects模块包含用于复制和交换矩形的函数,带有一个可选
的掩模,可将复制或交换操作限制在特定像素。trackers模块包含一
个名为FaceTracker的类,它使OpenCV的人脸检测功能适应面向对象的
编程风格。
因为我们已经在本章前面介绍了OpenCV的人脸检测功能,并且已
经在前面的章节中演示了面向对象的编程风格,所以这里不再讨论
FaceTracker的实现。你可以在本书的库中查看FaceTracker的实现。
打开cameo.py,这样就可以浏览对应用程序的全部更改:
(1)在文件的顶部,需要导入新的模块,如下列代码块中的粗体
所示:
(2)现在,我们把注意力转向对CameoDepth类的__init__方法的
修改。更新后的应用程序使用了一个FaceTracker实例。作为其功能的
一部分,FaceTracker可以在检测到的人脸周围绘制矩形。我们给
Cameo的用户一个选项来启用或禁用脸部矩形的绘制。我们将通过一个
布尔变量跟踪当前选择的选项。下面的代码块显示了初始化
FaceTracker对象和布尔变量所需的更改(以粗体显示):
我们在CameoDepth的run方法中使用了FaceTracker对象,该方法
包含捕获和处理帧的应用程序主循环。每成功捕获一帧,就调用
FaceTracker的方法来更新人脸检测结果,并获取最新检测到的人脸。
然后,对于每张脸,根据深度摄像头视差图创建掩模。(在第4章中,
我们针对整个图像创建了一个这样的掩模,而不是针对每个人脸矩形
创建掩模。)然后,调用函数rects.swapRects来执行人脸矩形的掩模
交换。(稍后,我们将在5.5.2节中讨论swapRects的实现。)
(3)根据当前选择的选项,我们可能会让FaceTracker在人脸周
围绘制矩形。所有相关的更改在下面的代码块中以粗体显示:
(4)最后,修改onKeypress方法以便用户可以按X键开始或停止
显示检测到的人脸周围的矩形。同样,相关的更改在下面的代码块中
以粗体显示:
接下来,我们来看在本节前面导入的rects模块的实现。
5.5.2 掩模复制操作
rects模块是在rects.py中实现的。在5.5.1节中,我们已经看到
对rects.swapRects函数的一个调用。可是,在考虑swapRects的实现
之前,我们首先需要一个更基本的copyRect函数。
回到第2章,我们学习了如何使用NumPy的切片语法将数据从一个
感兴趣的矩形区域复制到另一个感兴趣的矩形区域。在感兴趣的矩形
区域之外,源和目标图像不受影响。现在,我们想进一步限制这个复
制操作。我们想要使用一个与源矩形相同大小的给定掩模。
我们将只复制源矩形中掩模值不为零的那些像素。其他像素应保
留目标图像的原始值。这个逻辑包含一个条件数组和两个可能的输出
值数组,可以用numpy.where函数简洁地表达。
牢记这种方法,我们来考虑copyRect函数。它接受一幅源图像和
目标图像、一个源矩形和目标矩形,以及一个掩模作为参数。后者可
能是None,在这种情况下,只需调整源矩形的内容大小以匹配目标矩
形,然后将调整后的内容分配给目标矩形即可。否则,接下来就要确
保掩模和图像有相同的通道数。假设掩模有一个通道,但是图像可能
有三个通道(BGR)。我们可以使用numpy.array的repeat和reshape方
法将重复通道添加到掩模中。最后,使用numpy.where执行复制操作。
完整的实现如下:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。