文章目录
- 数据准备
- 提取特征
- 三角剖分
- 仿射变换
- 无缝克隆
简单的换脸只需要四步
- 提取脸部特征点
- 三角剖分
- 仿射变换
- 无缝克隆
数据准备
想完成这个,opencv
足以胜任,首先第一步,自然是打开准备换脸的图像
import matplotlib.pyplot as plt
import cv2 as cv
im1 = cv.imread("swap_face1.jpg")
im2 = cv.imread("swap_face2.jpg")
ax = plt.subplot(1,2,1)
ax.imshow(im1)
plt.axis('off')
ax = plt.subplot(1,2,2)
ax.imshow(im2)
plt.axis('off')
plt.show()
效果如下,由于opencv
和matplotlib
默认的通道顺序不同,所以不必为颜色感到惊惶,而且接下来的换脸操作,也基本不涉及到颜色的问题。
最终效果如下
提取特征
接下来就开始提取脸部特征点,这里需要用到dlib
库,同时还得从这dlib.net下载68点的人脸模型。
import dlib
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68_face_landmarks.dat')
gray = cv.cvtColor(im1, cv.COLOR_RGB2GRAY)
# 此为识别出的人脸方框
rect = detector(gray, 1)[0]
# 此为人脸的特征点列表
landmarks = [[p.x, p.y] for p in predictor(im1,rect).parts()]
font = cv.FONT_HERSHEY_SIMPLEX
imTest = im1*1
for i, pt in enumerate(landmarks):
# 利用cv.circle给每个特征点画一个圈,共68个
cv.circle(imTest, pt, 3, color=(0, 255, 0))
# 利用cv.putText输出1-68
cv.putText(imTest, str(i+1), pt, font, 0.5, (0, 0, 255), 1,cv.LINE_AA)
cv.imshow("ma", imTest)
cv.waitKey(0)
这样一来,就把马老师的脸部特征选出来了
上面画个图只是为了掩饰特征点的选择逻辑,下面对两张图片均进行特征点提取操作
gray = cv.cvtColor(im1, cv.COLOR_RGB2GRAY)
rect = detector(gray, 1)[0]
pts1 = [[p.x, p.y] for p in predictor(im1,rect).parts()]
gray = cv.cvtColor(im2, cv.COLOR_RGB2GRAY)
rect = detector(gray, 1)[0]
pts2 = [[p.x, p.y] for p in predictor(im2,rect).parts()]
三角剖分
所谓换脸,就是在脸部的区域,将一个人的五官换成另一个人的五官,现在我们已经找到了人脸的特征点,接下来要做的,就是把相应五官的位置摘选出来,然后将这个区域的人脸进行替换。
第一步,是根据特征点来寻找脸部的凸包,通俗地说,就是把边界点按照顺序连接成一个多边形,这个很简单
import numpy as np
ind1 = cv.convexHull(np.array(pts1), returnPoints=False)
ind2 = cv.convexHull(np.array(pts2), returnPoints=False)
ptsSlct1 = [pts1[i] for i in ind1.reshape(-1)]
ptsSlct2 = [pts2[i] for i in ind2.reshape(-1)]
然后把这个凸包内部分割成许多个三角形,从而让各个区域可以更加完美地一一对应,常见方法是德劳内三角剖分,首先要用到opencv中的Subdiv2D进行初始化。
def outsideRect(x, y, rect):
flag = x < rect[0] or x > rect[2]
return flag or y < rect[1] or y > rect[3]
def triOutRect(tri, rect):
flag = False
for i in range(3):
flag |= outsideRect(tri[i*2], tri[i*2+1], rect)
return flag
def findTri(img, points):
rect = (0, 0, img.shape[1], img.shape[0])
subs = cv.Subdiv2D(rect)
for point in points:
subs.insert(tuple(point))
tris = []
for tri in subs.getTriangleList():
if triOutRect(tri, rect):
continue
inds = []
for i in range(0, len(points)):
t = np.array(tri).reshape(3,2)
flags = np.abs(t-points[i]) < 1.0
flags = flags[:,0] & flags[:,1]
inds.extend([i for f in flags if f])
if len(inds) == 3:
tris.append(tuple(inds))
return tris
然后调用
tris1 = findTri(im1, ptsSlct1)
tris2 = findTri(im2, ptsSlct2)
仿射变换
有了三角形还不够,接下来需要完成两个剖分区域的一一对应,即需要进行仿射变换
POLY_FILL_COLOR = (1.0, 1.0, 1.0)
# src是源图像
def affineTransform(src, triSrc, triDst, size):
warp_mat = cv2.getAffineTransform(np.float32(triSrc), np.float32(triDst))
dst = cv2.warpAffine(src, warp_mat, size, None, flags=cv2.INTER_LINEAR,
borderMode=cv2.BORDER_REFLECT_101)
return dst
def morphRegion(tri_1, tri_2, im1, im2):
x1, y1, w1, h1 = cv2.boundingRect(np.float32([tri_1]))
x2, y2, w2, h2 = cv2.boundingRect(np.float32([tri_2]))
offset1 = [((x - x1), (y - y1)) for x,y in tri_1]
offset2 = [((x - x2), (y - y2)) for x,y in tri_1]
mask = np.zeros((h2, w2, 3))
cv2.fillConvexPoly(mask, np.int32(offset2), (1.0, 1.0, 1.0))
im1_within_bounds = im1[y1:y1 + h1, x1:x1 + w1]
size_bounds_tri_2 = (w2, h2)
transformed_area = affineTransform(im1_within_bounds, offset1, offset2,size_bounds_tri_2)
transformed_tri = transformed_area * mask
x2e, y2e = x2+w2, y2+h2
im2[y2:y2e, x2:x2e] *= (1.0, 1.0, 1.0) - mask
im2[y2:y2e, x2:x2e] = im2[y2:y2e, x2:x2e] + transformed_tri
return im2
def affineTrans(tris, ind1, ind2, im1, im2):
im2_with_face1 = np.copy(im2)
for tri in tris:
tris1 = [ind1[pt] for pt in tri]
tris2 = [ind2[pt] for pt in tri]
morphRegion(tris1, tris2, im1, im2_with_face1)
return im2_with_face1
无缝克隆
这一步主要用到opencv提供的无缝克隆函数,即seamlessClone
POLY_FILL_COLOR = (255, 255, 255)
def getMask(hull, img):
hulls = [(pt[0], pt[1]) for pt in hull]
mask = np.zeros(img.shape, dtype=img.dtype)
cv.fillConvexPoly(mask, np.int32(hulls), POLY_FILL_COLOR)
rect = cv.boundingRect(np.float32([hull]))
center = (rect[0] + int(rect[2] / 2),
rect[1] + int(rect[3] / 2))
return mask, center
def mergeMask(hull, faceImg, img):
mask, center = getMask(hull, img)
return cv.seamlessClone(np.uint8(faceImg), img, mask, center, cv.NORMAL_CLONE)