跳到主内容
快讯直播
AI智模界
教程

LoRA 与 InstantID 实战:多张图保持同一个人

这篇能做出什么

给定同一个人的 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 是一个罕见触发词,作用是把"这个人"绑定到一个模型没见过的词上。不要用 manwomangirl 这类常见词当触发词,否则它会和模型已有的概念打架。

标注有两种写法:

  • 简标注:只写触发词加一两个通用词,比如 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_scaleip_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_scaleip_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 对比

维度InstantIDLoRA
是否需要训练不需要需要
素材要求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_v1char_a_v2_角度补充 这种命名,比 final_final2 有用得多。每版 LoRA 配一组固定 seed 的对照出图,方便回溯。

6. 注意素材授权。用真人照片训练和生成,涉及肖像权与平台规则,商用前务必确认清楚授权范围。

AI 生成本文由 AI 基于公开信息自动生成,仅供参考。