利用opencv实现抖音最强变脸术
Posted 亓斌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用opencv实现抖音最强变脸术相关的知识,希望对你有一定的参考价值。
最近一个“最强变脸术”又火爆抖音啦,还不知道的朋友建议先打开抖音,搜索“最强变脸术”看个十来个视频再回来看这篇文章。视频看起来炫酷,其实本质就是图像的各种变换组合到一块的结果。那我们能不能也搞出一个来玩玩?我利用周末刷了两天抖音,不停的暂停、继续… 最终在尝试了仿射变换
和透视变换
两种方案后,搞出了一个“低配版最强变脸术”。首先先来看看最终实现的效果(忽略gif颜色问题),也可以到http://www.iqiyi.com/w_19saz1z92h.html查看完整视频,然后从数学原理、opencv代码实现入手一步步的搞一个“最强变脸术”。
人脸关键点识别
看过“最强变脸术”的都知道,这个效果最基础的技术就是人脸识别。都2020年了,人脸识别当然不是多难的事了,可以选择的技术也很多,比如可以利用深度学习自己训练一个,也可以和我一样使用dlib
这个三方库。
dlib
用起来很简单,下面直接上代码了。
img = cv2.imread("./imgs/2.jpg")
dets = detector(img, 1)
shape = predictor(img, dets[0])
landmarks = []
for p in shape.parts():
landmarks.append(np.array([p.x, p.y]))
for idx, point in enumerate(landmarks):
cv2.putText(img, str(idx), (point[0], point[1]), fontFace=cv2.FONT_HERSHEY_SCRIPT_SIMPLEX,
fontScale=0.3, color=(0, 255, 0))
cv2.imshow("--", img)
cv2.waitKey()
运行上面的代码可以看到这样的结果:
请注意上面图中36
、45
、29
三个数字的位置,因为在下面仿射变换
的版本中我们要用到。
版本一:仿射变换实现
人脸关键点搞定后的第一次尝试,我是用的图像仿射变换
来实现的。通过不断观察,我拆解出了一下三种变换方式:
- 平移
- 缩放
- 旋转
平移
需要平移,是因为我们需要把两张图片上的人脸叠放到一块。平移的变换操作矩阵是:
[ 1 0 t x 0 1 t y ] \\left[ \\beginmatrix 1 & 0 & tx \\\\ 0 & 1 & ty \\endmatrix \\right] [1001txty]
例如我们要向右平移100个像素,向下平移50个像素,那么变换矩阵就应该是:
[ 1 0 100 0 1 50 ] \\left[ \\beginmatrix 1 & 0 & 100 \\\\ 0 & 1 & 50 \\endmatrix \\right] [100110050]
对应的运算是:
[ x ′ y ′ ] = [ 1 0 100 0 1 50 ] ∗ [ x y 1 ] \\left[ \\beginmatrix x' \\\\ y' \\\\ \\endmatrix \\right]=\\left[ \\beginmatrix 1 & 0 & 100 \\\\ 0 & 1 & 50 \\endmatrix \\right]*\\left[ \\beginmatrix x \\\\ y \\\\ 1 \\endmatrix \\right] [x′y′]=[100110050]∗⎣⎡xy1⎦⎤
即
x
′
=
1
∗
x
+
0
∗
y
+
100
∗
1
y
′
=
0
∗
x
+
1
∗
y
+
50
∗
1
\\begincases x'=1*x+0*y+100*1 \\\\ y'=0*x+1*y+50*1 \\endcases
x′=1∗x+0∗y+100∗1y′=0∗x+1∗y+50∗1
所以平移操作的本质就是对每个像素加上一个偏移量。下面是使用opencv对图像进行平移操作的代码:
img = cv2.imread("./imgs/2.jpg")
M = np.float32(
[
[1, 0, 100],
[0, 1, 50]
]
)
dst = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
cv2.imshow("", dst)
cv2.waitKey()
运行上面的代码可以看到这样的结果:
缩放
需要缩放,是因为我们在人脸对齐的时候需要尽可能的保证两张人脸大小一致。缩放的变换操作矩阵是:
[ f x 0 0 0 f y 0 ] \\left[ \\beginmatrix fx & 0 & 0 \\\\ 0 & fy & 0 \\endmatrix \\right] [fx00fy00]
fx代表x方向的缩放因子,fy代表y方向的缩放因子。所以如果我们想x轴放大1.5倍,y轴放大2倍的代码如下:
img = cv2.imread("./imgs/2.jpg")
M = np.float32(
[
[1.5, 0, 0],
[0, 2, 0]
]
)
dst = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
cv2.imshow("", dst)
cv2.waitKey()
运行上面的代码可以看到这样的结果:
旋转
需要旋转,是因为我们需要把两张图片上的人脸进行对齐操作。旋转的变换操作矩阵是:
[ c o s ( θ ) − s i n ( θ ) 0 s i n ( θ ) c o s ( θ ) 0 0 0 1 ] \\left[ \\beginmatrix cos(\\theta) & -sin(\\theta) & 0 \\\\ sin(\\theta) & cos(\\theta) & 0 \\\\ 0 & 0 & 1 \\endmatrix \\right] ⎣⎡cos(θ)sin(θ)0−sin(θ)cos(θ)0001⎦⎤
如果我们想要旋转30度,可以使用一下代码:
img = cv2.imread("./imgs/2.jpg")
theta = math.radians(-30)
M = np.float32(
[
[np.cos(theta), -np.sin(theta), 0],
[np.sin(theta), np.cos(theta), 0]
]
)
dst = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
cv2.imshow("", dst)
cv2.waitKey()
运行效果如下:
观察结果可能发现了,这次旋转的中心是在原点,如果我们想以任意点为旋转中心怎么办? opencv提供了一个函数:
getRotationMatrix2D(center, angle, scale)
center: 指定旋转的中心
angle: 旋转角度
scale: 缩放因子
这个函数还顺手解决了我们上面需要的缩放操作。可以比较下面代码和上面的效果:
img = cv2.imread("./imgs/2.jpg")
M = cv2.getRotationMatrix2D((img.shape[1], img.shape[0]), 30, 1)
dst = cv2.warpAffine(img, M, (img.shape[1], img.shape[0]))
cv2.imshow("", dst)
cv2.waitKey()
最强变脸术第一次实现
仿射变换
版本其实就是利用了以上三种变换方式的组合,首先先定义一个函数入口。
def compose_img(name, frames_per_transformer, wait_frames, *imgs):
pass
参数1:生成视频的文件名
参数2:每两张图像之前的变换(称之为1次迭代)需要多少帧
参数3:每个迭代后写入多少帧静态图,也就是每次迭代完成后图片保持多少帧不变
参数4:参与生成视频的图片集合
除了这个函数外,我们还需要几个辅助函数。
def to_video(name, width, height):
fps = 10
video_writer = cv2.VideoWriter(name, cv2.VideoWriter_fourcc('I', '4', '2', '0'), fps, (width, height))
return video_writer
def get_equation(x0, y0, x1, y1, pow_arg=1):
k = (y1 - y0) / (pow(x1, pow_arg) - pow(x0, pow_arg))
b = y0 - k * pow(x0, pow_arg)
def f(x):
return k * pow(x, pow_arg) + b
return f
def get_rotate_theta(from_landmarks, to_landmarks):
from_left_eye = from_landmarks[36]
from_right_eye = from_landmarks[45]
to_left_eye = to_landmarks[36]
to_right_eye = to_landmarks[45]
from_angle = math.atan2(from_right_eye[1] - from_left_eye[1], from_right_eye[0] - from_left_eye[0])
to_angle = math.atan2(to_right_eye[1] - to_left_eye[1], to_right_eye[0] - to_left_eye[0])
from_theta = -from_angle * (180 / math.pi)
to_theta = -to_angle * (180 / math.pi)
return to_theta - from_theta
to_video
函数主要是用来创建一个视频生成器的。get_equation
函数是用来生成一个根据时间变化的方程,主要用到了一次方程和二次方程。get_rotate_theta
这个函数是通过计算左右眼的夹角来估计人脸倾斜角度差值,下标的值可以参考第一张图片。
最后我们就要进入主函数的实现了,主要思路是遍历所有图片,每个迭代拿出当前图和下一张图,然后识别出两张人脸中的关键点,通过这些关键点我们可以计算出两张图在某一时刻需要的旋转角度、旋转中心、缩放比例、位移像素数等关键参数。最终我们再次迭代frames_per_transformer
次通过对两张图片分别做旋转
、平移
变换来达到效果。
def compose_img(name, frames_per_transformer, wait_frames, *imgs):
video_writer = to_video(".avi".format(name), imgs[0].shape[1], imgs[0].shape[0])
img_count = len(imgs)
for idx in range(img_count - 1):
from_img = imgs[idx]
to_img = imgs[idx + 1]
from_width = from_img.shape[1]
from_height = from_img.shape[0]
to_width = to_img.shape[1]
to_height = to_img.shape[0]
from_face_region, from_landmarks = face_detector(from_img)
to_face_region, to_landmarks = face_detector(to_img)
# 第一张图最终的旋转角度
from_theta = get_rotate_theta(from_landmarks, to_landmarks)
# 第二张图初始的旋转角度
to_theta = get_rotate_theta(to_landmarks, from_landmarks)
# 两张图的旋转中心
from_rotate_center = (from_face_region.left() + (from_face_region.right() - from_face_region.left()) / 2, from_face_region.top() + (from_face_region.bottom() - from_face_region.top()) / 2)
to_rotate_center = (to_face_region.left() + (to_face_region.right以上是关于利用opencv实现抖音最强变脸术的主要内容,如果未能解决你的问题,请参考以下文章