【娱乐】GIF 快速改图:用 Python 自动识别并替换头像

引子

一切的起源:学累了描改gif表情包来放松,导进procreate看到帧数100+的时候还不以为然,想着反正要改的部分是静态的,每帧随便移动一下,每下3秒钟,也就300多秒的事!然鹅......

博主不会用AE等软件,也不想下载......
没事没事,没什么事是不能用一行代码解决的!如果不行,那就两行代码

注意:仅使用于替换处和替换图片都不变的改图

  • 输入:一张动图(cat.gif),一张透明背景的图像(BB.png

  • 输出:动图中每一帧的部分位置被新图像替换后的新 GIF(cat_with_avatar.gif

不想在这个站点放我的画,就仅展示一下所输入动图的第一帧,有蜘蛛猫预警:

(预警完全无用啊 下次装个可折叠图像的插件)

步骤一:提取 GIF 的第一帧

先从 GIF 中提取第一帧(这步手动也很简单,但代码也很简单嗯嗯),用来观察并进行标注:

from PIL import Image
import os

gif_path = 'cat.gif'
output_dir = 'frames'
os.makedirs(output_dir, exist_ok=True)

# 提取第一帧
with Image.open(gif_path) as im:
    im.seek(0)  # 第一帧
    im.convert("RGBA").save(f"{output_dir}/frame_000.png")
    print("保存了第一帧为 frame_000.png")

步骤二:手动标记替换位置的圆心和轮廓

(因为这里要替换的图像是圆形的,所以用圆形追踪,别的形状也可以视作圆形来近似处理)
为了自动追踪替换位置,我们需要用鼠标点击:

  • 第一个点 是替换位置的中心;
  • 第二个点 是替换边缘轮廓上的一点,用于计算替换图像半径。
import cv2
import matplotlib.pyplot as plt

points = []

def click_event(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        print(f"点击位置:({x}, {y})")
        points.append((x, y))
        if len(points) == 2:
            cv2.destroyAllWindows()

# 载入第一帧
frame_path = 'frames/frame_000.png'
img = cv2.imread(frame_path)
img_copy = img.copy()

cv2.imshow("点击猫头中心 -> 然后点击轮廓", img)
cv2.setMouseCallback("点击猫头中心 -> 然后点击轮廓", click_event)
cv2.waitKey(0)

# 计算圆心和半径
(center_x, center_y), (edge_x, edge_y) = points
radius = int(((center_x - edge_x)**2 + (center_y - edge_y)**2) ** 0.5)

print(f"猫头圆心:({center_x}, {center_y}), 半径:{radius}")

例如我点击的结果是:

猫头圆心:(239, 44), 半径:38

步骤三:模板匹配 + 自动替换每一帧

我们从第一帧中裁出需要修改的区域作为模板,后续每一帧用 OpenCV 的 matchTemplate 方法匹配相同图像的位置,然后粘贴上我们准备好的透明底图像。

import os
import cv2
from PIL import Image, ImageSequence
import numpy as np

# ==== 路径配置 ====
gif_path = 'cat.gif'          # 原始动图
avatar_path = 'BB.png'    # 替换头像
output_gif = 'cat_with_avatar.gif' # 输出动图路径
output_frames_folder = 'replaced_frames'  # 存储每帧输出图

# ==== 初始点击结果 ====
center_x, center_y = 239, 44 # 坐标,上一步得到的结果
radius = 38 # 半径,上一步得到的结果
template_size = radius * 2

os.makedirs(output_frames_folder, exist_ok=True)

# 1. 打开 GIF 并提取第一帧作为模板
original = Image.open(gif_path)
first_frame = original.copy().convert("RGBA")
first_cv = cv2.cvtColor(np.array(first_frame), cv2.COLOR_RGBA2BGR)
template = first_cv[
    center_y - radius : center_y + radius,
    center_x - radius : center_x + radius
]

# 2. 加载准备好的图片
avatar = Image.open(avatar_path).convert("RGBA")
avatar_width, avatar_height = avatar.size

# 3. 遍历所有帧,模板匹配并叠加头像
frames = []
durations = []

for i, frame in enumerate(ImageSequence.Iterator(original)):
    durations.append(frame.info.get('duration', 40))  # 默认帧间隔40ms
    frame_rgba = frame.convert("RGBA")
    frame_cv = cv2.cvtColor(np.array(frame_rgba), cv2.COLOR_RGBA2BGR)

    # 模板匹配(在整张图里找最相似区域)
    result = cv2.matchTemplate(frame_cv, template, cv2.TM_CCOEFF_NORMED)
    _, _, _, max_loc = cv2.minMaxLoc(result)
    top_left = max_loc
    cx = top_left[0] + radius
    cy = top_left[1] + radius

    # 将头像贴到对应位置(以中心点为锚)
    paste_x = cx - avatar_width // 2
    paste_y = cy - avatar_height // 2

    new_frame = frame_rgba.copy()
    new_frame.paste(avatar, (paste_x, paste_y), avatar)

    # 保存新帧
    new_frame_path = os.path.join(output_frames_folder, f"frame_{i:03d}.png")
    new_frame.save(new_frame_path)
    frames.append(new_frame)

# 4. 合成新 GIF
frames[0].save(
    output_gif,
    save_all=True,
    append_images=frames[1:],
    duration=durations,
    loop=0,
    disposal=2
)

print(f"生成完成:{output_gif}")

效果展示

依旧和开头一样的原因,没有效果展示~ 有缘的话或许某天能在推上刷到呢
但总之效果很好,水了一张图还水了一篇博客(

总结与发散

总结

在这次偷懒(bushi)中,简单用到了图像处理的几个知识点:

  • Pillow 处理 GIF 帧;
  • OpenCV 实现模板匹配;
  • 鼠标交互 + 自动化图像合成。

更多的思考

1.用 AI 检测代替模板匹配

A. 使用目标检测模型(如 YOLOv5、YOLOv8,之前的多媒体安全小组汇报有尝试过)训练一个检测器
B. 也可以直接使用 MediaPipe / Dlib / OpenCV Haar cascades 进行识别
检测出猫头 bounding box 后,根据中心点叠加头像即可

2.如果目标不仅仅是位置变化,还会角度、大小、轻微旋转或者表情变化
可以使用光流法(Optical Flow)或 关键点跟踪(KLT、ORB、SIFT) 获取每帧的猫头角度、变形

根据这些变化,对头像图像做仿射变换后再贴上:

M = cv2.getRotationMatrix2D(center, angle, scale)
warped_avatar = cv2.warpAffine(avatar_np, M, (w, h))

3.可不可以实现像 TikTok 特效那种“动态表情贴纸”?

检测脸部关键点(类似美颜相机那种点),可以用 Dlib 的 shape_predictor_68_face_landmarks.dat 或 MediaPipe FaceMesh,通过关键点来判断头的方向、嘴巴是否张开、眼睛是否闭合等,然后动态变换头像或局部贴图,让贴图跟着脸动


没了,下次见 也不知道下次什么时候有心再写(shui)这种博客哈哈哈。
好险啊博主差点儿要学会AE了