利用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()

运行上面的代码可以看到这样的结果:

请注意上面图中364529三个数字的位置,因为在下面仿射变换的版本中我们要用到。

版本一:仿射变换实现

人脸关键点搞定后的第一次尝试,我是用的图像仿射变换来实现的。通过不断观察,我拆解出了一下三种变换方式:

  1. 平移
  2. 缩放
  3. 旋转

平移

需要平移,是因为我们需要把两张图片上的人脸叠放到一块。平移的变换操作矩阵是:

[ 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] [xy]=[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=1x+0y+1001y=0x+1y+501

所以平移操作的本质就是对每个像素加上一个偏移量。下面是使用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(θ)0sin(θ)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实现抖音最强变脸术的主要内容,如果未能解决你的问题,请参考以下文章

利用opencv实现抖音最强变脸术

利用opencv实现抖音最强变脸术

最近特别火的换脸视频怎么弄?

抖音技术开放日

18OpenCV Python 简单实现一个图片生成(类似抖音生成字母人像)

利用opencv实现视频捕捉功能