返回首页
⚙️技术札记
#Python#Pillow#AI绘画#表情包#工程化

用 AI + Python 从 0 到 1 做动态表情包,顺便封装成了可复用 Skill

9 分钟阅读有封面图
用 AI + Python 从 0 到 1 做动态表情包,顺便封装成了可复用 Skill

一、缘起:一个"没赚到钱"的静态表情包#

起因很朴素——我之前手搓过一套静态表情包,发出去零收益。复盘下来,静态图在微信表情平台的完播/下载转化天然吃亏,而且没有固定 IP 主角,用户记不住。

于是这次定了三个硬指标:

  1. 做成动态 GIF(240×240、透明底、单图 ≤ 500KB);
  2. 必须有固定主角 IP(品牌记忆点,决定复下载率);
  3. 强共鸣场景向(大学生日常:早八、干饭、被点名、摸鱼、卷王……),而不是纯可爱。

目标受众和主题定下来后,技术上的核心矛盾只有一个:怎么让 AI 生成的 16 张图,长着同一张脸。


二、技术拆解:把"做表情包"变成可工程化的流程#

我把它拆成四个阶段,每一阶段都有明确的输入输出,可以当流水线跑:

Phase 0  需求建模   → brief
Phase 1  IP 锁版    → 锁版稿 + 角色 DNA
Phase 2  分镜       → 16 张场景稿
Phase 3  动效       → 16 个 GIF
Phase 4  提交包     → 预览/横幅/图标/头像 + 文案

2.1 IP 一致性:锁版稿 + 图生图#

AI 出图最大的痛是角色漂移——尤其是头上那根标志性呆毛,每次生成形状都不一样。我的解法是:

  • 先生成一张锁版稿(正面、全身、高保真)作为所有后续调用的 input_image
  • 后续全部走图生图input_fidelity: high)而非文生图;
  • 在提示词里强制锁定特征,例如那根呆毛要写 permanently on top, same shape every time,否则 AI 会"自作主张"。

这一步是后面所有一致性的地基,省掉了 80% 的返工。

2.2 动效流水线:ImageGen 出帧 + Pillow 拼 GIF#

出图用 ImageGen,拼图用 Pillow。下面这几个坑,是这篇文章最想讲的部分。

坑 1:Windows + Git Bash 的路径解析#

在 Windows 的 Git Bash 里,如果你传 /f/work/...,ImageGen 会把它误解析成 F:\f\work\...,于是找不到文件、静默失败。

解法:一律用 F:/work/...(盘符 + 正斜杠),不要写 /f/...

# ❌ 错
/f/workBuddy/表情包/_final/01_png/scene08/frame1.png
# ✅ 对
F:/workBuddy/表情包/_final/01_png/scene08/frame1.png

坑 2:并行调用的隐式冲突#

早期我为了快,对同一场景并行发 ImageGen 请求,结果出现两种诡异丢帧:

  • 多个任务的 output_dir 互相覆盖;
  • 同毫秒生成的时间戳文件名互吞,丢了一张关键帧。

解法:严格串行调用,且每场景独立目录。慢一点,但零丢帧。

另外遇到过 RequestLimitExceeded.JobNumExceed(服务端并发上限),重试 1 次即可恢复,不用慌。

坑 3:Pillow 量化 RGBA 会报错#

拼 GIF 时要量化降色省体积,但 RGBA 图用 MEDIANCUT 会直接抛 ValueError

# ❌ RGBA 下会报错
img.save(out, "GIF", optimize=True, quantize=Image.Quantize.MEDIANCUT)
# ✅ 用 FASTOCTREE
img.save(out, "GIF", optimize=True, quantize=Image.Quantize.FASTOCTREE)

坑 4:AI 漏掉的"灰底雾"#

AI 生成的图经常带一层浅灰雾,普通的"白底转透明"清不掉——因为那不是纯白,是 (234,234,231) 这种近灰。提交到平台后背景会脏。

解法:写个 clean_bg,把"亮且低饱和"的像素转透明。鸭身的黄、橙嘴、黑描边因为饱和度高,天然不会被误杀:

def clean_bg(im, thr=180, sat=20):
    """亮度>thr 且 饱和度<sat 的像素 → 透明(吃掉 AI 灰底雾)"""
    data = list(im.getdata())
    out = []
    for r, g, b, a in data:
        if a and max(r, g, b) > thr and (max(r, g, b) - min(r, g, b)) < sat:
            out.append((r, g, b, 0))
        else:
            out.append((r, g, b, a))
    im.putdata(out)
    return im

2.3 平台提交包:体积预算与圆形裁切#

体积:palette-256 + 最高压缩#

微信表情平台对提交图有硬体积限制(横幅 ≤ 80KB、头像 ≤ 500KB)。真彩色 PNG 动辄 130KB+,超限。

im = im.convert("P", palette=Image.ADAPTIVE, colors=256)
im.save(out, "PNG", optimize=True, compress_level=9)

256 色对渐变和角色贴纸颜色影响极小(肉眼基本无 banding),体积却能压到 1/3。横幅从 136KB → 45KB,头像 28KB,全部达标。

头像:inscribed circle 安全区#

微信会把头像裁成圆。如果文字或关键元素贴边,裁完就被切掉一半。

解法:所有关键内容压在 inscribed circle 内,半径 ≈ 0.47 × 边长。圆外可以放纯背景色,丢了也无伤大雅。

一个反直觉的查看器 bug#

部分聊天/预览工具会把"近白色"像素渲染成棋盘格。我一度以为是图坏了,用 Pillow 逐点采样才发现像素全是实色——纯属查看器渲染抖动。

验证方法(别被眼睛骗):

from PIL import Image
im = Image.open("preview.png").convert("RGB")
print(im.getpixel((130, 8)))   # 应该输出一个实色元组,而非透明

缓解:提交图一律用饱和色(琥珀/橙),别用近白奶油——饱和度高的背景在任何场景都干净。


三、沉淀:把流程封装成可复用 Skill#

做完一遍后我意识到,这套流程的"知识"全在那些坑里,不沉淀就白踩了。于是我用 skill-creator 把它固化成了一个可安装的 Skill

sticker-pack-creator/
├── SKILL.md                  # 四阶段工作流(Agent 自动加载)
├── README.md                 # 安装/使用/注意事项
├── scripts/
│   ├── make_gif.py           # 参数化 GIF 拼装器(含 clean_bg + FASTOCTREE)
│   └── build_submission.py   # 提交包生成器(预览/横幅/图标/头像)
└── references/
    ├── pitfalls.md           # 所有坑:根因 + 修复 + 代码
    ├── wechat_specs.md       # 平台规格/体积/合规表
    └── character_dna_template.md  # 锁版稿 manifest 模板

设计上有几点体会:

  • 静态提交图不要重新 AI 生成,而是用 Pillow 拼真实 GIF 帧——保证角色 100% 一致,绝不会跑偏。
  • 文档和脚本分离SKILL.md 讲流程,references/ 讲坑,scripts/ 给能直接跑的代码。换一个 Agent 装上,照着走就能复现,不用重新 debug。
  • 打包有个隐藏坑:官方的 package_skill.py 如果输出目录设在 skill 内部,会把刚生成的 zip 递归塞回 zip 自己(self-nested)。输出目录一定要放到 skill 外部。

四、写在最后#

整套流程跑下来,最终交付是 16 张达标 GIF(全 ≤ 500KB、总 832KB)+ 4 张规范静态图 + 完整文案,可以直接投到微信表情开放平台。

顺手产出的那套表情包叫**「鸭摆摆」**——一只头顶弹簧呆毛的黄色小鸭,专讲大学生摆烂日常,三个出圈位(灵魂飞离 / 摊平 puddle / 卷王光束)动效拉满。如果你也在做同类内容,上面那套 Skill 已经打包成可安装分发包,装上去就能复用整条流水线。

做表情包这件事,难的从来不是"画一张图",而是让 16 张图长着同一张脸、还能塞进平台的每一个体积和尺寸限制里。工程化之后,它就成了可复制的事。


附:关键参数速查#

资产 尺寸 上限 关键技巧
单张表情 240×240 ≤ 500KB FASTOCTREE 量化
主页横幅 750×400 ≤ 80KB palette-256 + compress_level=9
图标 50×50 / 240×240 缩放面板图
头像 640×640 ≤ 500KB inscribed circle 安全区

感谢阅读。如果这篇文章对你有所启发,那便是写作最大的意义。
— 黑豆拾光录