微信表情包生产流水线
> 两次全流程实测跑通:静态「橘猫日常」16 张(从零到可上架素材)、动态「抽象小人有点得瑟」16 张 GIF(质检 16/16、零失败、总成本 ≈3 元、约 2h45m)。本文所有参数与坑都来自实跑。
前提与环境
- 无 GPU 也能跑:主线走百炼云 API,CPU 机器完全可行。不要建议装 ComfyUI 本地推理(CPU 慢到不可用),也不要建议付费订阅类方案。
- 自备百炼 API Key:设环境变量
DASHSCOPE_API_KEY,或写在项目.env文件里(DASHSCOPE_API_KEY=sk-xxx)。脚本只读不打印。 - Python 依赖:
pillow、numpy、scipy(后处理与质检)。
pip install pillow numpy scipy
export DASHSCOPE_API_KEY=sk-xxx # Windows: set DASHSCOPE_API_KEY=sk-xxx
技术路线(全部实测可用)
| 环节 | 用什么 | 说明 |
|---|---|---|
| 出图(定妆照) | 百炼 qwen-image-3.0 同步接口 |
一次请求直接返回图片 URL,无需轮询 |
| 换表情保一致 | 百炼 qwen-image-2.0-pro(旧名 qwen-image-edit-max 已下架,会 404) |
以定妆照为参考图 + 指令改动作表情,角色身份能保持住;该模型有 100 张免费额度 |
| 自动质检(视觉) | qwen-vl-max |
看图判一致性/抠图干净度/表情可读性 |
| 客观质检(像素) | Pillow + numpy + scipy | 权威判据,见下文质检节 |
| 抠图 / 统一构图 / 规格化 | Pillow | scripts/cutout_release.py |
| 叠字 | Pillow + 系统字体(如 C:\Windows\Fonts\msyhbd.ttc) |
文字自己叠,不让 AI 画字 |
| 动态 GIF | wan2.2-kf2v-flash 首尾帧生视频 |
480P,0.10 元/秒,每条固定 5 秒,实测约 16 秒出片 |
接口细节(endpoint / payload / 模型名 / 参数)见 references/bailian-image-api.md。
静态工作流(七步)
- 定选题:主题 + 角色 + 张数。微信静态表情只接受 16 或 24 张,先定 16 张跑通。
场景选日用高频(谢谢/收到/大笑/哭/生气/害羞/加油/疑问/震惊/无语/睡觉/点赞/求求了/溜了/辛苦了/比心)。
⚠️ 定妆照(角色基准图)不占 16 张名额,留作图标/横幅素材。 - 出定妆照:文生图一张干净角色图。提示词要求:纯色(纯白)背景、主体居中、四周留白、
画面无任何文字、扁平矢量/卡通风格(选一种并写死)。尺寸1328*1328。 - 批量换表情:以定妆照为参考图,逐张调 edit 模型。每条指令都必须重复同一段"保持约束":
> 严格保持这只\<角色\>的外形、毛色斑纹、头身比例、插画风格、线条粗细、纯白背景完全不变;
> 画面中绝对不要出现任何文字、字母、数字或符号,也不要出现纯白色的独立色块;
> 主体居中、四周留白。只改变以下动作与表情:\<具体动作\>
- 动作描述要具体到姿势/朝向/道具("两只前爪叉腰,鼓腮瞪眉,头顶三条怒气弧线"),含糊描述会得到辨识度低的结果
- 脚本做成可续跑(文件已存在则跳过),13 张实测约 6.5 分钟 - 客观质检(先)→ 5. 定向返工(只重做不合格的)→ 6. 抠图出规格 → 7. 视觉复检 + 验收
动态表情(GIF)路线 —— 关键帧 + 首尾帧生视频
在静态 16 张之上,走「两张关键帧 → 首尾帧生视频 → 抽帧 → 抠图 → 叠字 → GIF」:
- 定妆照(一致性锚点,只出一次)
- 每表情 2 张关键帧 A/B:A=起手、B=峰值,用
qwen-image-2.0-pro以定妆照为参考图改。
两张帧的动作差 = 之后视频的动感上限,描述要写具体姿势,别写"开心一点" wan2.2-kf2v-flash首尾帧生视频(480P,每条固定 5 秒)- 运动窗口切片 → 抽 13 帧(跳过首尾静止段,否则动感被吃掉一半)
- 逐帧抠图(视频帧不能用静态阈值法,要 alpha 斜坡 + 反白边)→ 整段统一构图(不是逐帧居中)
- 叠中文 + 程序化特效(汗滴/星星/问号/速度线/爆炸等,PIL 画,不让 AI 画)
- 正放 + 倒放(ping-pong) 拼 26 帧 → 循环绝对无缝
- 全局调色板 GIF,自动降色/降帧直到 ≤500KB → 出
preview.html同屏循环验收
为什么用首尾帧而不是"一句提示词图生视频":两端是可控的静态图 → 抠图基准干净(不吃
H.264 噪点)、动作幅度被两张帧夹住(不会动过头、不会自己加运镜)、而且更便宜。
参数、算法、坑与成本见 references/dynamic-sticker-pipeline.md。引擎脚本:
scripts/dyn_common.py(公共库)、scripts/dyn_gen.py(花钱三步 base/keys/video)、
scripts/dyn_render.py(本地渲染,不花钱,--debug 出品红底查边条带)、
scripts/check_raw.py(核对 A/B 关键帧尺寸,出完关键帧先跑它)、
scripts/dyn_sheet.py(成套总览图 4×4 首帧网格,本地免费)、
scripts/dyn_assets.py(平台投稿素材一次出全:主图/缩略图/横幅/封面/图标/赞赏图 + 上传对照.txt)。
微信素材规格(以平台上传页为准)
表情主图 240×240 PNG(静态)/GIF(动图) 16 或 24 张,单张 ≤500KB,**文件名纯数字**
表情缩略图 120×120 PNG 数量与主图一致且同顺序,≤200KB
详情页横幅 750×400 PNG/JPG ≤80KB(纯平色插画存 PNG optimize 就够,实测 59KB)
表情封面图 240×240 PNG ≤500KB,**平台明确要求不放文字**
聊天面板图标 50×50 PNG ≤100KB;50px 下大头特写比全身更好认
赞赏引导图/致谢图 750×560 / 750×750 选金额页 / 答谢页(开通赞赏才要)
⚠️ 这张表是记忆值,规范页是 SPA 抓不到 → 上传前以平台上传页显示的为准,不一样就改流水线常量全部重出,不要重做创意部分。
抠图与统一构图(核心技术)
抠图用连通域,不要用阈值一刀切。 白底角色图直接"把所有近白像素变透明"会把角色
内部的白色(眼睛高光、茶杯、白色胸毛)一起抠穿。正确做法:
near_white = (r >= 240) & (g >= 240) & (b >= 240)
lab, _ = ndimage.label(near_white) # scipy 连通域
border = 与画面四边相连的 label 集合
background = isin(lab, border) # 只清「连到边缘」的白
alpha = where(background, 0, 255)
alpha = maximum(gaussian_blur(alpha, 0.7), alpha) # 羽化只向外扩,不侵蚀主体
→ 按构造保证不会产生角色内部的透明洞(可用质检里的"透明孤岛"断言验证)。
统一构图(十六张"看着像一套"的关键):不要直接缩放原图(各图留白不同 → 大小不一)。
要按 alpha 包围盒裁切 → 按 max(bbox_w,bbox_h) = 画布 × 0.88 缩放 → 居中贴到方形画布。
同一 FILL 常量套全部图,主体视觉大小才一致。实测 16 张透明区落在 55%~70% 属正常。
质检:客观测量优先,视觉模型只作复检
先跑客观测量(scripts/pack_qa.py),四项硬指标:
| 指标 | 判据 | 抓什么 |
|---|---|---|
| 尺寸/模式 | == 目标尺寸 且 RGBA | 规格错、丢了透明通道 |
| 透明占比 | 15%~85% | 抠图没生效 / 抠过头 |
| 透明孤岛 | 不与画面边缘连通的透明块 ≈ 0 px | 抠穿(内部被挖洞) |
| 边缘侵入像素 | 横幅类 == 0 | 主体被画布裁切 |
| 单张体积 | < 500KB | 平台拒收 |
然后再用 qwen-vl-max 看总览图做复检(一致性/可读性/畸形)。
> 🔴 实测教训:横幅 A 版底部被裁掉 1261 像素,而视觉模型复检时明确说
> "均完整在画布内,无裁切" —— 视觉模型会漏判尺寸/边界类问题。
> 凡是能用像素算出来的(尺寸、裁切、透明、留白、色差)一律用测量判,视觉模型只负责
> "像不像、看不看得懂、脏不脏"这类主观判断。
给用户看成果时拼一张总览图(N 格 + 编号 + 名称,浅灰格底便于看透明边缘),一眼验收。
坑清单(都踩过)
- 返工脚本的文件名必须与批量脚本同源。实测:批量脚本写
raw/03_生气.png,返工脚本却写raw/06_生气.png→ 重做图写成了新文件,出图脚本仍用旧图,白跑一轮。→ 场景清单单一事实来源,返工脚本加防呆:目标文件不存在就报错跳过,绝不静默创建新名字的文件。 - 排版坐标要做边界校验。横幅第一版贴纸 240px 高、从 y=250 贴 → 超出 400 高的画布。横排 N 个元素时先算总宽再决定缩放/间距,留白不小于 8px,然后逐张重新校验。
- 一批只出 16 或 24 张(微信硬要求)。基准图不占名额。
- 先做无文字版(最易过审)。要带字时用 Pillow 叠字(选字体+描边+位置),比让生成模型画中文可靠得多。
- 透明底 vs 白底两条都出:贴纸在聊天里需要透明,但平台/预览有时要求白底 →
main/与main_white/各存一份。 prompt_extend=False(关闭智能改写)用于细节可控;watermark=False;seed可让出图相对稳定,但模型本身有概率性,同 seed 也不能保证完全一致。- 一致性靠 edit 链,不靠文生图重复描述:重新描述角色 16 次必然跑偏;以定妆照为锚点做 image-edit 才稳。
- 爪/手指数量、多余物件是常见小瑕疵;判"可接受"要看辨识度(卡通简化不算畸形),但看不懂是硬伤 → 优先重做"辨识度低"的。
- 大图(1328²)缩到 240² 用 LANCZOS 会在细结构边缘产生低 alpha 振铃 —— "透明孤岛"断言报出几十像素级的小孤岛多半是振铃而非真抠穿(阈值给几十 px 容差)。
- Pillow
getpalette()在quantize(colors=N)后只返回实际用到的颜色数(248 色 → 744 个值),np.array(...).reshape(256, 3)直接 ValueError → 补零到 256×3。 - 限流重试会掩盖真错误:外层封装吞掉错误响应体,「404 模型不存在」被内层最后一次「429 限流」盖住。→ 诊断模型/参数类问题必须把 response body 打出来;只在 429/Throttle 时退避重试,其他错误立刻原样抛。
- 模型名会过期:
qwen-image-edit-max2026-10 已 404(改名qwen-image-2.0-pro)。复用旧脚本前先跑scripts/models_probe.py或抓价格页核对模型名。 - 服务端报 429 未必是 QPS 打满 → 批量出图每次成功后主动
sleep(3)降速,比事后退避便宜。 - 🔴 批量任务"卡住/中断"先读日志里的异常类型,别先怀疑账号欠费。实测:跑到第 8 个表情整批停住,真因只是图片 CDN 一次
IncompleteRead断流。→ 下载一律走带重试的download_retry()(递进重试 + 校验返回体 ≥2KB);keys/video两步每张图/每条视频单独 try/except,单张失败只记一笔、继续跑,末尾汇总失败清单。
支持文件
references/bailian-image-api.md—— 百炼图像 API 实测档案:正确端点、模型名清单、请求体样例、参数说明、走错端点的报错文案、质检提示词模板references/dynamic-sticker-pipeline.md—— 动态表情(GIF)实测档案:三条路线对比、首尾帧生视频接口、运动窗口切片、视频帧抠图与反白边、GIF 调色板补零 bug、ping-pong 无缝、成本 ≈3 元/16 张scripts/cutout_release.py—— 连通域抠图 + 统一构图 + 多规格批量输出scripts/pack_qa.py—— 客观质检:单图(尺寸/透明/孤岛/体积)+ 横幅(边缘裁切检测)scripts/models_probe.py—— 核对账号现役模型名:碰到 404 时先跑它scripts/dyn_sheet.py/scripts/dyn_assets.py—— 动态专辑总览图 / 平台投稿素材一次出全
成本参考
静态 16 张:定妆照 + 16 次 edit,edit 模型有 100 张免费额度,基本 0 元。
动态 16 张:32 张关键帧(免费额度内)+ 16 段视频(0.10 元/秒 × 5 秒 × 16 = 8 元档位,实测免费秒数抵扣后 ≈3 元)。