使用 Python 的人脸检测技术来解决这个问题

2025-06-11

使用 Python 的人脸检测技术来解决这个问题

“DEAL WITH IT”是一个梗图,眼镜从屏幕外飞进来,砸到用户脸上。这个梗图的最佳范例以一种独特的方式展现了这一点。

今天我们将编写一个自动表情包生成器,使用任何包含人脸的静态图像作为输入。这段代码可以作为表情包 API 的一个很好的起点,或者你也可以使用视频输入构建自己的动画版本。

我从Erik Taheri那里得到了这篇文章的想法,以及他在浏览器中实现的这种效果的Javscript 版本

作为奖励,在文章的最后,我还附加了一个可以使用 OpenCV 通过网络摄像头实时实现效果的版本。

人脸检测和 Gif 创建工具

人脸检测和 Gif 创建工具

我们将使用 Dlib 的get_frontal_face_detector,以及我们在Snapchat Lens文章中使用的68 点形状预测模型

我们的程序将接受一个命令行参数,即输入图像。然后,它将使用 Dlib 中的人脸检测算法来检查是否有人脸。如果有,它将为每张人脸创建一个最终位置,也就是眼镜应该戴在哪里。

接下来,我们需要缩放和旋转眼镜,使其适合每个人的脸型。我们将使用 Dlib 的 68 点模型返回的点集来找到眼睛的质心,以及眼睛之间空间的旋转角度。

确定眼镜的最终位置和旋转角度后,我们就可以制作一个 gif 动画了,眼镜会从屏幕顶部进入。我们将使用 MoviePy 和一个make_frame函数来绘制这个动画。

自动 GIF 的架构

自动 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
PREV
使用 fastify 和 TypeORM 构建 REST API
NEXT
你需要了解的 TypeScript 实用程序类型