使用 Python 的人脸检测技术来解决这个问题
“DEAL WITH IT”是一个梗图,眼镜从屏幕外飞进来,砸到用户脸上。这个梗图的最佳范例以一种独特的方式展现了这一点。
今天我们将编写一个自动表情包生成器,使用任何包含人脸的静态图像作为输入。这段代码可以作为表情包 API 的一个很好的起点,或者你也可以使用视频输入构建自己的动画版本。
我从Erik Taheri那里得到了这篇文章的想法,以及他在浏览器中实现的这种效果的Javscript 版本。
作为奖励,在文章的最后,我还附加了一个可以使用 OpenCV 通过网络摄像头实时实现效果的版本。
人脸检测和 Gif 创建工具
我们将使用 Dlib 的get_frontal_face_detector
,以及我们在Snapchat Lens文章中使用的68 点形状预测模型。
我们的程序将接受一个命令行参数,即输入图像。然后,它将使用 Dlib 中的人脸检测算法来检查是否有人脸。如果有,它将为每张人脸创建一个最终位置,也就是眼镜应该戴在哪里。
接下来,我们需要缩放和旋转眼镜,使其适合每个人的脸型。我们将使用 Dlib 的 68 点模型返回的点集来找到眼睛的质心,以及眼睛之间空间的旋转角度。
确定眼镜的最终位置和旋转角度后,我们就可以制作一个 gif 动画了,眼镜会从屏幕顶部进入。我们将使用 MoviePy 和一个make_frame
函数来绘制这个动画。
自动 GIF 的架构
该应用程序的架构相当简单。我们首先获取一张图片,然后将其转换为灰度 NumPy 数组。转换完成后,我们就可以把检测到的人脸输入到人脸方向预测模型中。
根据返回的脸部方向,我们可以选择眼睛,并缩放和旋转眼镜框以适合人的脸部。
我们可以积累一组面孔及其最终位置,并将它们附加到列表中。
最后,有了这个列表,我们就可以使用 MoviePy 创建一个绘制例程,然后生成我们的动画 gif。
编写代码
规划好代码架构后,接下来我们需要做的就是一步一步地构建代码。
我们首先导入所有工具,并从命令行获取图像:
import dlib
from PIL import Image
import argparse
from imutils import face_utils
import numpy as np
import moviepy.editor as mpy
parser = argparse.ArgumentParser()
parser.add_argument("-image", required=True, help="path to input image")
args = parser.parse_args()
有了这个,我们就可以调整图像大小以适应较小的宽度,这样我们的 gif 就不会变得太大,并导入我们的人脸检测器和形状预测器。
我们还可以打开要粘贴到图像上的眼镜和文字。
此时,我们还应该检测图像中是否有人脸。如果没有,则立即退出。
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('shape_predictor_68.dat')
# resize to a max_width to keep gif size small
max_width = 500
# open our image, convert to rgba
img = Image.open(args.image).convert('RGBA')
# two images we'll need, glasses and deal with it text
deal = Image.open("deals.png")
text = Image.open('text.png')
if img.size[0] > max_width:
scaled_height = int(max_width * img.size[1] / img.size[0])
img.thumbnail((max_width, scaled_height))
img_gray = np.array(img.convert('L')) # need grayscale for dlib face detection
rects = detector(img_gray, 0)
if len(rects) == 0:
print("No faces found, exiting.")
exit()
print("%i faces found in source image. processing into gif now." % len(rects))
太棒了!现在我们可以循环遍历每个检测到的脸部,并建立一个缩放和旋转后的眼镜列表,以及它们的最终位置。
faces = []
for rect in rects:
face = {}
print(rect.top(), rect.right(), rect.bottom(), rect.left())
shades_width = rect.right() - rect.left()
# predictor used to detect orientation in place where current face is
shape = predictor(img_gray, rect)
shape = face_utils.shape_to_np(shape)
# grab the outlines of each eye from the input image
leftEye = shape[36:42]
rightEye = shape[42:48]
# compute the center of mass for each eye
leftEyeCenter = leftEye.mean(axis=0).astype("int")
rightEyeCenter = rightEye.mean(axis=0).astype("int")
# compute the angle between the eye centroids
dY = leftEyeCenter[1] - rightEyeCenter[1]
dX = leftEyeCenter[0] - rightEyeCenter[0]
angle = np.rad2deg(np.arctan2(dY, dX))
# resize glasses to fit face width
current_deal = deal.resize((shades_width, int(shades_width * deal.size[1] / deal.size[0])),
resample=Image.LANCZOS)
# rotate and flip to fit eye centers
current_deal = current_deal.rotate(angle, expand=True)
current_deal = current_deal.transpose(Image.FLIP_TOP_BOTTOM)
# add the scaled image to a list, shift the final position to the
# left of the leftmost eye
face['glasses_image'] = current_deal
left_eye_x = leftEye[0,0] - shades_width // 4
left_eye_y = leftEye[0,1] - shades_width // 6
face['final_pos'] = (left_eye_x, left_eye_y)
faces.append(face)
眼镜最终位置调整好,缩放和旋转也调整好了,我们就可以制作影片了。我们会设置整个动图的时长,以及眼镜停止下落的时间,这样我们就可以把“处理此事”的文字显示在屏幕上。
# how long our gif should be
duration = 4
def make_frame(t):
draw_img = img.convert('RGBA') # returns copy of original image
if t == 0: # no glasses first image
return np.asarray(draw_img)
for face in faces:
if t <= duration - 2: # leave 2 seconds for text
current_x = int(face['final_pos'][0]) # start from proper x
current_y = int(face['final_pos'][1] * t / (duration - 2)) # move to position w/ 2 secs to spare
draw_img.paste(face['glasses_image'], (current_x, current_y) , face['glasses_image'])
else: # draw the text for last 2 seconds
draw_img.paste(face['glasses_image'], face['final_pos'], face['glasses_image'])
draw_img.paste(text, (75, draw_img.height // 2 - 32), text)
return np.asarray(draw_img)
你会注意到,我使用了图像作为文本叠加层,而不是 Pillow 内置的文本绘制功能。这样做是因为 Pillow 没有内置的文本描边功能。如果没有描边功能,文本在较亮的图像上会变得难以辨认。
最后,我们需要VideoClip
在 MoviePy 中创建一个对象,并传入我们的动画生成帧以及 fps。
animation = mpy.VideoClip(make_frame, duration=duration)
animation.write_gif("deal.gif", fps=4)
这样我们就完成了!
实时动画表情包
现在我们已经有了生成 gif 的基础集,调整我们的代码以实时与网络摄像头配合使用并不是太困难。
我们不用从命令行加载源图像,而是可以使用 OpenCV 作为源图像,并使用计数器跟踪动画。实现这一点的新代码非常简单,真正的重点是:
# I got lazy, didn't want to bother with transparent pngs in opencv
# this is probably slower than it should be
if dealing:
if current_animation < glasses_on:
current_y = int(current_animation / glasses_on * left_eye_y)
img.paste(current_deal, (left_eye_x, current_y), current_deal)
else:
img.paste(current_deal, (left_eye_x, left_eye_y), current_deal)
img.paste(text, (75, img.height // 2 - 32), text)
if dealing:
current_animation += 1
if current_animation > animation_length:
dealing = False
current_animation = 0
else:
frame = cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR)
cv2.imshow("deal generator", frame)
key = cv2.waitKey(1) & 0xFF
if key == ord("q"):
break
if key == ord("d"):
dealing = not dealing
这会创建一个计数器,并逐步显示已完成的帧数,以跟踪时间。利用这个计数器,我们可以将眼镜动画到最终位置。我们会观察用户是否按下了d
按键,并进行处理;当用户按下按键时,我们开始动画。
您可以在Github上阅读其余代码。
下一步该怎么做
我们已经成功构建了程序的第一部分,该程序可以用作 API 来自动生成模因。
通过将我们的程序连接到 Flask 之类的东西,我们可以显示一个网页,允许用户上传自己的图像,并返回完整的模因。
使用类似 youtube-dl 的工具,我们可以让用户粘贴 YouTube 网址视频来自动生成模因。
如果您错过了,代码可以在Github上找到。
如果你对更多类似的项目感兴趣,请在“用 Python 创作艺术”上注册一个账号。注册后,你将免费获得我新书的前三章,这将有助于我继续创作类似的项目。
鏂囩珷鏉ユ簮锛�https://dev.to/burningion/deal-with-it-in-python-with-face-detection-chi