这篇能做出什么
给定同一个人的 8~20 张照片,你能批量产出「同一个人」出现在不同场景、服装、姿势、镜头焦段下的图片,而且脸是同一张——不是"长得像",而是可以放到一起当角色设定集的程度。
具体一点,这套流程做完,你可以产出这样一组图:一个短发戴眼镜的年轻女性,分别出现在办公室工位、雨夜街头、咖啡馆窗边、山顶看日出;前两张穿深色西装,后两张穿冲锋衣;每张图的构图和光线都不同,但五官、脸型、发色是稳定的。
实现它有两条主流路线,本文会同时走通并给出对比:
- InstantID:不训练。给它一张清晰正脸照,加上提示词直接出图。上手快,适合快速验证和零散出图。
- LoRA:先训练。用十几张照片训练一个小模型(通常几十 MB),之后像加载插件一样调用。灵活度高,适合长期项目、系列图、批量生产。
两条路不冲突。常见的工作流是:先用 InstantID 快速试风格,确认角色的视觉方向后,再训一个 LoRA 做长期产出。
前置条件清单
硬件
- 推理:SDXL 模型建议显存 12GB 起,8GB 通过降分辨率、fp16、分块 VAE 也能跑,但体验会打折。
- 训练:LoRA 训练建议 12~24GB 显存。显存越小,能开的分辨率、batch size 越小,训练时间越长。具体下限看训练工具官方文档。
软件环境
- Python 3.10 左右的环境(以你所选工具官方文档当前要求为准)
- PyTorch,版本必须与你的 CUDA 驱动匹配,按 PyTorch 官方安装页给出的命令装
- ComfyUI 或 SD WebUI(推理用),kohya_ss / sd-scripts(训练用)
- InsightFace,两条路线都依赖它做脸部和关键点检测
- 一个 SDXL 写实底模,底模选择对结果影响很大
素材
- InstantID:1 张及以上清晰正脸照,光线均匀、无遮挡、无重滤镜
- LoRA:10~20 张同一个人的照片,覆盖不同角度、表情、光线、背景
- 所有素材建议先把人脸裁出来看过一遍,不能用的一律剔除
心理准备:这是一件"工程 + 挑选"的活。一次出十张,能用的可能两三张,这是正常的。
步骤一:整理训练素材
先建目录,把素材分类放好,后面所有脚本都指向它。
```bash
mkdir -p /data/char_a/raw # 原始照片
mkdir -p /data/char_a/train # 清洗后的训练图
mkdir -p /data/char_a/output # 生成结果
mkdir -p /data/char_a/ref # InstantID 用的参考正脸
```
清洗要点:
1. 只留一个人。合影、背景里有人脸的一律不要。
2. 剔除低质量。分辨率低于 512 边长、明显模糊、美颜磨皮过度、贴纸遮挡五官的,全部删掉。
3. 角度要杂。全是大头正脸会导致训练出来的模型换个角度就崩。目标比例大致是:正脸 1/3、半侧脸 1/3、侧脸和仰俯角 1/3。
4. 表情要杂。微笑、平静、说话中都可以,全是同一种表情会让模型把表情也一起记住。
5. 服装和背景要杂。如果 15 张全是同一件白衬衫,生成的图里会出现白衬衫"焊死"在身上的现象。
步骤二:写标注(caption)文件
LoRA 训练需要每张图配一个同名的 .txt 标注文件。
```bash
假设图片是 001.jpg,就生成 001.txt
cd /data/char_a/train
for f in *.jpg; do
base="${f%.jpg}"
printf "ohwx person, %s\n" "photo" > "${base}.txt"
done
```
这里 ohwx 是一个罕见触发词,作用是把"这个人"绑定到一个模型没见过的词上。不要用 man、woman、girl 这类常见词当触发词,否则它会和模型已有的概念打架。
标注有两种写法:
- 简标注:只写触发词加一两个通用词,比如
ohwx person, photo。让模型把外貌整体打包进触发词里,上手简单。 - 详标注:写清楚服装、背景、姿势、光线,但不写五官特征。比如
ohwx person, white shirt, standing in office, soft light。这样触发词负责脸,其他词负责场景,后期控制更精细。
人物 LoRA 一般先用简标注跑一版,效果不理想再换详标注。
步骤三:走 InstantID 路线(免训练)
3.1 安装依赖
```bash
pip install insightface onnxruntime-gpu opencv-python
pip install diffusers transformers accelerate safetensors
```
ComfyUI 用户走自定义节点安装,按节点仓库的 README 操作即可,通常把节点目录放进 custom_nodes/ 再装依赖。
InstantID 需要三个额外文件:InsightFace 的人脸识别模型包、InstantID 专用的 IP-Adapter 权重、配套的 AI 词典:ControlNet">ControlNet。这些文件的下载方式与放置路径以各项目官方文档当前说明为准,放错目录是新手最常见的报错来源。
3.2 ComfyUI 里的节点链路
```
Load Checkpoint (SDXL 写实底模)
├─> InstantID 节点 <── Load Image (参考正脸)
│ └── 需要同时接 InsightFace 模型 + IP-Adapter + ControlNet
└─> KSampler ─> VAE Decode ─> Save Image
```
提示词只写场景,不写脸。例如:
```
a young woman with short hair, dark suit, sitting in a modern office, window light, medium shot, photorealistic
```
注意提示词里不要再写发型、脸型、年龄这些细节——身份由参考图决定,文字再描述一遍反而会互相干扰。
3.3 diffusers 脚本版本
如果不想用图形界面,用 Python 脚本也能跑。下面是骨架,参数名以 diffusers 官方文档当前版本为准:
```python
import torch
import cv2
from diffusers import StableDiffusionXLInstantIDPipeline, ControlNetModel
from insightface.app import FaceAnalysis
1. 人脸分析器
app = FaceAnalysis(name="antelopev2", providers=["CUDAExecutionProvider"])
app.prepare(ctx_id=0, det_size=(640, 640))
2. 从参考图提取人脸 embedding 和关键点
ref = cv2.imread("/data/char_a/ref/face.jpg")
faces = app.get(ref)
face_emb = faces[0].normed_embedding
face_kps = faces[0].kps
3. 组装 pipeline
controlnet = ControlNetModel.from_pretrained(
"<InstantID ControlNet 本地路径>", torch_dtype=torch.float16
)
pipe = StableDiffusionXLInstantIDPipeline.from_pretrained(
"<SDXL 底模本地路径>",
controlnet=controlnet,
torch_dtype=torch.float16,
).to("cuda")
pipe.load_ip_adapter_instantid("<InstantID IP-Adapter 本地路径>")
4. 出图
image = pipe(
prompt="a young woman with short hair, dark suit, modern office, medium shot",
image_embeds=face_emb,
image_kps=face_kps,
controlnet_scale=0.8, # 身份控制强度
ip_adapter_scale=0.8, # 身份注入强度
num_inference_steps=30,
guidance_scale=5.0,
).images[0]
image.save("/data/char_a/output/instantid_001.png")
```
controlnet_scale 和 ip_adapter_scale 是两个关键旋钮:调太低脸不像,调太高画面僵硬、构图被参考图带偏。0.6~0.9 之间来回试。
步骤四:走 LoRA 路线(需训练)
4.1 准备数据集配置
```toml
[general]
enable_bucket = true
caption_extension = ".txt"
bucket_no_upscale = false
[[datasets]]
resolution = 1024
batch_size = 1
enable_bucket = true
[[datasets.subsets]]
image_dir = "/data/char_a/train"
num_repeats = 10
caption_extension = ".txt"
```
num_repeats = 10 表示每张图每轮重复 10 次。15 张图就是每轮 150 步。
4.2 启动训练
```bash
accelerate launch --num_cpu_threads_per_process 1 sdxl_train_network.py \
--pretrained_model_name_or_path="/models/sdxl_base.safetensors" \
--dataset_config="/data/char_a/dataset.toml" \
--output_dir="/data/char_a/output/lora" \
--output_name="char_a_v1" \
--network_module="networks.lora" \
--network_dim=32 \
--network_alpha=16 \
--learning_rate=1e-4 \
--text_encoder_lr=1e-5 \
--optimizer_type="AdamW8bit" \
--lr_scheduler="cosine_with_restarts" \
--max_train_epochs=10 \
--save_every_n_epochs=2 \
--mixed_precision="fp16" \
--gradient_checkpointing \
--resolution=1024 \
--save_model_as="safetensors"
```
参数名和默认值会随版本变化,以 sd-scripts 官方文档当前版本为准。这里几个值值得留意:
network_dim=32:网络容量。人物 LoRA 常用 16~64,越大越容易记住细节,也越容易过拟合。learning_rate=1e-4:常见起点。max_train_epochs=10+save_every_n_epochs=2:每隔两轮存一版,方便你横向挑。不要只存最后一版,过拟合往往发生在后半程。
4.3 推理调用
训练完会得到若干 .safetensors 文件。在推理时加载:
```python
pipe.load_lora_weights(
"/data/char_a/output/lora",
weight_name="char_a_v1.safetensors",
)
pipe.fuse_lora(lora_scale=0.75) # 权重先试 0.75
```
提示词里带上触发词:
```
ohwx person, wearing a dark suit, sitting in a modern office, window light, medium shot, photorealistic
```
lora_scale 通常从 0.6 开始往上试。0.5 以下身份会漂,0.9 以上画面容易发死、皮肤像塑料。
步骤五:验证一致性
不要靠肉眼"感觉像",写个脚本量化一下。用 InsightFace 提取人脸 embedding,算余弦相似度:
```python
import cv2
import numpy as np
from insightface.app import FaceAnalysis
app = FaceAnalysis(name="buffalo_l", providers=["CUDAExecutionProvider"])
app.prepare(ctx_id=0, det_size=(640, 640))
def get_emb(path):
img = cv2.imread(path)
faces = app.get(img)
if not faces:
return None
取面积最大的一张脸
face = max(faces, key=lambda f: (f.bbox[2] - f.bbox[0]) * (f.bbox[3] - f.bbox[1]))
return face.normed_embedding
ref = get_emb("/data/char_a/ref/face.jpg")
for p in ["/data/char_a/output/a.png", "/data/char_a/output/b.png"]:
e = get_emb(p)
if e is None:
print(p, "未检测到人脸")
continue
sim = float(np.dot(ref, e))
print(p, round(sim, 3))
```
经验上,余弦相似度 0.6 以上通常可以认为"是同一个人",0.5 附近属于像但不是,0.4 以下基本是另一个人。这个阈值不是硬标准,跟检测模型、图片质量都有关,建议先拿同一个人的两张真实照片跑一遍,得到你自己的基线分数,再拿生成图对比。
常见坑与排错
1. 训练出来换个角度就崩
素材角度太单一。回去补半侧脸、侧脸、不同俯仰角的照片,重训。
2. 生成的图背景里出现训练图的元素
过拟合。表现是无论什么提示词,画面角落总会冒出训练素材里的沙发、窗帘、书架。解决:减少 epoch、把 network_dim 降到 16、在 caption 里写清背景描述。
3. InstantID 出图脸糊、五官崩
按顺序排查:参考图是不是清晰正脸 → controlnet_scale 和 ip_adapter_scale 是不是一个太高一个太低 → guidance_scale 是否超过 7(过高会让脸崩) → 换一个写实底模再试。
4. 脸在画面里太小,相似度直线下降
身份信息在几十个像素里根本表达不出来。构图时让人脸占据画面一定比例,或者先出全身图,再用 FaceDetailer / ADetailer 一类的面部修复节点单独重绘脸部。
5. 两个角色在同一张图里脸混了
同时挂两个人物 LoRA,模型会把两张脸平均。做法是分区提示词,或者干脆分开生成、后期合成。
6. 显存不足(OOM)
降分辨率、把 batch size 降到 1、开 gradient_checkpointing、用 fp16 或 bf16、关闭其它占显存的程序。训练时还可以降 network_dim。
7. 结果每次都不一样,无法判断哪个参数好
固定随机种子(seed),一次只改一个参数。改两个,你永远不知道是哪个起的作用。
8. 训练日志里 loss 一直降,但出图越来越丑
loss 低不等于效果好,人物 LoRA 尤其如此。判断标准是出图,不是曲线。每隔两轮存一版,横向出图对比。
LoRA 与 InstantID 对比
| 维度 | InstantID | LoRA |
|---|---|---|
| 是否需要训练 | 不需要 | 需要 |
| 素材要求 | 1 张及以上清晰正脸 | 10~20 张多样照片 |
| 上手速度 | 装好环境当天可用 | 需要准备数据和调参 |
| 身份相似度 | 高,偏"贴" | 取决于素材与训练,上限可控 |
| 风格适配 | 受底模限制较多 | 可与不同底模、风格 LoRA 组合 |
| 多角色同框 | 较难 | 配合分区提示词可行 |
| 批量生产 | 单张调参为主 | 一次训好可长期复用 |
| 适合场景 | 快速验证、零散出图、少量角色 | 系列作品、长期项目、批量生产 |
一句话选择建议:只做几张图、想马上看到效果,走 InstantID;要做一个持续几周甚至几个月的角色项目,走 LoRA,并且先用 InstantID 把角色的视觉方向定下来再开训。
下一步建议
1. 加一步面部修复。把 FaceDetailer / ADetailer 接在出图之后,专门重绘脸部区域,小脸场景的相似度会明显改善。
2. 试试其它免训练方案。IP-Adapter FaceID、PuLID 这类方法思路相近,各有取舍,值得横向比一遍,选适合你素材特点的那个。
3. 建立自己的评估基线。用同一个人的两张真实照片算一次余弦相似度,作为"1.0 参考线",之后所有生成图都有对照。
4. 把角色做成可复用资产。触发词、底模、LoRA 权重、推荐 lora_scale、常用提示词模板,统一记在一个文件里。三个月后你会感谢现在的自己。
5. 规范命名与版本。char_a_v1、char_a_v2_角度补充 这种命名,比 final_final2 有用得多。每版 LoRA 配一组固定 seed 的对照出图,方便回溯。
6. 注意素材授权。用真人照片训练和生成,涉及肖像权与平台规则,商用前务必确认清楚授权范围。
