用Python实现一个简单的智能换脸软件
智能换脸软件
基本信息介绍
软件名称
软件名称是Picture Faceswap,表示图片换脸,是一款图片换脸软件。
软件功能
已知一幅A的人脸图像,新输入一张B的人脸图像,按下换脸键后,将A的图像自动地换成B的人脸。
项目地址
项目地址
软件使用说明
软件运行依赖库
OpenCV、dlib、PyQt5、numpy、PIL等库
软件使用步骤
运行指令:python main.py
- 首先按下 “Choose face picture” 和 “Choose head picture” 键选择脸部图像和头部图像;
- 然后按下 “Swap” 键进行换脸,换脸后的图像会在 “Result” 框图中展现;
- 按下 “Save result as” 可以保存换脸后的图片;
- 按下 “clear” 键会清除所有的图片;
- 按下 “Exit” 键退出软件。
界面
项目地址
效果
测试结果1:
测试结果2:
测试结果3:
关键程序和算法
- 获取图像中的面部特征点:
- 获取人脸遮罩;
- 获取仿射变换矩阵;
- 利用仿射变换将脸部图像遮罩映射到头部图片中,得到符合头部图片坐标的新的脸部图像遮罩;
- 利用仿射变换将脸部图像映射到头部图片中;
- 修正由于图像肤色和光线的不同导致脸部覆盖区域边缘的不连续问题;
- 将两个图像的人脸遮罩进行结合;
- 输出换脸图像。
其中1、2为人脸图像检测部分,3、4、5、6、7、8为人脸图像转换部分
1.获取图像中的面部特征点:
# 提取图像中的面部特征点,将图像转换为一个矩阵数组,矩阵中每个元素对应每一行的一个x,y坐标
def acquire_landmarks(image):# 首先从dlib库中获取一个人脸检测器detector = dlib.get_frontal_face_detector()# 然后利用这个人脸检测器对图像画人脸框,返回的是一个人脸检测矩形框的4点坐标faces = detector(image, 1)# 如果检测器检测到人脸的个数为0,说明图像没有人脸,抛出一个异常if len(faces) == 0:raise ZeroFaces# 如果检测器检测到人脸的个数大于1,说明图像中不止一个人脸,抛出一个异常if len(faces) > 1:raise MoreThanOneFaces# 从dlib库中通过已经预训练好的关键点模型,返回一个人脸关键点预测器,用来标记人脸关键点predictor = dlib.shape_predictor("./predictor.dat")# 定位人脸关键点,faces[0]是开始内部形状预测的边界框,最后返回关键点的位置shape = predictor(image, faces[0])matrix = numpy.matrix([[point.x, point.y] for point in shape.parts()])return matrix
首先可以从dlib库中下载一个预训练模型,以此构建特征提取器,dlib库中获取的人脸检测器能够获取人脸的一个边界框矩形列表,每个矩形列表代表一个人脸,并作为特征提取器的输入最后返回特征点矩阵,每个特征点对应图像的一个x,y坐标。
2.获取人脸遮罩:
# 得到人脸遮罩
def acquire_shade(image, landmarks):shape = image.shape[:2]image = numpy.zeros(shape, numpy.float64)# 获取dlib库中识别眉毛和眼睛的识别点区域brow_and_eye_points = list(range(17, 48))# 获取dlib库中识别鼻子和嘴巴的识别点区域nose_and_mouth_points = list(range(27, 35)) + list(range(48, 61))# 利用OpenCV库中convexHull函数得到眉毛和眼睛的凸包,从而得到眉毛和眼睛的轮廓brow_and_eye_landmarks = cv2.convexHull(landmarks[brow_and_eye_points])# 得到鼻子和嘴巴的凸包,从而得到鼻子和嘴巴的轮廓nose_and_mouth_landmarks = cv2.convexHull(landmarks[nose_and_mouth_points])# 填充眉毛和眼睛、鼻子和嘴巴的轮廓cv2.fillConvexPoly(image, brow_and_eye_landmarks, 1)cv2.fillConvexPoly(image, nose_and_mouth_landmarks, 1)# 将矩阵转换为图像表示image = numpy.array([image, image, image]).transpose((1, 2, 0))# 使用高斯滤波对图像进行降噪处理plume_amount = 11# 画出的两个区域的轮廓向遮罩的边缘外部羽化扩展plume_amount个像素,可以隐藏不连续的区域image = (cv2.GaussianBlur(image, (plume_amount, plume_amount), 0) > 0) * 1.0return cv2.GaussianBlur(image, (plume_amount, plume_amount), 0)
在用dlib的库检测人脸的特征点时,可以得到一个由68个点组成的映射,将这些映射组合起来,可以识别人脸的不同的部位:
可以看到,其中:
- 点42~47对应左眼;
- 点36~41对应右眼;
- 点22~26对应左眼眉毛;
- 点17~21对应右眼眉毛;
- 点27~34对应鼻子;
- 点48~60对应嘴巴。
那么,遮罩要用到的区域由两个区域组成,分别是由眉毛和眼睛组成的区域,以及鼻子和嘴巴组成的区域,识别区域分别为点17 ~ 47和点27 ~ 34、48 ~ 60,最后识别出的区域由这两个区域构成,点为17 ~ 60。
3.获取仿射变换矩阵:
# 获取仿射变换矩阵
def acquire_aff_tra_matrix(head_landmarks, face_landmarks):# 获取要转换的识别点区域head_landmarks = head_landmarks[list(range(17, 61))]face_landmarks = face_landmarks[list(range(17, 61))]# 先将获取到的头部特征点矩阵的数字类型转换为浮点数,以得到更精确的列均值head_landmarks = head_landmarks.astype(numpy.float64)# 对矩阵的各列求均值,得到各列均值,即矩心col_aver1 = numpy.mean(head_landmarks, 0)# 按照普式分析法,各列减去矩心head_landmarks = head_landmarks - col_aver1# 对矩阵的各列求标准差,得到各列的标准差SD1 = numpy.std(head_landmarks)# 各列除以标准差,按照标准差缩放,减少了有问题的组件的缩放偏差head_landmarks = head_landmarks / SD1# 获取到的脸部特征点矩阵也按照上面的过程处理face_landmarks = face_landmarks.astype(numpy.float64)col_aver2 = numpy.mean(face_landmarks, 0)face_landmarks = face_landmarks - col_aver2SD2 = numpy.std(face_landmarks)face_landmarks = face_landmarks / SD2col_aver1 = col_aver1.Tcol_aver2 = col_aver2.TSD_div = SD2 / SD1 * 1.0# 使用奇异值分解计算旋转部分,返回三个矩阵,分别为左奇异值、奇异值、右奇异值head_landmarks_tra = head_landmarks.Tu, s, vh = numpy.linalg.svd(head_landmarks_tra * face_landmarks)# 公式假设矩阵在右边,有行向量,解决方案要求矩阵在左边,有列向量,所以进行转置R = u * vhR = R.T# 返回根据普式分析法得到的仿射变换矩阵return numpy.vstack([numpy.hstack((SD_div * R, col_aver2 - SD_div * R * col_aver1)), numpy.matrix([0., 0., 1.])])
得到了脸部图片和头部图片的两个面部特征点矩阵之后,需要确定一个对应关系,使脸部特征点的向量通过映射转换之后得到的新向量与头部特征点的向量尽可能相似,这样人脸图像迁移时才会更加准确,转换为数学问题就是,寻找一个标量s,二维向量T,正交矩阵R,使:
∑i=0n∣∣sRpi+T−qi∣∣2(n=67)\sum\limits_{i=0}^n||sR\mathbf{p_i} + T - \mathbf{q_i}||^2 (n = 67)i=0∑n∣∣sRpi+T−qi∣∣2(n=67)
表达式结果最小(pip_ipi和qiq_iqi是两个面部特征点矩阵的第i行)。
可以通过普式分析法来解决这类问题,通过减去质心,按标准差缩放,然后使用奇异值分解计算旋转得到仿射变换矩阵[s * R | T],从而解决这一问题。
4、5.仿射变换映射图片:
def warpAffine_face(face_image, aff_tra_matrix, head_image):warpAffine_image = numpy.zeros(head_image.shape, face_image.dtype)width = head_image.shape[1]height = head_image.shape[0]cv2.warpAffine(face_image, aff_tra_matrix[:2],(width, height), warpAffine_image,borderMode=cv2.BORDER_TRANSPARENT,flags=cv2.WARP_INVERSE_MAP)return warpAffine_image
6.修正图像边缘函数:
# 修正由于图像肤色和光线的不同导致脸部覆盖区域边缘的不连续问题
def revise_edge(head_image, face_image, head_landmarks):# 取左眼的识别点矩阵的矩心left_eye_points_col_aver = numpy.mean(head_landmarks[list(range(42, 48))], 0)# 取右眼识别点矩阵的矩心right_eye_points_col_aver = numpy.mean(head_landmarks[list(range(36, 42))], 0)# 取两个矩心之差,取范数后乘以一个模糊量系数得到一个为奇数的高斯内核eye_points_col_aver_sub = left_eye_points_col_aver - right_eye_points_col_averblur_fra = 0.6kernel_size = int(blur_fra * numpy.linalg.norm(eye_points_col_aver_sub)) + 1if kernel_size % 2 == 0:kernel_size = kernel_size - 1# 使用高斯滤波函数得到两个图像的高斯模糊值head_image_blur = cv2.GaussianBlur(head_image, (kernel_size, kernel_size), 0)face_image_blur = cv2.GaussianBlur(face_image, (kernel_size, kernel_size), 0)face_image_blur = face_image_blur + 128 * (face_image_blur <= 1.0)# 将使用到的值都转换为float64类型的face_image = face_image.astype(numpy.float64)head_image_blur = head_image_blur.astype(numpy.float64)face_image_blur = face_image_blur.astype(numpy.float64)# 使用脸部图片乘以头部图片模糊值,再除以脸部图片模糊值,得到修正边缘后的图像return face_image * head_image_blur / face_image_blur
可以通过改变脸部图像的颜色,用RGB缩放颜色,使其更加接近面部头像,从而解决边缘问题,这里使用了一个模糊算法,通过一个模糊系数乘以两眼之间的距离作为高斯内核,得到两个图像的高斯模糊值,然后用脸部图像乘以头部图像的高斯模糊值,再除以脸部图像的高斯模糊值,返回修正边缘后的图像。
7、8.和“Swap”按钮相关联的换脸进行过程:
# 进行换脸然后显示结果
def swapFace(self):_translate = QtCore.QCoreApplication.translate# 如果没有选择脸部图片或头部图片,那么不换脸if self.FACE_PICTURE_PATH == "" or self.HEAD_PICTURE_PATH == "":self.resultLabel.setStyleSheet("border-image:url(./icons/initial2.png);")self.resultLabel.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:20pt; font-weight:600;\">Please choose<br>Face first!</span></p></body></html>"))returntry:# 初始化中间的结果图片框self.resultLabel.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:20pt; font-weight:600;\"></span></p></body></html>"))# 根据图像的路径名读入图像,将图像转为3通道BGR彩色图像head_image = cv2.imread(self.HEAD_PICTURE_PATH, cv2.IMREAD_COLOR)face_image = cv2.imread(self.FACE_PICTURE_PATH, cv2.IMREAD_COLOR)# 获取头部图片遮罩和脸部图片遮罩headshade = acquire_shade(head_image, head_landmarks)faceshade = acquire_shade(face_image, face_landmarks)# 获取仿射变换矩阵aff_tra_matrix = acquire_aff_tra_matrix(head_landmarks, face_landmarks)# 利用仿射变换将脸部图像遮罩映射到头部图片中,生成符合头部图片坐标的新脸部遮罩warpAffine_shade = warpAffine_face(faceshade, aff_tra_matrix, head_image)# 利用仿射变换将脸部图像映射到头部图片中warpAffine_image = warpAffine_face(face_image, aff_tra_matrix, head_image)# 得到修正边缘之后的图像revise_edge_image = revise_edge(head_image, warpAffine_image, head_landmarks)# 将符合头部照片的新的脸部图片遮罩和头部图片遮罩结合为一个,尽可能表现出头部图片遮罩的特性combined_shade = numpy.max([headshade, warpAffine_shade], 0)# 应用遮罩,输出换脸图像result_image = head_image * (1.0 - combined_shade) + revise_edge_image * combined_shade# 在缓存文件夹中保存换脸图片cv2.imwrite("./tmp/tmp_result.png", result_image)# 设置换脸图片self.resultLabel.setStyleSheet("border-image:url(./tmp/tmp_result.png);")# 记录图片路径self.RESULT_PICTURE_PATH = "./tmp/tmp_result.png"# 捕捉多于一个人脸的异常except MoreThanOneFaces:_translate = QtCore.QCoreApplication.translateself.resultLabel.setStyleSheet("border-image:url(./icons/initial2.png);")self.resultLabel.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:24pt; font-weight:600;\">More than<br>One faces!</span></p></body></html>"))self.RESULT_PICTURE_PATH = ""# 捕捉没有人脸的异常except ZeroFaces:_translate = QtCore.QCoreApplication.translateself.resultLabel.setStyleSheet("border-image:url(./icons/initial2.png);")self.resultLabel.setText(_translate("MainWindow", "<html><head/><body><p align=\"center\"><span style=\" font-size:30pt; font-weight:600;\">Zero<br>Face!</span></p></body></html>"))self.RESULT_PICTURE_PATH = ""
用Python实现一个简单的智能换脸软件相关推荐
- 使用 Python 实现一个简单的智能聊天机器人
使用 Python 实现一个简单的智能聊天机器人 文章目录 使用 Python 实现一个简单的智能聊天机器人 简要说明 总体的思路 需要准备的环境 接收用户的语音输入, 并将其存为音频文件 调用百度A ...
- 使用 Python 实现一个简单的智能聊天机器人(附完整代码)
文章目录 简要说明 总体的思路 需要准备的环境 接收用户的语音输入,并将其存为音频文件 技术提升 调用百度AI接口, 识别音频文件并以文本信息返回 请求智能机器人, 发送文本信息, 返回智能聊天内容 ...
- python编写一个简单的程序验证码_Python实现一个简单的验证码程序
老师讲完random函数,自己写的,虽然和老师示例的不那么美观,智能,但是也自己想出来的,所以记录一下,代码就需要自己不断的自己练习,实战,才能提高啊!不然就像我们这些大部分靠自学的人,何时能学会.还 ...
- python界面设计-手把手教你用Python设计一个简单的命令行界面
原标题:手把手教你用Python设计一个简单的命令行界面 对 Python 程序来说,完备的命令行界面可以提升团队的工作效率,减少调用时可能碰到的困扰.今天,我们就来教大家如何设计功能完整的 Pyth ...
- python游戏最简单代码-如何利用Python开发一个简单的猜数字游戏
前言 本文介绍如何使用Python制作一个简单的猜数字游戏. 游戏规则 玩家将猜测一个数字.如果猜测是正确的,玩家赢.如果不正确,程序会提示玩家所猜的数字与实际数字相比是"大(high)&q ...
- python推荐系统-利用python构建一个简单的推荐系统
摘要: 快利用python构建一个属于你自己的推荐系统吧,手把手教学,够简单够酷炫. 本文将利用python构建一个简单的推荐系统,在此之前读者需要对pandas和numpy等数据分析包有所了解. 什 ...
- 怎么用python编简单游戏_用Python实现一个简单的算术游戏详解
用Python实现一个简单的算术游戏 #!/usr/bin/env python from operator import add, sub from random import randint, c ...
- 【Python】如何用python做一个简单的输入输出交互界面?
看到知乎上有人在问,如何使用Python做一个简单的输入输出交互界面? 交互界面就涉及到GUI编程. Python有很多GUI框架,功能大同小异. 其中比较出名的有「PyQT」.**wxPython. ...
- 基于python的系统构建_利用python构建一个简单的推荐系统
摘要: 快利用python构建一个属于你自己的推荐系统吧,手把手教学,够简单够酷炫. 本文将利用python构建一个简单的推荐系统,在此之前读者需要对pandas和numpy等数据分析包有所了解. 什 ...
最新文章
- 5分钟速通 AI 计算机视觉发展应用
- 谁来担责!无人驾驶汽车还需要汽车保险吗?
- python如何帮我在投资中获取更高收益
- 电机驱动板测试:是否可以输出150kHz高频信号?
- python模块之HTMLParser之穆雪峰的案例(理解其用法原理)
- iOS Swift GCD 开发教程
- php实现小说字典功能_四十章 PHP实现获取并生成数据库字典的方法
- ssm 异常捕获 统一处理_统一异常处理介绍及实战,看这篇就对了
- Ajax链接输出数据库
- 自然语言处理(2)之文本资料库
- 机器视觉行业的很多知识
- BZOJ2428[HAOI2006] 均分数据
- 宋宝华:公元1024年Linux内核的尘封往事
- android 贴吧列表,Android仿百度贴吧客户端Loading小球
- FIRST集合基本构造
- com.android.provision基本介绍
- 大O符号/大Ω符号/大Θ符号/小o符号/小w符号等各种算法复杂度记法含义
- 拾方易告诉你:什么叫POS机封顶
- ebcdic编码与ascII编码互转
- 10亿数据找出前100大的数据(网易大数据面试算法题)
热门文章
- 软件测试——系统测试总结报告模板
- Mybatis【#{}和${}的区别】
- ASP.NET Core微服务(七)——【docker部署linux上线】(RDS+API接口测试部分)
- OracleTimesten使用方法总结
- C#中的依赖注入那些事儿
- 合肥工业大学宣城校区大学生创新创业训练项目申报书:“基于Spark平台的人工智能知识的知识图谱构建”...
- 【BZOJ1452】[JSOI2009]Count(树状数组)
- Linux centosVMware Apache 配置防盗链、访问控制Directory、访问控制FilesMatch
- 2017-10-17 开源非英文关键词编程语言
- [一天一个小知识]instanceof