基于 OpenCV 的面部特征交换——实验二

一、课程介绍

今天这门课程将通过 OpenCV 库来实现人脸面部特征交换,其实就是将第二张人脸的眼睛、鼻子和嘴巴通过程序自动裁剪适配并覆盖到第一张人脸上,并且为了使得修改后的照片看着更加自然,我们还需要调整皮肤颜色。

说明:本次实验课程的项目来源于 https://github.com/matthewearl/faceswap ,该项目基于 MIT 许可证。

该项目我将通过两次实验课来实现,这是本项目的第二次实验。

1.1 课程知识点

通过本次课程,我们将接触到以下几个知识点:

  • OpenCV 库的使用
  • dlib 库的使用
  • docopt 库的使用

1.2 主要流程

实验的流程为:

  • 环境配置
  • 设计思路
  • 编程实现

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 的同学可以参考以下官方文档。

  • http://docs.opencv.org/master/d7/d9f/tutorial_linux_install.html

2.3 安装 docopt

docopt 是 Python 的一个第三方参数解析库,可以根据使用者提供的文档描述自动生成解析器。因此使用者可以用它来定义交互参数与解析参数。

安装 docopt 库的过程就非常简单了。

$ sudo pip3 install docopt

参考:http://docopt.org/

三、设计思路

我们先来回顾一下上节课介绍的这个程序需要完成的两大个功能。

  1. 支持从命令行获取指定图像路径
  2. 读取指定图像并进行处理保存

第一点要求通过借助 docopt 库可以非常快速便捷地构建命令行解析器,具体用法在前一次试验中已经介绍过了。

而针对第二点,我们的处理方法主要分为以下几个步骤。

  1. 借助 dlib 库检测出图像中的脸部特征
  2. 计算将第二张图像脸部特征对齐到一张图像脸部特征的变换矩阵
  3. 综合考虑两张照片的面部特征获得合适的面部特征掩码
  4. 根据第一张图像人脸的肤色修正第二张图像
  5. 通过面部特征掩码拼接这两张图像
  6. 保存图像

四、编程实现

进入 Code 目录,创建 course_686 文件夹作为项目目录,之后所有的资源文件都位于该文件夹之下。

$ cd Code
$ mkdir face_swap && cd face_swap

4.1 实现命令行解析器

新建文件 face_swap.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.
"""import cv2
import dlib
import numpy as np
from docopt import docopt__version__ = '1.0'def main():arguments = docopt(__doc__, version=__version__)if __name__ == '__main__':main()

在这部分我们导入了需要用到的库,并且编写了帮助文档,程序接受一个可选参数和两个必填参数。其中 <image1> 和 <image2> 指定了图像的文件路径。

4.2 实现脸部特征交换

为了让整个程序逻辑更加清晰,我首先将程序的主体部分代码展现出来,这里用到了很多自定义函数,这些都将在后边一一实现,并且这里还定义了一系列的常量和初始化工作。

程序中使用到的 shape_predictor_68_face_landmarks.dat 是 dlib 官方提供的模型数据,有了这个模型之后我们就不需要自己再耗费时间去训练模型,直接拿来使用即可。

下载方式:

wget https://labfile.oss.aliyuncs.com/courses/686/shape_predictor_68_face_landmarks.dat
...
# 加载训练模型
PREDICTOR_PATH = "shape_predictor_68_face_landmarks.dat"
# 图像放缩因子
SCALE_FACTOR = 1
FEATHER_AMOUNT = 11FACE_POINTS = list(range(17, 68))  # 脸
MOUTH_POINTS = list(range(48, 61))  # 嘴巴
RIGHT_BROW_POINTS = list(range(17, 22))  # 右眉毛
LEFT_BROW_POINTS = list(range(22, 27))  # 左眉毛
RIGHT_EYE_POINTS = list(range(36, 42))  # 右眼睛
LEFT_EYE_POINTS = list(range(42, 48))  # 左眼睛
NOSE_POINTS = list(range(27, 35))  # 鼻子
JAW_POINTS = list(range(0, 17))  # 下巴# 选取了左右眉毛、眼睛、鼻子和嘴巴位置的特征点索引
ALIGN_POINTS = (LEFT_BROW_POINTS + RIGHT_EYE_POINTS + LEFT_EYE_POINTS +RIGHT_BROW_POINTS + NOSE_POINTS + MOUTH_POINTS)# 选取用于叠加在第一张脸上的第二张脸的面部特征
# 特征点包括左右眼、眉毛、鼻子和嘴巴
OVERLAY_POINTS = [LEFT_EYE_POINTS + RIGHT_EYE_POINTS + LEFT_BROW_POINTS + RIGHT_BROW_POINTS,NOSE_POINTS + MOUTH_POINTS,
]# 定义用于颜色校正的模糊量,作为瞳孔距离的系数
COLOUR_CORRECT_BLUR_FRAC = 0.6# 实例化脸部检测器
detector = dlib.get_frontal_face_detector()
# 加载训练模型
# 并实例化特征提取器
predictor = dlib.shape_predictor(PREDICTOR_PATH)# 定义了两个类处理意外
class TooManyFaces(Exception):passclass NoFaces(Exception):pass...
...def main():arguments = docopt(__doc__, version=__version__)# 获取图像与特征点im1, landmarks1 = read_im_and_landmarks(arguments['<image1>'])im2, landmarks2 = read_im_and_landmarks(arguments['<image2>'])# 选取两组图像特征矩阵中所需要的面部部位# 计算转换信息,返回变换矩阵M = transformation_from_points(landmarks1[ALIGN_POINTS],landmarks2[ALIGN_POINTS])# 获取 im2 的面部掩码mask = get_face_mask(im2, landmarks2)# 将 im2 的掩码进行变化,使之与 im1 相符warped_mask = warp_im(mask, M, im1.shape)# 将二者的掩码进行连通combined_mask = np.max([get_face_mask(im1, landmarks1), warped_mask],axis=0)# 将第二幅图像调整到与第一幅图像相符warped_im2 = warp_im(im2, M, im1.shape)# 将 im2 的皮肤颜色进行修正,使其和 im1 的颜色尽量协调warped_corrected_im2 = correct_colours(im1, warped_im2, landmarks1)# 组合图像,获得结果output_im = im1 * (1.0 - combined_mask) + warped_corrected_im2 * combined_mask# 保存图像cv2.imwrite('output.jpg', output_im)
...

4.2.1 实现 read_im_and_landmarks()

...
# 获取特征点
def get_landmarks(im):# detector 是特征检测器# predictor 是特征提取器rects = detector(im, 1)# 如果检测到多张脸if len(rects) > 1:raise TooManyFaces# 如果没有检测到脸if len(rects) == 0:raise NoFaces# 返回一个 n*2 维的矩阵,该矩阵由检测到的脸部特征点坐标组成return np.matrix([[p.x, p.y] for p in predictor(im, rects[0]).parts()])# 读取图片文件并获取特征点
def read_im_and_landmarks(fname):# 以 RGB 模式读取图像im = cv2.imread(fname, cv2.IMREAD_COLOR)# 对图像进行适当的缩放im = cv2.resize(im, (im.shape[1] * SCALE_FACTOR,im.shape[0] * SCALE_FACTOR))s = get_landmarks(im)return im, s
...

4.2.2 实现 transformation_from_points()

# 计算转换信息,返回矩阵
def transformation_from_points(points1, points2):points1 = points1.astype(np.float64)points2 = points2.astype(np.float64)c1 = np.mean(points1, axis=0)c2 = np.mean(points2, axis=0)points1 -= c1points2 -= c2s1 = np.std(points1)s2 = np.std(points2)points1 /= s1points2 /= s2U, S, Vt = np.linalg.svd(points1.T * points2)R = (U * Vt).Treturn np.vstack([np.hstack(((s2 / s1) * R,c2.T - (s2 / s1) * R * c1.T)),np.matrix([0., 0., 1.])])

这里的变换矩阵是根据以下公式计算出来的。

\sum_{i=1}^{68}\left \| sRp_{i}^{T}+T-q_{i}^{T} \right \|^{2}∑​i=1​68​​​∥​∥​​sRp​i​T​​+T−q​i​T​​​∥​∥​​​2​​

建议直接复制使用,如果对原理感兴趣的同学不妨参考
https://en.wikipedia.org/wiki/Procrustes_analysis#Ordinary_Procrustes_analysis

4.2.3 get_face_mask

...
# 绘制凸多边形
def draw_convex_hull(im, points, color):points = cv2.convexHull(points)cv2.fillConvexPoly(im, points, color=color)# 获取面部的掩码
def get_face_mask(im, landmarks):im = np.zeros(im.shape[:2], dtype=np.float64)for group in OVERLAY_POINTS:draw_convex_hull(im,landmarks[group],color=1)im = np.array([im, im, im]).transpose((1, 2, 0))# 应用高斯模糊im = (cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0) > 0) * 1.0im = cv2.GaussianBlur(im, (FEATHER_AMOUNT, FEATHER_AMOUNT), 0)return im
...

get_face_mask 用于获取面部特征部分(眉毛、眼睛、鼻子以及嘴巴)的图像掩码。效果如下:

图像掩码作用于原图之后,原图中对应掩码部分为白色的部分才能显示出来,黑色的部分则不予显示,因此通过图像掩码我们就能实现对图像“裁剪”。

4.2.4 warp_im

由 get_face_mask 获得的图像掩码还不能直接使用,因为一般来讲用户提供的两张图像的分辨率大小很可能不一样,而且即便分辨率一样,图像中的人脸由于拍摄角度和距离等原因也会呈现出不同的大小以及角度,所以如果不能只是简单地把第二个人的面部特征抠下来直接放在第一个人脸上,我们还需要根据两者计算所得的面部特征区域进行匹配变换,使得二者的面部特征尽可能重合。

...
# 变换图像
def warp_im(im, M, dshape):output_im = np.zeros(dshape, dtype=im.dtype)# 仿射函数,能对图像进行几何变换# 三个主要参数,第一个输入图像,第二个变换矩阵 np.float32 类型,第三个变换之后图像的宽高cv2.warpAffine(im,M[:2],(dshape[1], dshape[0]),dst=output_im,borderMode=cv2.BORDER_TRANSPARENT,flags=cv2.WARP_INVERSE_MAP)return output_im
...

效果如下:

4.2.5 correct_colours()

最后我们还需要修改皮肤颜色,使两张图片在拼接时候显得更加自然。

# 修正颜色
def correct_colours(im1, im2, landmarks1):blur_amount = COLOUR_CORRECT_BLUR_FRAC * np.linalg.norm(np.mean(landmarks1[LEFT_EYE_POINTS], axis=0) -np.mean(landmarks1[RIGHT_EYE_POINTS], axis=0))blur_amount = int(blur_amount)if blur_amount % 2 == 0:blur_amount += 1im1_blur = cv2.GaussianBlur(im1, (blur_amount, blur_amount), 0)im2_blur = cv2.GaussianBlur(im2, (blur_amount, blur_amount), 0)# 避免出现 0 除im2_blur += (128 * (im2_blur <= 1.0)).astype(im2_blur.dtype)return (im2.astype(np.float64) * im1_blur.astype(np.float64) /im2_blur.astype(np.float64))

五、运行

运行方式:

$ python3 face_swap.py <image1> <image2>

另外本次课程的实验源码与图片可以通过 wget 命令从以下地址下载:

http://labfile.oss.aliyuncs.com/courses/686/dt.jpg
http://labfile.oss.aliyuncs.com/courses/686/hc.jpg
http://labfile.oss.aliyuncs.com/courses/686/faceswap.py
https://labfile.oss.aliyuncs.com/courses/686/shape_predictor_68_face_landmarks.dat

六、总结

通过本次课程我们接触到了 docopt, dlib 与 OpenCV 的使用,并实现了人脸特征交换程序。

七、参考

  • http://dlib.net/
  • http://opencv.org/
  • https://github.com/docopt/docopt

川普撞脸希拉里(基于 OpenCV 的面部特征交换)-2相关推荐

  1. 川普撞脸希拉里(基于 OpenCV 的面部特征交换)-1

    基于 OpenCV 的面部特征交换--实验一 一.课程介绍 今天这门课程将通过 OpenCV 库来实现人脸面部特征交换,其实就是将第二张人脸的眼睛.鼻子和嘴巴通过程序自动裁剪适配并覆盖到第一张人脸上, ...

  2. 基于opencv的面部特征交换(可选部位,可视化窗口)

    基于opencv的面部特征交换(可选部位,可视化窗口) 1.环境搭建 本项目使用软件为pycharm(作者强推,其他软件也可),使用库为opencv-python.dlib.docopt.tkinte ...

  3. OpenCV检测面部特征点的实例(附完整代码)

    OpenCV检测面部特征点的实例 OpenCV检测面部特征点的实例 OpenCV检测面部特征点的实例 #include "opencv2/objdetect.hpp" #inclu ...

  4. Facemark:使用OpenCV进行面部特征点检测

    面部特征检测应用很多,我将在下一节介绍当前项目用到一个典型例子,因为疲劳检测有一张方案是通过检测人眼的闭合时间来实现的,在实际装车应用中效果还不错.本节先介绍一下opencv中自带的特征点检测功能,后 ...

  5. 基于OpenCV的面部交换

    需要装python库 OpenCV dlib docopt(根据打开方式选择是否装) # -*- coding: UTF-8 #本电脑试运行 命令 python F:\python_project\s ...

  6. 基于 OpenCV 的面部关键点检测实战

    [ 编者按]这篇文章概述了用于构建面部关键点检测模型的技术,这些技术是Udacity的AI Nanodegree程序的一部分. 作者 | 小白 责编 | 欧阳姝黎 概述 在Udacity的AIND的最 ...

  7. 圆形标定板_基于圆形标定板特征点提取及排序的方法

    基于圆形标定板特征点提取及排序的方法 刘智 [摘 要] 摘要:在计算机视觉中 , 圆形标定板被广泛使用在像机标定中 , 本文针 对圆形标定板图像 ( 图 1) 在特征点提取后的排序问题 , 提出了利用 ...

  8. 运用特征脸方法的基于Opencv的猫脸检测实现

    本文禁止转载.抄袭,请尊重作者权利. 使用特征脸方法的基于Opencv的猫脸检测实现 摘要 目前,在计算机视觉和模式识别领域,脸识别技术是一个很活跃的课题,人脸识别的方法已经十分丰富,而对于日常生活中 ...

  9. 面部特征点检测(使用opencv+dlib)

    作为计算机视觉工程师和研究人员,很久以前,我们就一直在努力理解人类的面孔,从很早的时候起.面部分析最明显的应用是人脸识别.但是为了能够识别图像中的一个人,我们首先需要找到图像中脸所在的位置.因此,人脸 ...

最新文章

  1. Mybatis的delete方法
  2. Lisp 的单行注释和多行注释
  3. 接口测试之基础篇--http协议
  4. java中四种默认的权限修饰符,Java中四种访问权限资料整理
  5. jzoj4245-er【dp,贪心】
  6. Navicat for MySQL v8.0.27 的注册码
  7. 大厂面试必问!50w字+的Java技术类校招面试题汇总
  8. What means the error-message 'java.lang.OutOfMemoryError: GC overhead limit exceeded' in Java?
  9. linux文件操作命令介绍(一)
  10. 昔日“宅男最爱”、视频播放器之王破产清算:4.5万元商标拍到950万元
  11. NO.128 开发团队篇:参加项目计划会议,分解任务,领取任务,每天更新任务。...
  12. ipad写python代码用什么软件_iPad 能用来写代码吗?有哪些必备软件推荐?
  13. ad 原理图放置差分对_Altium Designer差分对设置方法
  14. Web安全班作业 | WireShark抓包ARP报文分析并实施ARP中间人攻击
  15. Java项目:问卷调查系统(java+SSM+layui+JSP+Mysql)
  16. codeforces 897 D Ithea Plays With Chtholly(交互)
  17. 如何使用路由器实现无网局域网,并实现PC与开发板间的通信
  18. bim综合软件:一次性快速解锁所有轴网,生成轴网
  19. [春秋云镜]CVE-2021-44983
  20. Beautiful Soup库的用法

热门文章

  1. R 使用emoji画图
  2. Ubuntu16.4安装搜狗拼音输入法
  3. 华为matepad10.4适配M-Pen2教程
  4. CEF与Off-Screen Rendering与Transparent Background
  5. docker是啥?是干什么的?
  6. 披着人皮的幽灵们……(读《死亡清扫日记》有感)
  7. js-六爻排盘-六神
  8. 陆军步兵学院文职面试计算机,2018军队文职文职面试试题回忆版(2018年第二号)...
  9. Win10怎么把磁盘格式化成exfat格式_使用命令格式化磁盘为exfat的方法
  10. 程序员的算法趣题Q05: 硬币兑换