Files
change_hair/docs/换发型集成文档.md
T
xsl ce445b64a6 完善部署并训练5个新发型 + 换发型集成文档
部署修复:
- torch.load 增加 weights_only=False patch,兼容 PyTorch 2.6+ 加载旧权重
- OSS 改为懒加载,本地用 output_format=base64 无需配凭证即可启动
- 补全被 gitignore 误排除的必需代码:core/models/layers/data、models/layers/data、keypoints/lib
- webui 训练命令 --xformers 改 --sdpa(修复 xformers 无 CUDA 支持报错)

功能调整:
- hair_grow_service 端口改 8899、preview 路由修复(send_file)
- list_hairstyles 增加发型白名单,测试页只展示当前5个发型

新增脚本:
- train_lora_parallel.py:直接调 kohya 并行训练 LoRA(绕过 photo_service 串行限制)
- train_hairstyles_parallel.py / train_batch_stepC.py:批量训练辅助脚本
- scripts/sync_data_to_server.sh:大文件断点续传到云服务器

文档:
- docs/换发型集成文档.md:换发型完整流程、服务架构、资源依赖、训练方法、集成步骤
2026-07-09 20:45:08 +08:00

409 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 换发型功能集成文档
本文档说明"换发型"功能的完整工作流程、服务依赖、资源结构,供其他项目集成参考。
---
## 一、功能概述
**换发型**:用户上传一张人像照片,选择一个目标发型(hair_id),系统把用户的头发替换成目标发型,保留人脸和身体。
- **输入**:用户人像图(base64 或 URL + 目标发型 ID
- **输出**:换好发型的结果图(base64 或 OSS URL
- **耗时**:单次约 10-12 秒(GPU: RTX 5090
### 同类功能(同套服务支撑)
- **换发色**`/hairColor/v2`):把头发颜色换成指定 RGB,额外依赖 `ref_color` 颜色查找表
- **训练新发型**:用发型图片训练新的 LoRA,详见第七节
---
## 二、服务架构
换发型由 **3 个后端服务**协同完成(缺一不可),另有 1 个前端页面服务(可选)。
| 服务 | 端口 | conda 环境 | 职责 |
|------|------|-----------|------|
| **webui** | 57860 | `sdwebui` | Stable Diffusion img2img 推理引擎(基于 AUTOMATIC1111 webui |
| **photo_service** | 32678 | `py310` | LoRA 调度:推理时把对应发型的 LoRA 复制到 webui 并注入 prompt;训练时调度 kohya |
| **hair_service_sd** | 8801 | `my_hair` | 换发型主服务:人脸检测、关键点、头发抠图、材质推理、图像合成 |
| hair_grow_service | 8899 | `my_hair` | (可选)前端测试页面,提供发型列表/预览/换发型 UI |
### 服务间调用链(换发型)
```
调用方
│ POST /api/swapHair/v1
hair_service_sd (8801) ← 人脸检测/关键点/抠图/材质推理/图像合成
│ POST /api/hair/inference (转发 img2img 请求)
photo_service (32678) ← 复制 LoRA 到 webui、prompt 注入 <lora:xxx:1.0>
│ POST /sdapi/v1/img2img
webui (57860) ← SD img2img 真正推理
```
### 启动方式
```bash
cd /home/xsl/change_hair
bash start_all.sh start # 启动 webui + photo_service + hair_service_sd
# 等待 1-2 分钟(webui 加载 SD 模型)
# 可选:前端页面
nohup bash start_hairgrow.sh > project/logs/hair_grow_service.log 2>&1 &
```
> 三个核心服务有启动顺序依赖:webui 必须先启动并加载完模型(约 1-2 分钟),photo_service 和 hair_service_sd 才能正常工作。
---
## 三、换发型 API 接口
### 请求
```
POST http://<host>:8801/api/swapHair/v1
Content-Type: application/json
```
**请求体参数:**
| 参数 | 类型 | 必填 | 说明 |
|------|------|:---:|------|
| `hair_id` | string | ✓ | 目标发型 ID(如 `chang_tuoyuan`),对应发型资源目录名 |
| `task_id` | string | ✓ | 任务唯一标识,用于临时文件命名 |
| `user_img_path` | string | ✓ | 用户人像图。支持 `data:image/jpeg;base64,<base64>` 或图片 URL |
| `is_hr` | string | ✓ | 是否高清模式。`"true"` 输出 1152×1536`"false"` 输出 576×768 |
| `output_format` | string | ✗ | 输出格式。`"base64"`(推荐,直接返回图)或 `"url"`(上传 OSS 返回链接,需配 OSS 凭证) |
**请求示例:**
```bash
curl -X POST http://127.0.0.1:8801/api/swapHair/v1 \
-H "Content-Type: application/json" \
-d '{
"hair_id": "chang_tuoyuan",
"task_id": "req_001",
"is_hr": "true",
"user_img_path": "data:image/jpeg;base64,/9j/4AAQ...",
"output_format": "base64"
}'
```
```python
# Python 示例
import base64, requests
with open("user.jpg", "rb") as f:
img_b64 = base64.b64encode(f.read()).decode()
r = requests.post("http://127.0.0.1:8801/api/swapHair/v1", json={
"hair_id": "chang_tuoyuan",
"task_id": "req_001",
"is_hr": "true",
"user_img_path": "data:image/jpeg;base64," + img_b64,
"output_format": "base64",
}, timeout=300)
result = r.json() # {"state":0, "data":"<base64>", "msg":"success"}
with open("out.jpg", "wb") as f:
f.write(base64.b64decode(result["data"]))
```
### 响应
**成功(HTTP 200):**
```json
{
"state": 0,
"msg": "success",
"data": "<base64 编码的结果图>" // output_format=url 时为 OSS 图片链接
}
```
**失败(HTTP 400):**
```json
{
"state": -1,
"msg": "推理发型失败", // 具体错误信息,如:参数错误/用户图像加载失败/发型模板加载失败/推理发型失败
"data": "",
"task_id": "req_001"
}
```
---
## 四、换发型内部流程(9 个功能步骤)
理解内部流程有助于排查问题和性能优化。`hair_service_sd` 收到请求后依次执行:
| 步骤 | 功能 | 说明 | 耗时占比 |
|------|------|------|---------|
| 1 | 获取请求参数 | 解析参数,base64/URL 图片解码落盘 | <1% |
| 2 | 加载用户图像 | 复制到工作目录 | <1% |
| 3 | 加载发型模板图像 | 从 `upload_train_imgs/<hair_id>/` 读模板原图 | <1% |
| 4 | 检查遮挡眼睛 | 从 `hair_template_material/<hair_id>/``*_matting.png``.pkl`(关键点) | <1% |
| 5 | 检查发型材质 | 确认 `ref_hairstyle/<hair_id>/` 存在 | <1% |
| 6 | **推理发型** | 用 GAN 模型把用户图和 `ref_rgb_8uc3_768.png` 融合,生成初步换发图 | ~30% |
| 7 | 判断遮挡眼睛 | 检查头发是否遮挡眼睛(决定后续处理路径) | <1% |
| 8 | 处理发型区域 | 头发区域裁剪、mask 生成、透视变换到 576×768/1152×1536 | ~5% |
| 9 | **webui img2img** | 经 photo_service 调 webui,加载 LoRA 做 SD 重绘(核心画质步骤) | ~60% |
**关键:LoRA 加载机制**(步骤 9
1. `hair_service_sd` 把裁剪好的头发区域图 + mask 发给 `photo_service`
2. `photo_service``train_material/<hair_id>/model/hairstyle_hd_lora.safetensors` 复制到 webui 的 `models/Lora/<hair_id>_hd.safetensors`(已存在则跳过)
3. `photo_service` 在 prompt 前注入 `<lora:<hair_id>_hd:1.0>`
4. webui 收到 img2img 请求,自动加载该 LoRA 做局部重绘
5. 结果返回 `hair_service_sd`,贴回原图对应位置
---
## 五、必需资源清单
换发型功能依赖以下资源。集成时需保证这些文件到位。
### 5.1 核心模型权重(全局共享,约 10G)
位置:`project/hair_service_sd/weights/`
| 文件/目录 | 用途 |
|-----------|------|
| `yolov5l.pt` | RetinaFace/人脸检测 |
| `Resnet50_Final.pth` | 人脸检测辅助 |
| `79999_iter.pth` | 关键点检测(MomocvFaceAlignment1K |
| `master_hair_v8_onlyhair_nowarp_all_0709.pt` | 头发迁移 GAN(发型融合) |
| `zxm_v7_gen_bald_add_changed_hair_pairdata_5seg_0611.pt` | 秃头分割 + 发型生成 GAN |
| `parser_model.pth` | 头发抠图(matting |
| `gender_models/` | 性别识别 |
| `keypoints/` | 关键点模型库 |
### 5.2 Stable Diffusion 推理资源(约 5G
位置:`project/onediff/stable-diffusion-webui/models/`
| 文件/目录 | 用途 |
|-----------|------|
| `Stable-diffusion/v1-5-pruned-emaonly.safetensors` | **SD 1.5 底模**(必需,4G |
| `Lora/<hair_id>_hd.safetensors` | 各发型的 LoRA(推理时自动生成,见 5.3) |
| `ESRGAN/4x-UltraSharp.pth` | 超分辨率(高清模式用) |
| `GFPGAN/` | 人脸修复(可选) |
### 5.3 单个发型的资源(每个发型约 160M)
**一个可用的发型需要 4 个目录的资源配套:**
```
# 1. 发型材质(换发型必需,由训练 step3 生成)
project/data/ref_hairstyle/<hair_id>/
├── config.json # {"gender":"girl","version":"20221206","ratio":"1"}
├── ref_rgb_8uc3_768.png # 发型 RGB 图(GAN 融合参考图,必需)
├── ref_matting_8uc3_768.png # 发型 mask
├── ref_matting_fg_8uc3_768.png # 发型前景
├── ref_baldseg_8uc3_768.png # 秃头分割
├── ref_landmark_f1k2_768.txt # 1000 个关键点
└── input_another_pose_hair_image.npy
# 2. LoRA 权重 + 训练图(换发型必需,由训练 step1+step2 生成)
data/train_material/<hair_id>/
├── model/
│ └── hairstyle_hd_lora.safetensors # LoRA 权重(~145MSD 重绘用)
└── images/
└── 1_hairstyle/ # 训练图 + caption
├── 0001.png
├── 0001.txt # caption tagwebui 推理 prompt 用)
└── ... (约20张)
# 3. 模板材质(换发型必需,由训练 step4 生成)
project/data/hair_template_material/<hair_id>/
├── first##<uuid>.png # 发型模板原图
├── first##<uuid>_matting.png # 发型 matting(遮挡检测用)
└── first##<uuid>.pkl # 关键点(遮挡检测用)
# 4. 发型模板原图(换发型必需,由训练准备)
project/data/upload_train_imgs/<hair_id>/
└── first##<hair_id>.jpg # 发型模板原图(步骤3读取)
```
> **注意**`images/1_hairstyle/` 目录名必须以数字开头(`1_hairstyle`),代码用 `os.listdir(images)[0]` 取子目录并校验数字开头。
### 5.4 路径配置
所有路径在 `project/hair_service_sd/config/configure.ini` 集中配置,迁移时改这里的路径即可:
```ini
[default]
modelDir = weights # 核心模型目录(相对 hair_service_sd
hairstyleDir = /home/xsl/change_hair/project/data/ref_hairstyle # 发型材质(5.3-1
haircolorDir= /home/xsl/change_hair/project/data/ref_haircolor # 换发色用
userDir=/home/xsl/change_hair/project/data/userImage # 用户图工作目录
tmp_dir=/home/xsl/change_hair/project/data/tmp # 临时目录
res_dir=/home/xsl/change_hair/project/data/res_dir # 结果输出目录
userInfo_dir=/home/xsl/change_hair/project/data/user_info # 用户关键点缓存
train_dir = /home/xsl/change_hair/data/train_material # LoRA+训练图(5.3-2
refImgDir=/home/xsl/change_hair/project/data/refImage # 参考图缓存
hair_template_material_dir=/home/xsl/change_hair/project/data/hair_template_material # 5.3-3
upload_train_dir=/home/xsl/change_hair/project/data/upload_train_imgs # 5.3-4
version=online # online 用 0.0.0.0 地址
```
`photo_service` 的 webui/LoRA 路径在 `project/photo_service/lora_train_service_1.py` 第 41 行:
```python
webui_lora_dir = '/home/xsl/change_hair/project/onediff/stable-diffusion-webui/models/Lora'
```
---
## 六、集成步骤(接入新项目)
### 1. 部署服务端
按第二节启动 webui + photo_service + hair_service_sd 三个服务,确认 `http://<host>:8801` 可达。
### 2. 调用换发型接口
新项目只需调 `hair_service_sd``/api/swapHair/v1`,无需关心内部服务调用。
### 3. 准备发型资源
每个要支持的发型,必须备齐第五节 5.3 的 4 个目录资源。两种获取方式:
- **训练新发型**(见第七节):用发型图训练,自动生成全部资源
- **复制现有发型**:从已部署环境复制 `<hair_id>` 的 4 个目录
### 4. 查询可用发型列表
`hair_grow_service` 的接口(需启动 8899):
```
GET http://<host>:8899/api/hairstyles
→ {"state":0, "count":5, "data":[{"hair_id":"chang_tuoyuan","gender":"girl"}, ...]}
```
> 注意:该接口在 `hair_grow_swap.py:list_hairstyles()` 内有白名单过滤(当前只返回 5 个 chang_* 发型),集成时按需调整或去掉。
### 5. 获取发型预览图
```
GET http://<host>:8899/preview/<hair_id>
→ 直接返回预览图(优先 static/previews/<hair_id>.jpg,回退到 ref_rgb_8uc3_768.png
```
---
## 七、训练新发型
### 7.1 一键训练脚本
```bash
cd /home/xsl/change_hair/project/hair_service_sd
python /home/xsl/change_hair/train_hairstyle_full.py \
--hair-id <新发型ID> \
--input <训练图目录> \ # 目录里放该发型的 jpg/png(1张即可,多张效果更好)
--gender <girl|boy> \
--template-img <模板图> # 用于生成材质和预览,通常是该发型最代表性的图
```
### 7.2 训练 5 个阶段
| 阶段 | 功能 | 产物 | 耗时 |
|------|------|------|------|
| step1 | 准备训练数据 | 抠图 + 数据增广(水平翻转 × 5 分辨率)→ `train_material/<hair_id>/images/1_hairstyle/` 约 20 张 png+txt | ~1 分钟 |
| step2 | LoRA 训练 | kohya 训练 1500 步 → `train_material/<hair_id>/model/hairstyle_hd_lora.safetensors` | ~10 分钟(单卡) |
| step3 | 生成 ref 材质 | 调 hair_service_sd 回调 → `ref_hairstyle/<hair_id>/` 6 个文件 | ~1 秒 |
| step4 | 生成模板材质 | → `hair_template_material/<hair_id>/` 3 个文件 | ~3 秒 |
| step5 | 生成预览图 | → `hair_grow_service/static/previews/<hair_id>.jpg` | ~10 秒 |
### 7.3 批量并行训练(多个发型)
`train_lora_parallel.py` 直接调 kohya,支持并发:
```python
# 编辑 train_lora_parallel.py 顶部的 HAIRSTYLES 列表
HAIRSTYLES = ["新发型1", "新发型2", "新发型3"]
PARALLEL = 2 # 并发数(按显存调整,5090 32G 建议 2)
# step1 数据需先用 train_hairstyles_parallel.py 或逐个跑 step1 准备好
python train_lora_parallel.py
```
### 7.4 训练注意事项
- **hair_id 命名**:只能用英文/数字/下划线,不能含中文或特殊字符(会作目录名、文件名、LoRA 名)
- **单图训练**:1 张图经增广出 ~20 张训练图,LoRA 能用但泛化有限;多图(不同角度/光照)效果更好
- **GPU 显存**:训练峰值约 13G/卡,推理约 6G。并行训练需控制并发数避免 OOM
- **训练前提**webui + photo_service + hair_service_sd 必须在运行(step3 回调依赖)
---
## 八、环境依赖
### Conda 环境
| 环境 | Python | 用途 | 关键依赖 |
|------|--------|------|---------|
| `my_hair` | 3.10 | hair_service_sd / hair_grow_service / 训练 step1 | torch, cv2, flask, gevent, oss2 |
| `sdwebui` | 3.10 | webui | torch, gradio, transformersAUTOMATIC1111 全套) |
| `py310` | 3.10 | photo_service | torch, flask, requests |
| `kohya` | 3.10 | LoRA 训练(kohya_ss | torch, accelerate, safetensors |
### 关键兼容性处理(迁移时注意)
1. **PyTorch 2.6+ 的 `weights_only`**:加载旧权重(yolov5l.pt 等)需 `torch.load(..., weights_only=False)`。代码已在 `run_copy_cost_colorb64.py``hair_grow_service/app.py` 顶部做了全局 patch。
2. **OSS 上传(可选)**`output_format=url` 时需配 OSS 凭证环境变量:
```bash
export OSS_TEST_ACCESS_KEY_ID=<...>
export OSS_TEST_ACCESS_KEY_SECRET=<...>
export OSS_TEST_BUCKET=<...>
export OSS_TEST_ENDPOINT=<...>
```
用 `output_format=base64` 则完全不需要 OSS。
3. **webui xformers**kohya 训练命令用 `--sdpa`PyTorch 原生),不要用 `--xformers`(若 xformers 与 torch 版本不匹配会报 CUDA 错误)。
---
## 九、故障排查
| 现象 | 可能原因 | 排查 |
|------|---------|------|
| 换发型返回 `推理发型失败` | webui 没启动 / LoRA 文件缺失 | 检查 webui 57860 端口;检查 `train_material/<hair_id>/model/` 是否有 LoRA |
| 换发型返回 `发型模板加载失败` | `upload_train_imgs/<hair_id>/` 缺 `first##` 开头的文件 | 检查 5.3-4 目录 |
| 换发型返回 `检查遮挡失败` | `hair_template_material/<hair_id>/` 缺 matting/pkl | 检查 5.3-3 目录 |
| 预览图 404 | `ref_hairstyle/<hair_id>/ref_rgb_8uc3_768.png` 不存在 | 检查 5.3-1 目录 |
| 发型列表少发型 | `hair_grow_swap.py:list_hairstyles()` 白名单过滤 | 调整 `_VISIBLE_HAIRSTYLES` 集合 |
| webui 启动报 xformers 错 | xformers 与 torch 版本不匹配 | webui 启动去掉 `--xformers`,或用 `--sdpa` |
| 训练报 CUDA error | 显存不足或 xformers 问题 | 训练命令用 `--sdpa`;降低并发数 |
### 日志位置
- webui`project/logs/webui.log`
- photo_service`project/logs/photo_service.log`
- hair_service_sd`project/logs/hair_service.log`
- hair_grow_service`project/logs/hair_grow_service.log`
- 训练:`project/logs/train_<hair_id>.log`
---
## 十、目录结构总览
```
/home/xsl/change_hair/
├── project/
│ ├── hair_service_sd/ # 换发型主服务
│ │ ├── weights/ # 核心模型权重(10G)
│ │ ├── config/configure.ini # 路径配置
│ │ ├── run_copy_cost_colorb64.py # 主入口(API 定义)
│ │ ├── gen_super_image.py # webui img2img 调用
│ │ └── core/ # 发型推理核心逻辑
│ ├── photo_service/ # LoRA 调度服务
│ │ └── lora_train_service_1.py # 训练调度 + 推理 LoRA 切换
│ ├── onediff/ # SD webui(推理引擎,14G
│ │ └── stable-diffusion-webui/models/
│ │ ├── Stable-diffusion/ # SD 底模
│ │ └── Lora/ # 各发型 LoRA(自动生成)
│ ├── kohya_ss_home/ # LoRA 训练框架(13G
│ ├── data/ # 数据目录(由 configure.ini 指向)
│ │ ├── ref_hairstyle/ # 发型材质(5.3-1
│ │ ├── hair_template_material/ # 模板材质(5.3-3
│ │ ├── upload_train_imgs/ # 发型模板原图(5.3-4)
│ │ └── ...
│ └── logs/
├── data/
│ └── train_material/ # LoRA + 训练图(5.3-2
├── hair_grow_service/ # 前端页面(可选)
├── train_hairstyle_full.py # 单发型一键训练
├── train_lora_parallel.py # 批量并行训练
├── start_all.sh # 服务启停脚本
└── docs/换发型集成文档.md # 本文档
```