基于 OpenCV 的面部特征交换——实验一
一、课程介绍
今天这门课程将通过 OpenCV 库来实现人脸面部特征交换,其实就是将第二张人脸的眼睛、鼻子和嘴巴通过程序自动裁剪适配并覆盖到第一张人脸上,并且为了使得修改后的照片看着更加自然,我们还需要调整皮肤颜色。
说明:本次实验课程的项目来源于 https://github.com/matthewearl/faceswap ,该项目基于 MIT 许可证。
该项目我将通过两次实验课来实现,这是本项目的第一次实验。
1.1 课程知识点
通过本次课程,我们将接触到以下几个知识点:
- 人脸特征交换程序设计思路
- 使用 docopt 库快速构建命令行解析器
- 使用 dlib 库的标定人脸特征点
1.2 主要流程
实验的流程为:
- 环境配置
- 设计思路
- 通过 docopt 构建命令行解析器
- 使用 dlib 标定面部特征
1.3 实验环境
Python:3.4.3
OpenCV:3.1.0
dlib:1.9.0
1.4 效果截图
最终我们程序的转换效果如下。
这是原图。
转换之后的结果是这样的。
二、环境配置
所谓“工欲善其事,必先利其器”,开始编写代码之前我们需要先把用到的库安装好。
依赖的库有:
dlib
opencv
docopt
2.1 安装 dlib
dlib 是一个基于 C++ 编写的扩展库,包含有许多常用的机器学习算法以及图像处理函数。并且还支持大量的数值计算,如矩阵、大整数随机运算等。但是在编译安装 dlib 之前我们还需要先给系统装上各种依赖环境,步骤如下。
参考:dlib 官网
安装 Python 的开发库 python3-dev
和 python3-setuptools
。
$ sudo apt-get update
$ sudo apt-get install python3-dev python3-setuptools
另外还要安装 Boost Python 开发文件,它为 Python 编程提供了简单易用的 C++ 函数库接口。
$ sudo apt-get install libboost-python-dev
dlib 本来可以直接通过 pip3
指令进行编译安装,但是由于虚拟机的内存资源不足无法完成编译,因此我这里提供一份编译好的 dlib 动态库。可以通过 wget
命令进行下载,并将 dlib.so
复制到 /usr/local/lib/python3.4/dist-packages/
目录之下,这样就可以全局使用该模块了。
$ wget http://labfile.oss.aliyuncs.com/courses/686/dlib.so
$ sudo cp dlib.so /usr/local/lib/python3.4/dist-packages/
2.2 安装 OpenCV
OpenCV 是一款功能强大的跨平台计算机视觉开源库,可以用于解决人机交互、物体检测、人脸识别等领域的问题。库本身是采用 C++ 编写的,但是同时也对 Python, Java, C# 等语言提供接口支持。
本门课程考虑到 OpenCV 的安装过程相对较繁琐且耗时较长,因此实验环境已经配置好 OpenCV 3.1 的环境。
参考:如果想知道如何编译 OpenCV 的同学可以参考以下官方文档。
2.3 安装 docopt
docopt 是 Python 的一个第三方参数解析库,可以根据使用者提供的文档描述自动生成解析器。因此使用者可以用它来定义交互参数与解析参数。
安装 docopt 库的过程就非常简单了。
$ sudo pip3 install docopt
三、设计思路
我们先来考虑一下这个程序需要完成的两大个功能。
- 支持从命令行获取指定图像路径
- 读取指定图像并进行处理保存
事实上,第一点要求通过借助 docopt 库可以非常快速便捷地构建命令行解析器,具体用法在后续将会详细介绍。
而第二点才是我们真正需要关注的问题。想象一下在现实生活中,给你两张人像照片,现在要求你将他们的脸部特征交换一下(本次实验中的脸部特征主要指眉毛、眼睛、鼻子和嘴巴组合而成的部位),那么最简单的方法就是将第二个人脸部特征部分剪下拼接到第一个人的照片上。所以事实上,我们的程序实现也是在模仿这一个过程。但是程序并不知道哪些部分是眉毛、眼睛、鼻子或者嘴巴,在电脑看来这些图片仅仅是一连串二进制数,所以我们还需要利用 dlib 库中的机器学习算法来帮助我们检测并标记这些特征部位。
所以我们的处理方法主要分为以下几个步骤。
- 借助 dlib 库检测出图像中的脸部特征
- 计算将第二张图像脸部特征对齐到一张图像脸部特征的变换矩阵
- 综合考虑两张照片的面部特征获得合适的面部特征掩码
- 根据第一张图像人脸的肤色修正第二张图像
- 通过面部特征掩码拼接这两张图像
- 保存图像
好吧,也许步骤看起来有些多,暂时看不懂也没关系,继续下面的课程,你将会对这些概念慢慢熟悉起来。
四、通过 docopt 构建命令行解析器
接下来我们将通过一个简单的例子来说明 docopt 的使用。
进入 Code
目录中创建程序文件 docopt_demo.py
,可以使用 Vim 或者 gedit 进行编辑。
4.1 docopt 初体验
将下面的代码直接复制粘贴,先保存Demo程序并运先查看效果,之后再进行讲解。
docopt_demo.py
的代码如下:
"""
faceswap can put facial features from one face onto another.
Usage: faceswap [options] <image1> <image2>
Options:
-v --version show the version.
-h --help show usage message.
"""
from docopt import docopt
__version__ = 'demo 1.0'
if __name__ == '__main__':
arguments = docopt(__doc__, version=__version__)
print(arguments)
运行命令:
$ python3 docopt_demo.py
$ python3 docopt_demo.py img1.jpg img2.jpg
结果如图。
运行之后我们发现 docopt 库返回了一个字典,字典中的键由我们给出的 Usage
与 Options
中的选项构成。
再运行命令:
$ python3 docopt_demo.py -h
结果如图。
我们发现执行 help
选项的时候输出到屏幕的帮助信息就是程序中最开始部分我们用三个双引号包围住的字符串内容。
所以 docopt(__doc__, version=__version__)
函数能自动根据三个双引号中的文档内容(存储于 __doc__
中)生成命令行解析器,并将解析结果以字典对象返回。所以使用 docopt
进行命令行解析,我们唯一需要做好的事情就是编写好帮助文档,然后其它一切解析的问题都可以甩手给 docopt
自动解决。
4.2 编写帮助文档
首先看看我们前边程序中定义的帮助文档。
"""
faceswap can put facial features from one face onto another.
Usage: faceswap [options] <image1> <image2>
Options:
-v --version show the version.
-h --help show usage message.
"""
为了使 __doc__
能引用到帮助文档的内容,帮助文档需要书写在程序的最开始部分并且采用三双引号进行注释。当然,或者你也可以在程序段中通过 __doc__ = """[帮助文档]"""
这种显示赋值的方式为 __doc__
指定引用内容。
在帮助文档中必须指定 usage
字段, docopt
正是通过 usage
字段确定了命令行参数的格式。 usage
之前的允许有任意多个字符,这些字符可以用来介绍程序信息与作用也可以是任何用于提升帮助文档美观性的字符,因为这些都不会影响到 docopt
的命令行参数解析。
这里我们的 usage
定义为:
Usage: faceswap [options] <image1> <image2>
我们的 usage
指定了一个可选参数 [options]
与两个必填参数 <image1>, <image2>
,它们分别是参数选项与图片路径名。
更进一步地,我们紧接着 usage
之后还定义了 options
的内容:
Options:
-v --version show the version.
-h --help show usage message.
例子中,各符号含义如下:
-
描述了选项的短形式--
描述了选项的长形式
关于 docopt 的讲解就到这,想了解更多的同学可以自行去 docopt 官网查阅文档。
五、使用 dlib 标定面部特征
接下来我将介绍如何使用 dlib 检测面部特征,并标定出来。
dlib 的面部特征提取主要分为两个步骤,首先通过 dlib 提供的前脸检测器检测出图像中是否存在人脸,如果存在则返回一个矩形框出人脸部分,再进一步通过面部特征提取器获取面部特征点。
进入 Code
文件夹,创建文件 draw_landmarks.py
文件,编写如下代码。
import cv2
import dlib
# 加载面部检测器
detector = dlib.get_frontal_face_detector()
# 加载训练模型并获取面部特征提取器
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
# 以 RGB 模式读入图像
im = cv2.imread('hc.jpg', cv2.IMREAD_COLOR)
# 使用检测器检测人脸
rects = detector(im, 1)
# 使用特征提取器获取面部特征点
l = [(p.x, p.y) for p in predictor(im, rects[0]).parts()]
# 遍历面部特征点并绘制出来
for (cnt, p) in enumerate(l):
cv2.circle(im, p, 5, (0, 255, 255), 2)
cv2.putText(im, str(cnt), (p[0]+5, p[1]-5), 0, 0.75, color=(0, 0, 255))
# 保存图像
cv2.imwrite('landmarks.jpg', im)
cv2.waitKey(0)
程序中使用到的 shape_predictor_68_face_landmarks.dat
是 dlib 官方提供的模型数据,有了这个模型之后我们就不需要自己再耗费时间去训练模型,直接拿来使用即可。
shape_predictor_68_face_landmarks.dat
与 hc.jpg
文件可以通过 wget
命令分别从以下链接下载。
http://labfile.oss.aliyuncs.com/courses/686/hc.jpg
https://labfile.oss.aliyuncs.com/courses/686/shape_predictor_68_face_landmarks.dat
通过命令 python3 draw_landmarks.py
运行程序,程序运行结束之后我们发现目录底下多出了一个 landmarks.jpg
的图片。双击打开看看效果。
可以发现 dlib 很准确地检测出来了下巴、嘴巴、鼻子、眼睛和眉毛这些面部特征点。并且每个部位都被分配固定索引的点进行标注,范围如下表,一共是 68 个标记点。
部位 | 索引 |
---|---|
下巴 | 0~16 |
左眉毛 | 17~21 |
右眉毛 | 22~26 |
鼻子 | 27~35 |
左眼睛 | 36~41 |
右眼睛 | 42~47 |
嘴巴 | 48~67 |
备注:不难发现,其实嘴巴部分还可以更细致地分为上下嘴唇。
六、总结
本节课我们接触学习了 docopt 与 dlib 库的使用,并且讲解了使用 OpenCV 实现面部特征交换程序的大致思路,下节课我们将开始进行编码实现。