“换脸”案例源码详解 (Python)
faceswap_gui.py用于换脸,可与facemovie_gui.py身体互换源码(上一篇文章)对照观看
- 打开faceswap_gui换脸案例
- 点读取函数
- 数学变换方法
- 换脸函数
- 图像预处理函数
- 检测模式函数
- 绘制网格函数
- 获取关键点及标志的函数(2~8步与“身体互换”相同,未验证是否存在细微差别)
- 构建程序的图形化类
- 初始化处理函数(首先被调用)
- 程序入口
打开facemovie_gui.py换脸案例
在VScode中进入代码编辑状态。
导入相关库
'''faceswap_gui.py用于examples中的换脸'''
'''
导入基础包作用详解
'''
#导入包介绍开始
#cv2模块是OpenCV 2.0的简写,在计算机视觉项目的开发中,
#OpenCV作为较大众的开源库,拥有了丰富的常用图像处理函数库,
#采用C/C++语言编写,可以运行在Linux/Windows/Mac等操作系统上,能够快速的实现一些图像处理和识别的任务。
import cv2
#math模块提供了许多对浮点数的数学运算函数。
import math
#sys模块包括了一组非常实用的服务,内含很多函数方法和变量,
#用来处理Python运行时配置以及资源,从而可以与前当程序之外的系统环境交互。
import sys
#NumPy(Numerical Python) 是 Python 语言的一个扩展程序库,
#支持大量的维度数组与矩阵运算,
#此外也针对数组运算提供大量的数学函数库。
#在机器学习算法中大部分都是调用Numpy库来完成基础数值计算的。
import numpy as np
#导入包介绍结束
#BlazeFace是一个非常轻量级的人脸检测器,
#其在许多嵌入式设备中都可以达到超实时的效率
#在一些性能较好的手机gpu中甚至可达亚毫秒级
from blazeface import *
#cvs包是Aid内置的代替cv2的包,基本上cv2支持的函数cvs一样支持,cvs包在X模式下和非X模式下一样执行
#cvs更多详细介绍查看官网文档OpenCVhttps://www.aidlearning.net/showdoc/web/#/5?page_id=45
from cvs import *
# tflite_gpu,GPU加速代码由AID提供,TensorFlow Lite 支持多种硬件加速器。GPU 是设计用来完成高吞吐量的大规模并行工作的。
# 因此,它们非常适合用在包含大量运算符的神经网络上,一些输入张量可以容易的被划分为更小的工作负载且可以同时执行,通常这会导致更低的延迟。
# 在最佳情况下,用 GPU 在实时应用程序上做推理运算已经可以运行的足够快,而这在以前是不可能的
import tflite_gpu
##############################################################################
back_img_path=('models/rs.jpeg','models/wy.jpeg','models/zyx.jpeg','models/monkey.jpg','models/star2.jpg','models/star1.jpg','models/star3.jpg','models/star4.jpg')
#读图
faceimg=cv2.imread(back_img_path[0])
mod=-1
bfirstframe=True
点读取函数
'''
此函数读取点,输入文件路径,输出读取到的点集
'''
def readPoints(path) :
#初始化一个空的点数组
points = [];
#读取点集
with open(path) as file :
for line in file :
x, y = line.split()
points.append((int(x), int(y)))
#返回点
return points
数学变换方法
'''
此函数应用仿射变换
'''
#ApplyAffineTransform使用srcTri、dstTri、src计算sto
#并输出一副图像的大小。
#应用一个仿射变换,这个变换由srcTri(源三角形)和dstTri(目标三角形)计算出
#输出一幅图像
def applyAffineTransform(src, srcTri, dstTri, size) :
#仿射变换,又称仿射映射,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。
#仿射变换需要一个M矩阵,但是由于仿射变换比较复杂,一般直接找很难找到这个矩阵,
#opencv提供了根据变换前后三个点的对应关系来自动求解M的函数,这个函数就是:cv2.GetAffineTransform(src, dst)
#由给定的一对三角形找出仿射变换,warpMat为仿射变换矩阵
warpMat = cv2.getAffineTransform( np.float32(srcTri), np.float32(dstTri) )
#将得到的仿射变换应用至源图像
dst = cv2.warpAffine( src, warpMat, (size[0], size[1]), None, flags=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REFLECT_101 )
return dst
'''
检查某特定点是否在一个具体矩形内
'''
#判断某点是否在一个矩形内
#一个矩形由对角的两个点(四个坐标数据)组成
def rectContains(rect, point) :
#原理:point数组中的两个值分别代表该点的x坐标、y坐标
#rect数组中的四个值分别理解为左上角点的x坐标、左上角点的y坐标、矩形宽、矩形高
#如果某点坐标在矩形左边界以左
#或在矩形上边界以上
#则不存在于矩形内
if point[0] < rect[0] :
return False
elif point[1] < rect[1] :
return False
#如果某点坐标在矩形右边界以右
#或在矩形下边界以下
#则不存在于矩形内
elif point[0] > rect[0] + rect[2] : #rect[0] + rect[2] 即指矩形右边界的x坐标(以下y坐标同理)
return False
elif point[1] > rect[1] + rect[3] :
return False
#以上可能性排除即可判定点在矩形内
return True
'''
计算徳洛內三角形
'''
#计算徳洛內三角形
def calculateDelaunayTriangles(rect, points):
#创建subdiv(细分)
subdiv = cv2.Subdiv2D(rect);
#将各点插入subdiv
for p in points:
subdiv.insert(p)
#初始化三角形列表、徳洛內三角形、点集
triangleList = subdiv.getTriangleList();
delaunayTri = []
pt = []
#遍历三角形列表中的三角形
#每个三角形是一个长度为6的数组
for t in triangleList:
#在点集中加入三角形中每个点的x、y坐标
#append是内置函数,向列表末尾压入元素
pt.append((t[0], t[1]))
pt.append((t[2], t[3]))
pt.append((t[4], t[5]))
pt1 = (t[0], t[1])
pt2 = (t[2], t[3])
pt3 = (t[4], t[5])
if rectContains(rect, pt1) and rectContains(rect, pt2) and rectContains(rect, pt3):
ind = []
#根据坐标,从68个人脸检测器得到脸上的面部点
for j in range(0, 3):
for k in range(0, len(points)):
#这里参数取1.0,若从三角形得到的点与输入points中各点在x、y坐标上距离均小于1.0,则向ind中加入下标k
if(abs(pt[j][0] - points[k][0]) < 1.0 and abs(pt[j][1] - points[k][1]) < 1.0):
ind.append(k)
#三点构成一个三角形。
#这里的三角形列表对应于FaceMorph中的tri.txt
if len(ind) == 3:
delaunayTri.append((ind[0], ind[1], ind[2]))
pt = []
return delaunayTri
'''
仿射及透明混合
'''
#从img1和img2仿射并进行透明混合,得到img
def warpTriangle(img1, img2, t1, t2) :
#找到每个三角形(t1、t2)的外接矩形(r1、r2)
r1 = cv2.boundingRect(np.float32([t1]))
r2 = cv2.boundingRect(np.float32([t2]))
#以每个矩形的左上角为基点的偏移点
#初始化为空列表
t1Rect = []
t2Rect = []
t2RectInt = []
for i in range(0, 3):
t1Rect.append(((t1[i][0] - r1[0]),(t1[i][1] - r1[1])))
t2Rect.append(((t2[i][0] - r2[0]),(t2[i][1] - r2[1])))
t2RectInt.append(((t2[i][0] - r2[0]),(t2[i][1] - r2[1])))
# 填充三角形,得到遮罩(mask)
mask = np.zeros((r2[3], r2[2], 3), dtype = np.float32)
cv2.fillConvexPoly(mask, np.int32(t2RectInt), (1.0, 1.0, 1.0), 16, 0);
#将变换后的图像应用于小矩形贴片
img1Rect = img1[r1[1]:r1[1] + r1[3], r1[0]:r1[0] + r1[2]]
#img2Rect = np.zeros((r2[3], r2[2]), dtype = img1Rect.dtype)
size = (r2[2], r2[3])
img2Rect = applyAffineTransform(img1Rect, t1Rect, t2Rect, size)
img2Rect = img2Rect * mask
#将矩形贴片中的三角区域复制到输出图像
img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] = img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] * ( (1.0, 1.0, 1.0) - mask )
img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] = img2[r2[1]:r2[1]+r2[3], r2[0]:r2[0]+r2[2]] + img2Rect
换脸函数
'''
换脸
'''
def faceswap(points1,points2,img1,img2):
# # Read images
# filename1 ='sabina.jpg'
# filename2 ='bid.jpg'
# img1 = cv2.imread(filename1);
# img2 = cv2.imread(filename2);
img1Warped = np.copy(img2);
# Read array of corresponding points
# points1 = readPoints('sabina.txt')
# points2 = readPoints('bid.txt')
# 凸包(Convex Hull)是一个计算几何(图形学)中的概念。
# #在一个实数向量空间V中,对于给定集合X,所有包含X的凸集的交集S被称为X的凸包
#在二维欧几里得空间中,凸包可想象为一条刚好包著所有点的橡皮圈。
#用不严谨的话来讲,给定二维平面上的点集,
# 凸包就是将最外层的点连接起来构成的凸多边形,它能包含点集中所有的点。
# 寻找凸包
hull1 = []
hull2 = []
#cv2.convexHull是opencv提供的寻找凸包函数
hullIndex = cv2.convexHull(np.array(points2), returnPoints = False)
for i in range(0, len(hullIndex)):
hull1.append(points1[int(hullIndex[i])])
hull2.append(points2[int(hullIndex[i])])
# 由凸包边界上的点得到徳洛內三角形
sizeImg2 = img2.shape
rect