← 全部文章

从 AI 出图到 128 像素精灵:一条人物素材流水线

游戏里的人物精灵是 128×128。AI 出图给的是 1024。中间这段路,比”生成一张好看的图”远得多。

这篇把这条流水线完整记下来——因为里面每一条规则,都是用一次被否决换来的。

一、起点:叠层方案被推翻

最初的设想很自然:人物精灵 + 衣服精灵,叠起来就行。

用户的原话(逐字):

「直接把物品的精灵图叠加在人物精灵图上这个办法恐怕行不通,因为叠加上去看不出来是人物穿了这个衣服,所以我现在想法是这样,直接做人物的几组精灵图,对应人物没穿衣服、人物只穿了内衣、人物穿了普通衣服、人物穿了风衣、人物穿了皮夹克、人物穿了盔甲、人物穿了长袍、人物穿了道袍、人物穿了铠甲、人物穿了动力装甲、人物穿了外骨骼等等;用人物穿的最外层上衣来决定人物的精灵图。」

理由很硬:叠层只能表达”身上多了一块布”,表达不了”这个人穿着什么”。 实测的崩溃现场是——层帧和身体层画在同一个格子里,层帧画得比身体大就把整个人盖住(用户实机反馈:「皮夹克太大了把人物都挡住了」)。整套人物精灵按最外层上衣切换,是这类游戏图像包的通行做法。

于是落的契约是这样:

顺序(高→低)类别说明
1外骨骼最外层躯干装备的 outfit_category 字段
2动力装甲同上
3铠甲
4盔甲
5道袍
6长袍
7风衣
8皮夹克
9普通衣服兜底:任何未登记类别的衣物都算它
10内衣仅内衣层
11没穿(裸)躯干无任何覆盖

解析规则单一真源:取躯干部位所有已穿装备 → 按上表取最高优先级那件的类别 → 没有躯干覆盖就依次看内衣/裸 → 装备变更(穿/脱/丢/读档)必须立即重算。

最后这条是踩过的:丢东西之后外观没恢复,就是因为只在”穿”的路径上重算了。

契约里还有一条硬红线:

同一性别的所有套装精灵必须共用同一身体几何——身体 bbox 与落脚基线逐张一致,只有衣物轮廓不同。

理由同上:防的就是”衣服把人放大/挡住”。

二、定版配方:照抄,别再自己调

日系那一套,8 张(4 套 × 男女)过眼认可后固定下来的配方:

底模   <动漫底模>.safetensors
LoRA   pixel-art-xl.safetensors @ 0.6 / 0.6
CLIP   CLIPSetLastLayer -2
latent 832 × 1216(竖构图)
采样   dpmpp_2m + karras · 28 步 · cfg 7.0 · denoise 1.0 · 固定 seed
正向   <性别词>, solo, <外观>, slim build, full body, standing, front view,
       facing viewer, arms at sides, feet together, <衣服词>, pixel art, 16-bit,
       flat solid colors, crisp 1px pixels, clean outlines, limited palette,
       retro game sprite, centered, white background, simple background
负向   lowres, bad anatomy, bad hands, ... chibi, super deformed,
       oversized head, big head, child ...

三条机制支撑它,缺任一条就退回被否的老样子:

  1. 画布必须竖(832×1216)。 方形 1024 画布会把全身人物压扁成 Q 版大头——这是”反复出 Q 版”的真正原因。画布形状决定头身比,不是提示词能救的。
  2. LoRA 强度要和”最终降采样倍数”配对。 目标是 128px 帧时用 0.6:出的是”大像素像素画”(源图 1 像素 ≈ 6–8 个图像像素),降到 128 刚好约等于 1 帧像素,五官不糊。用 1.0 出的是细像素,8× 降采样之后脸就没了。1.0 适合直接看的大图,0.6 适合要往下缩的帧。
  3. 负向必须明确禁”Q 版词”:chibi / super deformed / oversized head / big head / child。

另外这套(日系)和另一套(美式、另一个底模 + LoRA 强度 1.0 + 1024² 方形画布)共用同一套后处理,但底模/强度/画布不同。改一套的配方不要顺手改另一套——两套并存的落地方式是各自一张图集,entry 名带 _anime 后缀。

三、最省时间的一步:读 PNG 里的元数据

这条是整条流水线里性价比最高的经验。

任何”照这个风格来”的输入——用户递的参考图、历史输出图——先读它内嵌的工作流。ComfyUI 的保存节点会把整条工作流 + 正负词 + 底模/LoRA/分辨率/步数/cfg/seed 全写进 PNG 的文本块:

from PIL import Image
import json
wf = json.loads(Image.open("任意一张 PNG").text["prompt"])   # 节点 id → class_type + inputs
# 底模        → CheckpointLoaderSimple.ckpt_name
# LoRA 强度   → LoraLoader.lora_name / strength_model / strength_clip
# CLIP 层     → CLIPSetLastLayer.stop_at_clip_layer
# 画布        → EmptyLatentImage.width / height
# 采样        → KSampler.seed / steps / cfg / sampler_name / scheduler
# 正负词      → CLIPTextEncode.text

试错三轮不如读一次元数据。 这条分支前 3 轮全是”凭印象调词”,考古一次就定版了。

拿到配方后的动作是原样复刻一张 + 并排对照(左 = 参考图 ‖ 右 = 复刻),确认画风对得上再泛化到整套。用户认可的参考图存一份进仓库,作为以后对齐风格的基准。

四、后处理:四道必需工序

这不是”优化”,是工序——少一道就不合格。

1. 抠底不能只看”与背景色差”。

AI 出图脚下有一圈渐变投影,跟白底差约 55,大于常用阈值 42 → 影被当成前景、又和人物连通 → 整片留下(实测外框被撑到 334px 宽,而真身约 110px)。

正解是从图像四边 flood fill,判据是「与背景色差 ≤ 42」或「饱和度 < 60 且明度在 105–178 之间」。明度上界把皮肤挡在外面;同时人物轮廓自然成了围墙,被身体包住的白内衣不会被误吃。numpy 迭代膨胀就够(1024² 约 1–3 秒),不需要额外的图像库。

2. 残留影要单独扫尾。

负向词里写 shadow under feet 压不住,每张都带。实测影的数值特征是「饱和度 3–20、明度 120–165」,所以在底部 25% 的行里清「饱和度 < 24 且明度 > 115」。这个区间两边都不误伤:暗色裤子的暗部明度只有 80–113,皮肤饱和度在 40 以上。

3. 彩色地面/台座要单独清。

动漫底模爱画粉红/棕色的台座,低饱和判据和脚宽裁剪都吃不掉。做法分两级:1024 尺度清”底部几行内、与人物中部调色板任何色都差 > 45”的像素;帧级再用小腿/靴子带(底部 30–8 行)取主色复核——不能用最底部那 13 行取主色,否则”脚下那块地面色”会混进主色,导致漏清。

(有一个反例要记住:裸体帧脚下的黑线是脚底描边,属于脚部主色,别当残留误删。)

4. 降采样用”限色 + 8×8 块众数投票”,不要面积平均。

高细节图(动漫、厚涂)用 BOX 面积平均必然产出过渡色:实测脸带半透明像素 121 个,糊成渐变块。改成 8×8 块内众数投票后,半透明像素 0,眼睛、鼻影、嘴都是硬色块。限色数取 96–128(32 会把脸切成白粉杂块,太低)。

配套一条:去飘点只删不替换。“孤立像素 → 邻域众数替换”式的去杂点会把脸蹭脏(一轮 5424 次改动,女版脸直接变浑,已回退)。只删「离主体水平中心 > 20px 的孤立飘点」。

五、过眼纪律(血泪最多的一节)

  • 断言”五官在不在”之前,把脸带放大 10 倍看。 缩略图或 3× 总览上,头发和杂色块看起来很像眼睛——这一轮就是在缩略尺度上宣称”8 张都有五官”,被用户一句”没五官啊”推翻。
  • 自检要看最终交付尺寸。 1024 好看 ≠ 128 能用。做法是拼一张对照图:左 1024 原图 ‖ 右 128 帧 4× 放大 + 棋盘透明底,再加脸带 10× 放大,落在 review/ 目录里逐张过眼再定稿。
  • 门禁全绿 ≠ 好看。 门禁只量结构(有效帧数、落脚线、透明底、可辨度),量不出崩坏。
  • 用户否掉一版时,第一个动作是量化定位,不是马上换词重出。 数出半透明像素数、洞像素数、头身比。这一轮有两个”直觉指错方向”的案例:脸没五官不是抠图吃掉的(洞像素 0,抠底前后逐格一致);脸糊是降采样方式与限色数的问题。
  • 成人向素材不做内容判定,交用户人工过眼;自己看图只为判断结构崩没崩。
  • 覆盖前先备份,让”回退一版”是一条命令的事。

六、落地口径与门禁

  • 源帧:128×128 RGBA,基线一律 y=120(最低不透明行),高度女 114 / 男 111;文件名不带 _128 后缀。
  • 两套风格各一张图集,打包脚本按类别优先级自动排布(一行一个性别、一列一个类别,幂等可重跑)。不要再写死布局 + 正则替换 JSON ——类别一多就得重写整段。
  • 现在的规模:48 张基础表 + 5 张风格变体 = 53 张;套装类别 11 个;源帧目录里有 1100+ 个文件(含怪物)。
  • 门禁:套装精灵门禁(72/72)、自检负例(6/6)、图集门禁(逐 entry + 透明底 ≥ 30%)、引擎侧还有透明底/建卡性别/外观三支。
  • 架构一变,旧门禁要同步口径。 外观门禁原来要求”同性别四态 bbox 逐张一致”,在”每状态独立出图”下必然红;改成「顶点/落脚基线一致 · 水平中心 ±3px · 宽度在 0.6–1.5 倍没穿帧之间」。红的是口径,不是素材。

还有一条很容易忘的:每加一张表都要同步登记进”图形包”清单,否则那张表在切换图形包之后会静默不加载——表在、图不显,排查成本极高。

七、走错过的路(别再回去)

  • 方形 1024 画布出全身角色 → 压扁成 Q 版(画布形状决定头身比);
  • 只用写实词 → 128 档脸无五官(脸只有约 10px,写实动漫脸在那里细节不够);
  • 只用可爱词 → 脸有了,身材成 Q 版(正解是两组词同时写,不是改头身比);
  • 提示词里写”头身比约 N 头” → 出角色设定稿(一排小人);
  • 两步法(动漫立绘 → img2img 像素化)→ 半透明能压到 0,但站姿歪(侧身、手举脸旁),质量不如竖构图直出;
  • 配方抄串(把 A 模型的 CLIP 设置用在 B 底模上)→ 风格当场不对,用户一眼看出。

八、这套流水线真正在解决什么

不是”生成好看的图”。是让 1024 的生成结果在 128 像素下仍然可辨,并且可复现、可门禁、可回退。

三件事支撑它:

  • 配方固定(含”LoRA 强度和降采样倍数配对”这种反直觉但决定成败的耦合);
  • 后处理是工序(四道,缺一不合格);
  • 过眼按最终尺寸、按放大倍数(不许在缩略图上做判断)。

最后一句话总结这段经历:AI 出图的上限由模型决定,能不能用由后处理和过眼纪律决定。