开篇:为什么”脸不变”比”图好看”难十倍
做分镜的人几乎都经历过同一个崩溃时刻:第一张图气质完美,角色满意,甲方点头;等你按同一套提示词画到第 17 格,人物的眼睛变大了、鼻梁变塌了、发色偏了半档,再从第 30 格开始,脸已经彻底是另一个人。你把种子固定回去,好一点,但也只是好一点——因为 AI 绘画从来不是”同一台机器重复打印同一张照片”,它每次都是在一团概率分布里重新采样。种子只能锁住噪声起点,锁不住”这个人是谁”。
这就是人设锁定(Character Consistency)要解决的问题。而 IP-Adapter 是目前性价比最高的答案之一:它由腾讯 AI 团队提出,思路很朴素——用 CLIP Vision 把参考图压成一个特征向量,在扩散过程的交叉注意力里把它当作”额外的条件”注入进去。你可以把它粗暴地理解成”单图 LoRA”:不必训练,给一张图,模型就学会了”长得像这样”。相比真正训一个 LoRA(要几十张图、几十分钟到几小时、几个 GB 的产物),IP-Adapter 的边际成本几乎为零,改图改设定只要换参考图。
本文要走的是一条完整流水线:ComfyUI 搭节点 → IP-Adapter 锁人设 → 8G 显存跑分镜 → 帧校验 → FFmpeg 批量压制。不是演示”能出一张好图”,而是演示”能稳定产出一整组分镜并自动成片”。
先说一句贯穿全文的判断,也是我做了大量项目后最深的体会:8G 显存的关键不是能不能跑,而是能不能批量跑。一张图跑出来需要 6 分钟,不影响任何人;一百张图每张 6 分钟、中间崩三次、每次崩都要人工去点重新排队,这个流程就是不可用的。能跑不等于可交付。本文所有的参数取舍、脚本设计、节点组织,都服务于”可批量、可复现、可交接”这九个字。
本文适合谁
适合
- 手上有 8G 笔记本独显(如 RTX 4060 Laptop、3070 Laptop),想跑短剧分镜、漫画分格、产品视频故事板的创作者。
- 已经会用 ComfyUI 但被”脸崩”折磨过,想知道参数该怎么配而不是瞎试的人。
- 要把 AI 图变成”交付物”(成片)而不只是”素材”的团队。
不太适合
- 零基础、从没装过 ComfyUI 的人(本文会带安装,但默认你懂 Python 环境的基本概念)。
- 追求 4K 电影级写实的团队(8G 显存的上限决定了这条路现阶段走不通,建议上 24G 卡)。
一、环境准备:ComfyUI 与三件套模型
1.1 ComfyUI 是什么,为什么选它
ComfyUI 是一个节点式的 Stable Diffusion 工作流工具,本地运行,免费开源。它和 WebUI 最大的区别在于:所有操作都被拆成节点,节点之间用线连接,形成一张可保存、可复用的计算图。这意味着你的工作流本身就是一个文件(.json 或 .png 内嵌),可以版本管理、可以传给别人、可以参数化后批量执行。
对本文的流水线来说,这一点是决定性的。WebUI 里的”一次抽卡”没法自动化,而 ComfyUI 的 /prompt API 允许你从外部脚本提交整张计算图,改几个字段就是一张新图。批量渲染的基础,就建立在这里。
1.2 安装
Windows 用户最简单的方式是下载官方便携包(ComfyUI 的 release 页面有 ComfyUI_windows_portable),解压即用。手动安装则走 Python 环境:
# 1. 克隆仓库
git clone https://github.com/comfyanonymous/ComfyUI.git
cd ComfyUI
# 2. 创建虚拟环境(Python 3.10 - 3.12 均可)
python -m venv venv
# Windows
venv\Scripts\activate
# Linux / macOS
source venv/bin/activate
# 3. 安装依赖
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
pip install -r requirements.txt
# 4. 启动(8G 显存建议直接带低显存参数)
python main.py --lowvram --preview-method auto
启动后浏览器访问 http://127.0.0.1:8188。第一次打开是空画布,别慌,后面我们一层层搭。
1.3 必装插件:ComfyUI_IPAdapter_plus
原生的 IP-Adapter 节点功能很裸,社区里真正好用的是 cubiq 开发的 ComfyUI_IPAdapter_plus,国内社区的主流选择。它把 CLIP Vision 加载、权重、起止步数、多图批次、FaceID 支持全部封装成独立节点,参数暴露得非常细。
cd ComfyUI/custom_nodes
git clone https://github.com/cubiq/ComfyUI_IPAdapter_plus.git
# 重启 ComfyUI 后,右键菜单会出现 IPAdapter 分类
如果你用的是 ComfyUI-Manager,直接在管理器里搜 “IPAdapter plus” 安装更快,还能一并处理依赖。
1.4 三个关键模型,各放哪里
这一节是全文最容易踩坑的地方。IP-Adapter 不是”一个模型”,它是三个东西的组合:CLIP Vision 编码器 + IP-Adapter 权重 + 底模。三者必须匹配,不匹配就是报错,或者更糟——不报错但效果稀烂。
(1)CLIP Vision 编码器
负责把参考图变成特征向量。它是”眼睛”,决定了 IP-Adapter 能看懂什么。
- CLIP-ViT-H-14-laion2B-s32b-b79K:SD1.5 与 SDXL 通用,最常用的那一个。约 2.5GB,放在
models/clip_vision/。 - CLIP-ViT-bigG-14:SDXL 专用的更高精度版本,维度更大,对一些细节(服装纹理、配饰)保留更好,但显存占用也更高,放在同一个目录。
(2)IP-Adapter 权重
真正注入模型的那部分,按用途分版本:
ip-adapter-plus_sdxl_vit-h.safetensors:SDXL 通用/风格适配,对应 ViT-H 编码器。适合锁定整体气质、服装风格。ip-adapter-plus-face_sdxl_vit-h.safetensors:SDXL 人脸专用,对五官结构约束更强,适合特写镜头。ip-adapter-faceid-plusv2_sd15.bin:SD1.5 的 FaceID 版本,配合 InsightFace 做面部 embedding,比普通 face 版更”认人”,但生态在向 SDXL 迁移。
统一放到 ComfyUI/models/ipadapter/。注意 FaceID 类模型还需要 models/insightface/ 下的 buffalo_l 模型,节点首次运行会自动下载,国内网络可能需要手动放置。
(3)底模
SD1.5 系列(如各种 realistic 系 checkpoint,约 2GB)跑得快、显存占用低;SDXL 系列(约 6.5GB)画质和肢体稳定性明显更好,但显存开销大。选哪个,见后文第三节的显存对照表。
1.5 目录结构速查
ComfyUI/models/
├── checkpoints/ # 底模
├── clip_vision/
│ ├── CLIP-ViT-H-14-laion2B-s32b-b79K.safetensors
│ └── CLIP-ViT-bigG-14-laion2B-s39b.safetensors
├── ipadapter/
│ ├── ip-adapter-plus_sdxl_vit-h.safetensors
│ ├── ip-adapter-plus-face_sdxl_vit-h.safetensors
│ └── ip-adapter-faceid-plusv2_sd15.bin
├── insightface/
│ └── models/buffalo_l/
└── controlnet/ # Canny / Depth / OpenPose
二、节点连线:从参考图到一张分镜
这一节把搭图过程拆开讲。请务必在第一次跑通后立刻存成模板——节点留存就是你的生产线,改完一定要存成模板。我见过太多人调了一晚上参数、效果满意,第二天开新工程全忘了当时连了什么,从头再调一遍。这是纯粹的浪费。
2.1 最简可用链路
先跑通最小版本,再往上加东西。最小链路只需要 7 个节点:
Load Checkpoint (SDXL底模)
├─ MODEL ─────────────► KSampler
├─ CLIP ──────────────► CLIP Text Encode (正面) / (负面)
└─ VAE ───────────────► VAE Decode
Load Image (角色参考图, 建议裁成正方形)
└─IMAGE─► IPAdapterUnifiedLoader (preset: PLUS (high strength), 加载CLIP-ViT-H)
└─IPADAPTER/FACEID─► IPAdapter (weight=0.75, start_at=0, end_at=1)
├─MODEL─► KSampler.model
└─(正/负条件)─► KSampler.positive / negative
Empty Latent Image (1024×1024) ─► KSampler.latent_image
KSampler ─► VAE Decode ─► Save Image
写成文字版就一句话:Load Image → IPAdapterUnifiedLoader → IPAdapter → KSampler → VAE Decode → Save Image,同时 CLIP Text Encode 的正负条件也要经过 IPAdapter 节点(因为它会把参考特征融进 conditioning,不走这一步等于没接)。
IPAdapterUnifiedLoader 的坑
新版插件推荐用 Unified Loader 自动匹配编码器和权重,省去手动选文件。但它的 preset 名字有歧义:PLUS (high strength) 指的是 plus 权重,不是”强度高”。第一次使用时去节点里点开下拉看清楚文件名再选,别凭名字猜。
为什么条件要经过 IPAdapter 节点
这是新手最常见的错误连线。很多人把 CLIP Text Encode 直接连到 KSampler 的 positive,然后 IPAdapter 只连了 MODEL,结果发现”完全没效果”。原因在于:IP-Adapter 是把图像特征拼接进文本条件里一起送去采样的,只改模型不改条件,只有一半生效。正确做法是:文本条件先进入 IPAdapter 节点,IPAdapter 节点输出再连 KSampler。
2.2 加 ControlNet:锁住构图和骨骼
IP-Adapter 管”长什么样”,ControlNet 管”站在哪、什么姿势、画面结构”。两者是互补的,但也容易打架。
Load Image (构图参考/首帧线稿)
└─► Canny / Depth / Open Pose 预处理器
└─► ControlNet Apply Advanced (strength 0.3~0.5)
└─►(条件输出)──► IPAdapter 节点之前或之后接入 KSampler
三个 ControlNet 的分工:
- Canny:锁轮廓。适合已有线稿、要严格保持角色外形和服装剪影的分镜。
- Depth:锁空间。适合需要保持前后景关系、纵深透视一致的镜头序列。
- OpenPose:锁骨骼。做动作分镜(走位、打斗、回头)时几乎是必选,因为 AI 对人体的自由度太高,不锁就会乱跑。
强度建议总强度 1.0 以下。IP-Adapter 0.7 + ControlNet 0.5 已经是比较”硬”的组合,超过这个数值画面会明显僵硬、细节发死。如果你只是想”大致保构图”,把 ControlNet 压到 0.3-0.4 就够了——权重越低,留给模型的发挥空间越大,与 IP-Adapter 的冲突也越少。
2.3 双 IP-Adapter 叠加:通用 + 人脸
单挂一个 IP-Adapter,锁得住整体但锁不住脸;单挂 face 版,锁得住脸但服装风格飘。工业做法是叠两层:
Load Image (全身/半身参考)
└─► IPAdapterUnifiedLoader
└─► IPAdapter A (权重 0.85, 通用plus, 管风格/服装)
└─► IPAdapter B (权重 0.7, face版, 管五官, 接在A之后)
└─► KSampler
顺序上一般”通用在前、人脸在后”,因为后接入的节点对最终条件的覆盖更直接。两个节点串联时,A 输出的 MODEL 和 conditioning 都传给 B,B 再输出给 KSampler。
2.4 FaceID 的额外接线
如果你用 FaceID(SD1.5 版本),链路多一个 InsightFace 节点:
Load Image → IPAdapter FaceID (insightface=buffalo_l, weight=0.8, weight_faceidv2=1.0)
└─► KSampler
FaceID 的 weight 和普通 IP-Adapter 的 weight 是两个独立的旋钮,前者控制”人脸 embedding 影响多少”,后者控制”整体参考图影响多少”。两者搭配的取值,见第四节表格。
三、8G 显存:算清楚账再动手
3.1 1024×1024 下的真实开销
很多人对显存的理解停留在”底模多大就占多大”,这是错的。实际开销由四部分组成:底模常驻 + IP-Adapter 分支 + ControlNet + 采样过程的激活值(attention 中间结果)。到 1024 分辨率,激活值这部分已经不能再忽略。
以一个基准场景(1024×1024、SDXL 底模、1 个 IP-Adapter、1 个 ControlNet)估算:
- SDXL 基础:约 10GB(底模 + 文本编码器 + VAE + 采样激活)
- IP-Adapter:2-3GB(含 CLIP Vision 编码器常驻)
- 1 个 ControlNet:约 1.5GB
- 合计:约 13.5-14.5GB
这组数字非常关键:它说明在 8G 显存上,按原样跑 1024 的 SDXL + IP-Adapter + ControlNet,物理上是不可能的。任何声称”8G 能直接跑 1024 SDXL 全链路”的说法,要么开了极低的分块导致速度慢到不可用,要么就是用了 OOM 后自动回落到 CPU。
3.2 三条可行路线
路线 A:降分辨率 + 轻量模型
把出图分辨率降到 768×768 或 832×1216(竖屏分镜常用),显存需求直接腰斩到 7GB 左右。配合 --lowvram 启动参数,底模会按需在显存和内存间搬运,能跑,但每步会多几十毫秒。
路线 B:轻量模型替代(推荐)
用参数量更小的模型替代 SDXL。例如 Wan 2.2 1.3B 的量化版本,1.3B 参数在 int8/fp8 量化下模型本体只有 1-2GB,配合分块(tiling)能在 8G 上跑起来,代价是细节和风格控制力不如 SDXL。这条路适合”量大于质”的故事板阶段——先把构图、人物位置、镜头逻辑定下来,精修再用大显存机器补。
路线 C:分块 + 低显存模式
ComfyUI 有两档低显存:--lowvram(把模型拆成小块,用哪块加载哪块)和 --novram(更激进,几乎全部放内存,速度极慢)。另外 VAE Decode 阶段可以启用 tiled decode,把大图分块解码,避免解码时 OOM。
启动参数建议:
python main.py --lowvram --reserve-vram 0.6 --preview-method none
--reserve-vram 0.6 给系统和浏览器留 600MB,否则跑图时切个网页就可能崩。--preview-method none 关掉采样预览,预览本身也占一两百 MB,批量跑的时候没必要看。
3.3 各显存档位对照表
下面这张表是我按实际项目整理的,可以直接当选型参考:
| 显存 | 推荐底模 | 分辨率 | IP-Adapter 层数 | ControlNet | 启动参数 | 单图耗时(参考) | 适用场景 |
|---|---|---|---|---|---|---|---|
| 6GB | SD1.5 | 512-640 | 1 层 | 1 个,强度≤0.4 | –lowvram | 25-40s | 草稿、构图探索、故事板 |
| 8GB | SD1.5 / Wan 2.2 1.3B 量化 | 768 | 2 层(通用+FaceID) | 1 个,强度 0.3-0.5 | –lowvram –reserve-vram 0.6 | 45-90s | 分镜主力档,本文推荐配置 |
| 8GB | SDXL | 1024 | 2 层 | 叠加使用会 OOM | –lowvram(极慢) | 5-8min | 只做单张精修,不适合批量 |
| 12GB | SDXL | 1024 | 2 层 | 1 个,强度 0.5 | 默认 | 35-60s | SDXL 分镜的起步档 |
| 16GB | SDXL | 1024 | 2 层 + FaceID | 1-2 个 | 默认 | 25-40s | 稳定量产,可边跑边做别的 |
| 24GB | SDXL | 1216-1536 | 多层 + 多参考 | 2-3 个 | 默认 | 20-35s | 高分辨率交付级 |
注意 8GB/SDXL/1024 那一行:它”能跑”,但单图 5-8 分钟意味着 100 格分镜要跑 8 小时以上,且任何一次轻微的内存抖动都可能让整批失败。这正是那句判断的现实版本——能跑,和能批量跑,是两回事。
四、权重配置:四组可直接抄的参数
参数不是玄学,但确实需要平衡。IP-Adapter 权重过高,画面会”糊参考图的质感”——纹理变平、光影变死、姿势被参考图拽住;过低则角色不像。下面四组配置覆盖了从”轻微参考”到”极限锁定”的常见需求。
4.1 人脸场景:单挂 0.7-0.85
如果只做人物特写、且参考图清晰,单挂一个 face 版 IP-Adapter 通常够用。人脸权重的安全区间是 0.7-0.85:
- 0.7:保留更多表情和光影变化,适合”同一个人不同情绪”的镜头。
- 0.8:默认值,结构与气质兼顾。
- 0.85:极像,但表情会偏僵、笑容不自然,适合证件照式的正脸分镜。
4.2 标准一致性:IP-Adapter 0.7 + FaceID 0.8
一集分镜 30 格以上、包含正侧背多角度时,用双 IP + FaceID 的组合最稳。
# 标准一致性配置(推荐日常使用)
底模: SDXL (或 SD1.5 + faceid-plusv2)
IP-Adapter: ip-adapter-plus_sdxl_vit-h, weight = 0.70
FaceID: weight = 0.80, weight_faceidv2 = 1.0
采样: steps = 30, cfg = 7.0, sampler = dpmpp_2m, scheduler = karras
分辨率: 832×1216 (竖屏) / 1216×832 (横屏)
ControlNet: OpenPose 0.4 (仅动作镜头)
30 步 CFG 7.0 是一个经过大量实战验证的平衡点。步数再低(20 以下),IP-Adapter 注入的特征来不及收敛,会出”半像不像”的怪脸;步数加到 35 以上收益递减,时间却翻倍。
4.3 最大一致性:IP-Adapter 0.85 + FaceID 0.9
用于角色出场镜头、定妆照、海报主视觉——这些图数量少但要求最高。
# 最大一致性配置(少量精修用)
IP-Adapter: weight = 0.85
FaceID: weight = 0.90, weight_faceidv2 = 1.0
采样: steps = 40, cfg = 7.5, sampler = dpmpp_2m, scheduler = karras
分辨率: 1024×1024 或 1024×1536
ControlNet: 建议不挂,避免与高强度 IP 冲突
备注: 出图后常需 1-2 轮局部重绘修正手部
40 步 + CFG 7.5 是为了给高强度条件留出足够的收敛时间。CFG 再往上推,画面会过饱和、油光明显,这是 SDXL 的通病,别硬调。
4.4 权重取值场景对照表
这是第二张横向对照表,把 IP-Adapter 权重和 FaceID 权重放在一起,按场景给建议值:
| 场景 | IP-Adapter 权重 | FaceID 权重 | 步数 | CFG | 风险提示 |
|---|---|---|---|---|---|
| 远景/全身,人物占画面比例小 | 0.55-0.65 | 0.7 | 28 | 7.0 | 权重再低脸就完全不像,远景宁可用景别遮掩 |
| 中景日常对话镜头 | 0.70 | 0.80 | 30 | 7.0 | 标准配置,直接套用 |
| 面部特写/情绪镜头 | 0.60 | 0.85 | 30 | 7.0 | 特写时 IP 权重过高会压住表情,要靠 FaceID 兜底 |
| 动作镜头(跑、打、跳) | 0.65 | 0.80 | 32 | 7.0 | 必须配 OpenPose 0.4-0.5,否则脸会和身体脱节 |
| 定妆照/出场主视觉 | 0.85 | 0.90 | 40 | 7.5 | 表情僵硬,需局部重绘修嘴 |
| 换装镜头(衣服变、人不变) | 0.50 | 0.90 | 34 | 7.0 | IP 权重必须降,否则新衣服会被参考图拽回去 |
| 侧脸/背身镜头 | 0.75 | 0.70 | 32 | 7.0 | FaceID 依赖正脸 embedding,侧背时精度骤降 |
最后两行值得多说。换装镜头是最容易失败的场景——你希望人不变、衣服变,但 IP-Adapter 看到的是”整张参考图”,它分不清哪部分是脸、哪部分是衣服。所以要把脸交给 FaceID 死锁,把通用 IP 权重压到 0.5,让底模有空间去画新衣服。侧背镜头同理:FaceID 的 embedding 是在正脸上提取的,角度一变,精度就塌,此时反而不如单挂通用 IP。
4.5 起止步数的小技巧
IPAdapter 节点上有 start_at 和 end_at 两个参数,控制参考特征在采样的哪个阶段生效。默认 0 到 1 全程生效。如果需要”构图由 ControlNet 主导,而风格由 IP-Adapter 决定”,可以把 IP-Adapter 设成 start_at=0.1, end_at=0.8,前 10% 和最后 20% 的步数留给其他条件。这个技巧在双条件打架时特别有效,比降权重更精细。
五、分镜渲染:把单张变成一整集
5.1 分镜脚本的字段设计
批量渲染的前提是”把每一格变成一个可传参的记录”。我建议用一个 YAML 或 JSON 文件描述整集分镜:
{
"character": "lin_xi",
"base_seed": 20260215,
"shots": [
{"id": "S01", "desc": "全景,林夕站在雨夜的街口,撑黑伞", "pose": "standing_front", "cn_strength": 0.35},
{"id": "S02", "desc": "中景,林夕侧头看向巷子深处", "pose": "looking_side", "cn_strength": 0.4},
{"id": "S03", "desc": "特写,眼神警觉,雨滴打在伞面", "pose": "closeup", "cn_strength": null},
{"id": "S04", "desc": "近景,转身迈步走向巷子", "pose": "walking_away", "cn_strength": 0.45}
]
}
关键字段是 id(用于命名和后续压制排序)和 base_seed(所有镜头共用一个基础种子,只按序号做微小偏移,能显著减少风格漂移)。
5.2 用脚本调用 ComfyUI API
ComfyUI 默认监听 127.0.0.1:8188,提供 /prompt 接口。把工作流在界面里搭好后,点”Save (API Format)”导出一份 API JSON,然后用 Python 循环提交:
import json, time, requests, copy
API = "http://127.0.0.1:8188"
with open("workflow_api.json", "r", encoding="utf-8") as f:
wf = json.load(f)
# 节点 id 需要和你导出的 JSON 对应,先打开文件确认
POS_ID = "6" # CLIP Text Encode 正面
SEED_ID = "3" # KSampler
REF_ID = "10" # Load Image (参考图)
shots = json.load(open("shots.json", encoding="utf-8"))["shots"]
for i, shot in enumerate(shots):
g = copy.deepcopy(wf)
g[POS_ID]["inputs"]["text"] = shot["desc"] + \
", cinematic still, consistent character, film grain"
g[SEED_ID]["inputs"]["seed"] = 20260215 + i
g[REF_ID]["inputs"]["image"] = "refs/lin_xi.png"
r = requests.post(f"{API}/prompt", json={"prompt": g})
if r.status_code != 200:
print(f"[FAIL] {shot['id']}: {r.text}")
continue
pid = r.json()["prompt_id"]
print(f"[QUEUE] {shot['id']} -> {pid}")
# 轮询直到完成
while True:
h = requests.get(f"{API}/history/{pid}").json()
if pid in h:
print(f"[DONE ] {shot['id']}")
break
time.sleep(2)
这份脚本只是骨架,实际使用时要注意三点:一是节点 id 每次重建工作流都会变,建议在节点上右键设置标题(Title),用稳定名字做映射;二是 Load Image 的 image 字段必须相对于 ComfyUI 的 input/ 目录;三是失败重试要加上限,800 张分镜跑一晚上,卡在某个镜头无限轮询是最常见的”假死”。
5.3 为什么要在同一台机器上连续跑
换底模会导致脸变——这是第三个常见坑,也是最隐蔽的。IP-Adapter 的特征是注入到当前底模的交叉注意力里的,底模一换,注意力层的数值分布就变了,同样的 0.8 权重在两个底模上出来的”像”的程度完全不同。所以:
- 一集只用一个底模,中途绝不切换。
- 如果必须换(比如后续镜头要写实风格),换完后必须把全部参考镜头重跑一遍定妆照,重新校准权重,而不是沿用旧参数。
- 底模文件名的版本号也记下来,写进项目文档。同一名字的不同版本(v2.1 / v2.2)在社区里经常出现,效果差异不小。
5.4 参数漂移的累积效应
第二个常见坑是参数漂移累积。你可能在第 5 格觉得”眼睛再大一点”把权重从 0.8 调到 0.78,第 12 格又觉得”太僵”调到 0.72,到第 40 格时已经在用 0.55 了,回头一看前后完全不像同一个人。
解决办法很土但有效:参数只允许在批次开始前设定一次,中途不再手改。如果某格不满意,改提示词、换种子,但权重和步数锁死。真要调,就在新的一批里统一调,然后重跑整批。这听起来浪费算力,实际上节省的是后面返工的十倍时间。
5.5 参考图的使用原则
参考过多会让画面僵死——这是第三个坑。有人为了”更准确”,给同一个 IP-Adapter 塞三张参考图,结果人物的脸是”加权平均后的第三个陌生人”。IP-Adapter 的多图模式本质是特征平均,不是”融合优点”。
我的建议是:每个 IP-Adapter 节点只吃一张图。需要多角度参考,就分两个节点挂在不同阶段(比如 0-0.4 步用全身图锁服装,0.4-0.8 步用脸图锁五官),而不是让一个节点吃三张。参考图本身也要挑:正面、均匀光、无遮挡、背景干净,稍微侧一点都会让 embedding 偏。
六、帧校验:工业化和抽卡的分界线
这批图跑完了,你打开文件夹一看,100 张图,脸基本还行,但有几张明显崩了:一张脸糊成油画,一张多了一只手,一张背景里出现了不该有的元素。你是怎么发现的?一张张点开看——这就是抽卡和工业化的区别。
帧校验是工业化和抽卡的分界线。抽卡是”出一张看一张,好看就用”;工业化是”批量产出 → 机器筛掉明显坏的 → 人工只看剩下的 10%”。这一步不做,你的流水线在生产,但你本人还是那个在流水线上人肉质检的工人。
6.1 校验什么
帧校验分三层,从便宜到贵:
第一层:文件完整性
最基础的检查——文件存在吗、能打开吗、尺寸对不对、是不是全黑。OOM 时有时会留下 0 字节文件或损坏的 PNG,这一层能立刻抓出来。
第二层:人脸相似度
用 InsightFace 提取每张图的人脸 embedding,和参考图的人脸 embedding 算余弦相似度。低于阈值的打标。这是最实用的一层,能抓住 80% 的崩图。
第三层:美学/技术质量
检测模糊度(拉普拉斯方差)、过曝比例、CN 结构偏离度。这层更依赖调阈值,建议作为参考而非硬性淘汰。
6.2 一个可用的校验脚本
import os, json
import numpy as np
import cv2
from insightface.app import FaceAnalysis
REF = "refs/lin_xi.png"
OUT = "frames/s01"
THRESH_SIM = 0.55 # 余弦相似度阈值
THRESH_BLUR = 60.0 # 拉普拉斯方差,低于即判模糊
app = FaceAnalysis(name="buffalo_l", providers=["CPUExecutionProvider"])
app.prepare(ctx_id=0, det_size=(640, 640))
def face_emb(path):
img = cv2.imread(path)
if img is None:
return None
faces = app.get(img)
if not faces:
return None
faces.sort(key=lambda f: (f.bbox[2]-f.bbox[0]) * (f.bbox[3]-f.bbox[1]),
reverse=True)
return faces[0].normed_embedding
ref_e = face_emb(REF)
report = []
for name in sorted(os.listdir(OUT)):
if not name.lower().endswith(".png"):
continue
p = os.path.join(OUT, name)
img = cv2.imread(p)
if img is None or img.size == 0:
report.append({"frame": name, "status": "broken"}); continue
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.Laplacian(gray, cv2.CV_64F).var()
mean_lum = gray.mean()
e = face_emb(p)
if e is None:
report.append({"frame": name, "status": "no_face",
"blur": round(blur, 1)}); continue
sim = float(np.dot(e, ref_e))
status = "ok"
if sim < THRESH_SIM: status = "face_drift"
elif blur < THRESH_BLUR: status = "blurry"
elif mean_lum < 12 or mean_lum > 245: status = "exposure"
report.append({"frame": name, "status": status,
"sim": round(sim, 3), "blur": round(blur, 1),
"lum": round(float(mean_lum), 1)})
with open(os.path.join(OUT, "check_report.json"), "w", encoding="utf-8") as f:
json.dump(report, f, ensure_ascii=False, indent=2)
bad = [r for r in report if r["status"] != "ok"]
print(f"总计 {len(report)} 帧,异常 {len(bad)} 帧")
for r in bad:
print(" ", r)
跑完会生成一份 check_report.json,同时终端打印异常清单。阈值可以按项目调:写实风格 0.55 比较合适,动漫风格因为五官本来就简化,可以放宽到 0.45。
6.3 校验结果怎么用
拿到报告后不是”把坏的删掉”,而是把坏的列出来,只重跑这些帧。重跑时可以针对性调整:face_drift 的帧提高 FaceID 权重到 0.85,blurry 的帧检查是否被 ControlNet 强度过高压死,no_face 的帧要么是构图问题(远景没人脸很正常),要么是脸崩到检测不到。
更进一步,可以把这个脚本挂进渲染脚本里做自动重试:某帧相似度低于阈值就自动换种子重跑,最多重试 3 次。这样一晚上无人值守,第二天早上拿到的基本都是能用的。
6.4 人工校验看什么
机器筛完剩下的,”还行”的帧仍然要靠眼睛。人工只看三个东西:手、眼神、逻辑。手是 AI 的老大难,多指少指是常态;眼神的朝向要和镜头语言匹配,不然观众会出戏;逻辑是指前后镜头能不能连起来——上一格还站着,这一格突然躺下了,机器是看不出来的。
七、FFmpeg 批量压制:从图片到成片
7.1 为什么用 FFmpeg
图片序列转视频,剪映也能做,但剪映需要手动导入、拖拽、导出,100 格分镜就是一小时手工活。FFmpeg 是命令行工具,一条命令处理整个文件夹,参数可记录、可复用、可写进脚本。做批量流水线,它是唯一合理的选择。
7.2 按镜头序号批量合成
假设你的分镜图按 S01.png S02.png … S12.png 命名,放在 frames/s01/,想每张停留 3 秒,输出 24fps 的无声片段:
# 方案A:单帧时长法(每张图停留固定秒数)
ffmpeg -y -framerate 1/3 -i "frames/s01/S%02d.png" \
-vf "fps=24,format=yuv420p" \
-c:v libx264 -preset medium -crf 18 \
frames/s01/s01_silent.mp4
-framerate 1/3 表示每张图占 3 秒,后面 fps=24 再补帧到 24 帧每秒,保证播放器兼容。这是做故事板最省事的做法。
如果要给重要镜头加 Ken Burns 推拉效果,改用 concat 加 zoompan:
# 方案B:带缓推效果,从 1.0 推到 1.08
ffmpeg -y -loop 1 -i frames/s01/S03.png -t 4 \
-vf "scale=2400:-1,zoompan=z='min(zoom+0.0004,1.08)':d=96:s=1080x1920:fps=24,format=yuv420p" \
-c:v libx264 -preset slow -crf 16 frames/s01/s03_zoom.mp4
注意 scale 先放大到 2400 宽再做 zoompan,否则推近时边缘会糊。d=96 是 4 秒 × 24 帧。
7.3 多镜头拼接:一集成片
各镜头片段都做好后,用 concat 拼成一集。写一个列表文件:
# 生成文件列表(注意路径必须是绝对路径或相对列表文件的路径)
for f in frames/s01/s*_final.mp4; do
echo "file '$PWD/$f'" >> concat_s01.txt
done
# 无损拼接(要求所有片段编码参数完全一致)
ffmpeg -y -f concat -safe 0 -i concat_s01.txt -c copy s01_draft.mp4
-c copy 不重新编码,几秒钟就好。但如果各片段的编码参数有差异(比如有的用 crf 16 有的 crf 18),拼接会失败或音画不同步,此时去掉 -c copy 让它重新编码。
7.4 一条命令批量处理整季
真正的批量是”整个项目的所有分镜一起压”。用一个 shell 循环:
#!/usr/bin/env bash
set -euo pipefail
PROJ="./proj_night_rain"
FPS=24
HOLD=3
CRF=18
for d in "$PROJ"/frames/*/; do
sid=$(basename "$d")
# 跳过没有图的目录
ls "$d"S*.png >/dev/null 2>&1 || { echo "skip $sid"; continue; }
echo "==> 压制 $sid"
ffmpeg -hide_banner -loglevel error -y \
-framerate 1/$HOLD -i "$d/S%02d.png" \
-vf "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2,fps=$FPS,format=yuv420p" \
-c:v libx264 -preset medium -crf $CRF -movflags +faststart \
"$PROJ/out/${sid}.mp4"
done
# 合并全部镜头
ls "$PROJ"/out/*.mp4 | sort | sed "s|^|file '|;s|$|'|" > "$PROJ/concat_all.txt"
ffmpeg -hide_banner -y -f concat -safe 0 -i "$PROJ/concat_all.txt" -c copy \
"$PROJ/out/full_episode.mp4"
echo "完成: $PROJ/out/full_episode.mp4"
几个细节:scale + pad 组合保证竖屏 1080×1920 不变形(等比缩放后补黑边),-movflags +faststart 让文件能被网页边下边播,sort 保证 S01 到 S12 按数字顺序而不是字典序。
7.5 加背景音和字幕
# 给成片加一条环境音,并统一音量
ffmpeg -y -i full_episode.mp4 -i bgm_rain.mp3 \
-filter_complex "[1:a]volume=0.25,aloop=loop=-1:size=2e9[a]" \
-map 0:v -map "[a]" -shortest \
-c:v copy -c:a aac -b:a 128k full_episode_audio.mp4
# 烧入按时间轴生成的 SRT 字幕
ffmpeg -y -i full_episode_audio.mp4 -vf "subtitles=subs_s01.srt:force_style='FontSize=42'" \
-c:a copy full_episode_final.mp4
7.6 输出参数怎么选
三步压缩决策:
- CRF:18 视觉无损,20 是交付常用值,23 适合网络传阅。做 AI 图序列建议 16-18,因为 AI 图本身高频细节多,压狠了会糊成一片。
- preset:medium 性价比最高。slow 提升 5% 体积但慢一倍,批量的时候不划算。
- 分辨率:除非交付要求 4K,1080p 足够。AI 图放大到 4K 只是放大瑕疵。
八、完整可抄的参数清单
下面这份清单可以直接贴进项目文档,作为”本项目标准参数”:
# ===== 项目标准配置 v1.0 =====
[环境]
ComfyUI 启动参数: --lowvram --reserve-vram 0.6 --preview-method none
插件版本: ComfyUI_IPAdapter_plus (cubiq) 最新稳定版
底模: SD1.5 写实系 / 或 Wan 2.2 1.3B 量化(8G 专用)
CLIP Vision: CLIP-ViT-H-14-laion2B-s32b-b79K.safetensors
IP-Adapter: ip-adapter-plus-face_sdxl_vit-h(SDXL)
ip-adapter-faceid-plusv2_sd15.bin(SD1.5)
InsightFace: buffalo_l
[采样]
分辨率: 1080×1920 (竖屏) / 1920×1080 (横屏)
Steps: 30(标准一致)/ 40(最大一致)
CFG: 7.0(标准一致)/ 7.5(最大一致)
Sampler: dpmpp_2m
Scheduler: karras
Seed: 按镜头序号 +1 递增,基数锁定
[IP-Adapter]
通用层权重: 0.70(标准)/ 0.85(最大)
FaceID 权重: 0.80(标准)/ 0.90(最大)
FaceID v2 权重: 1.0
起止步数: 0.0 - 0.9(末段留给细节)
参考图: 单张 / 正面 / 均匀光 / 干净背景
[ControlNet]
OpenPose: 0.40 - 0.50(动作镜头)
Canny: 0.30 - 0.40(有线稿时)
Depth: 0.30 - 0.45(空间序列)
总强度上限: ≤ 1.0
[帧校验]
人脸相似度阈值: 0.55(写实)/ 0.45(动漫)
模糊阈值: Laplacian var < 60 判模糊
自动重试: 最多 3 次,每次 seed+1000
[FFmpeg 输出]
容器: MP4
编码: libx264, preset medium, crf 18
帧率: 24 fps
单帧停留: 3 秒(可按叙事节奏调整)
音频: AAC 128kbps
兼容: -movflags +faststart
九、常见问题与系统性解决
9.1 换底模导致脸变
已在前文说明,核心是”一集一底模”。如果必须换,把三个参考镜头的定妆照在新底模上重跑,用眼睛对比后重新定权重,别用旧参数硬套。
9.2 参数漂移累积
批次内锁死参数,只改提示词和种子。定期在批次中间插入一张”基准帧”(和第一批的第一张完全同样的参数和种子),如果基准帧跑出来变了,说明有东西在漂——可能是插件更新了,可能是缓存问题,立刻停下排查。
9.3 参考过多导致画面僵死
一个节点一张图。需要多角度就挂多个节点,用 start_at/end_at 分段控制,不要堆图。参考图本身的筛选标准要狠:光照不匀、有滤镜、有遮挡的,一律不用。
9.4 IP-Adapter 与 ControlNet 冲突
症状是画面”又僵又不像”。优先降 ControlNet,因为 IP-Adapter 管的是身份这个更重要的东西。如果必须在两者间妥协,把 IP-Adapter 的 end_at 提前到 0.7,让最后 30% 的步数由 ControlNet 主导细化结构。
9.5 批量跑一晚上崩了
三个排查方向:一是显存泄漏(每跑 20 张重启一次 ComfyUI 能缓解),二是某个特定镜头触发了 OOM(降分辨率或跳过),三是网络问题导致模型加载失败(把模型全部本地化,关掉自动下载)。在渲染脚本里加一个”已完成列表”,重启后自动跳过已完成的,这是最省事的容错。
9.6 节点乱了,想回到昨天
这就是为什么强调”改完一定要存模板”。建议每次调参满意后,把工作流另存为 v_日期_说明.json,并且在主工作流旁留一个 CHANGELOG.md 记录改了什么、为什么改、效果如何。半年后回头看,这份记录比任何教程都值钱。
FAQ
Q1:8G 显存真的跑得动吗?
跑得动,但要接受几个前提。第一,用 SD1.5 或轻量模型(Wan 2.2 1.3B 量化),别硬上 1024 的 SDXL;第二,分辨率控制在 768-832 区间;第三,单挂一个 ControlNet,且强度不超 0.5;第四,接受单图 60-90 秒的速度。满足这四条,8G 能稳定跑通全链路。如果非要 1024 的 SDXL 加双 IP 加 ControlNet,那就是 13.5-14.5GB 的需求,物理上不成立——能跑也不是你能接受的速度。
Q2:该用 SD1.5 还是 SDXL?
看显存和目标。8G 及以下选 SD1.5,生态成熟、速度快、FaceID(faceid-plusv2_sd15)锁脸效果扎实,缺点是大图细节弱、手和肢体容易崩。12G 以上直接 SDXL,肢体稳定性、光影质感全面更好,ip-adapter-plus_sdxl_vit-h 加 face 版的组合也比 SD1.5 更可控。中间地带(10-12G)建议 SDXL 跑 1024 单图精修、SD1.5 跑批量分镜,两条腿走路。
Q3:帧校验怎么自动化?
三层自动化叠加。第一层脚本扫文件完整性和尺寸,抓 0 字节和损坏图;第二层用 InsightFace 算人脸 embedding 和参考图的余弦相似度,低于 0.55 打标;第三层算拉普拉斯方差判模糊、算平均亮度判过曝。全部结果写进 JSON,渲染脚本读取后只重跑异常的帧,重试上限 3 次。这样一晚上无人值守,第二天只需要人工过一遍”手、眼神、逻辑”三项。校验这一步一旦自动化,你的产能就不取决于你能看多少张图,而取决于显卡能跑多少张。
Q4:IP-Adapter 权重是不是越高越像?
不是。权重超过 0.9 之后,画面会明显”糊参考图的质感”——纹理变平、光影死板、姿势被参考图拽住、表情僵化。0.85 左右是上限,再往上收益为负。真正需要”更像”的时候,应该加 FaceID 层,而不是把通用 IP 权重往上推。两者分工不同:通用 IP 管整体气质和服装,FaceID 管五官身份。
Q5:一张参考图能做完整一集吗?
能做,但要看剧情。如果全程是同一个景别(比如都是中景对话),一张正面参考图足够。如果有大量侧脸、背身、动作镜头,FaceID 会力不从心(它的 embedding 是在正脸上提取的),此时建议准备 3 张参考图:正面、四分之三侧、全身。分三个 IP-Adapter 节点按 start_at 分段挂载,比一张图硬撑效果好得多。再多就没必要了,超过 3 张容易互相干扰。
Q6:整条流水线跑一集大概要多久?
按 60 格分镜估算:8G 机器用 SD1.5、单图 70 秒,纯渲染约 70 分钟;加上 10% 的失败重跑,约 80 分钟。帧校验(CPU 跑人脸检测)60 张约 3 分钟。FFmpeg 压制 1080p 的三分钟成片,CRF 18 加 medium,约 2-4 分钟。总计不到 1.5 小时。这个数字在 2026 年不算快,但胜在可以夜里跑、无人值守、第二天直接拿成片——这正是”能批量跑”的价值所在。
结语:把工具变成生产线
回到开头那个问题:为什么”脸不变”比”图好看”难?因为好看是单点问题,一次抽卡的好运气就能解决;脸不变是系统问题,它要求你在采样、权重、显存、批处理、校验、压制每一个环节都做到可复现。
IP-Adapter 只是这条链路里的一个零件。真正让项目跑起来的是三件事:一是把参数固定下来,写进文档,谁接手都能跑出同样的图;二是把校验自动化,机器筛掉明显坏的,人只看值得看的;三是把节点存成模板,让生产线本身成为可交付的资产。
8G 显存的机器,跑一张 1024 的 SDXL 要五六分钟,谁看了都摇头。但如果你把分辨率降到 832、把底模换成 SD1.5、把校验和压制全部脚本化,这台机器一晚上能出 60 格分镜加一条成片——这就是那份工作的真实价值。不是”我的显卡能跑 SDXL”,而是”我一条命令就能出一集”。
显存决定上限,流程决定产能。前者你改不了,后者你随时能改。








暂无评论内容