Files
change_hair/README.md
T
xsl 443cfa298f 初始化:换发型/换发色/训练发型服务
包含:
- hair_service_sd: 主服务(换发型/换发色/生发,端口8801)
- photo_service: LoRA调度+训练(端口32678)
- hair_grow_service: 调试测试页(端口8888,含4个测试页)
- 批量训练脚本(batch_train_hairstyles.py)
- 发际线mask自动识别(hairline_mask.py,4种方案)
- 手绘mask换发型(hair_swap_manual.py)
- 文档:README.md + LARGE_FILES.md + docs/

大文件(模型权重200G、训练数据123G)已排除,见 LARGE_FILES.md
OSS/COS密钥已脱敏为环境变量,原文件备份在本地
2026-07-07 13:53:52 +08:00

345 lines
15 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_service_sd)在本机的部署架构、接口用法、训练功能与日常运维操作。
> 该服务从 `szlc@192.168.101.63:/home/szlc/project` 克隆部署至本机 `/home/xsl/change_hair`。
---
## 一、总体架构
服务由三个独立进程组成,存在调用依赖关系:
```
┌─────────────────────────────┐
客户端 ──HTTP──▶ │ hair_service_sd (:8801) │ 主服务,对外提供所有接口
│ conda env: my_hair │
└──────────┬──────────────────┘
│ 换发型/训练时内部调用
┌──────────────┴───────────────┐
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ photo_service (:32678)│ │ webui (:57860) │
│ conda env: py310 │ │ conda env: sdwebui │
│ LoRA加载/训练调度 │────▶ │ SD模型 + ControlNet │
│ 训练: conda env:kohya│ 推理 │ LoRA权重 │
└──────────────────────┘ └──────────────────────┘
```
| 进程 | 端口 | conda 环境 | 职责 |
|------|------|-----------|------|
| **hair_service_sd** | 8801 | my_hair | 主服务,对外提供换发色/换发型/上传发型/训练回调接口 |
| **photo_service** | 32678 | py310 (推理) / kohya (训练) | LoRA 权重加载、webui 推理调度、LoRA 训练执行 |
| **webui** | 57860 | sdwebui | Stable Diffusion img2img 推理(SD模型 + ControlNet + LoRA |
**启动顺序**(存在依赖):webui → photo_service → hair_service_sd
---
## 二、目录结构
所有数据部署在 `/home/xsl/change_hair`(对应原服务器 `/home/szlc/project` + `/data`):
```
/home/xsl/change_hair/
├── README.md # 本文档
├── start_all.sh # 统一启动/停止/状态脚本
├── start_photo.sh # photo_service 启动包装脚本
├── start_hair.sh # hair_service_sd 启动包装脚本
├── adapt_paths.sh # 路径适配脚本(部署时一次性执行)
├── fix_conda_paths.py # conda 环境路径修复脚本(部署时一次性执行)
├── test_haircolor.py # 换发色接口测试脚本
├── test_swaphair.py # 换发型接口测试脚本
├── test_train_api.py # 训练接口测试脚本
├── project/ # ← 原服务器 /home/szlc/project
│ ├── hair_service_sd/ # 主服务(9.9G,含 weights 模型权重)
│ ├── onediff/ # webui49G,含SD模型+228个LoRA+ControlNet
│ │ └── stable-diffusion-webui/
│ ├── photo_service/ # LoRA调度+训练服务(4.1M
│ ├── kohya_ss_home/ # kohya LoRA训练框架(13G
│ │ └── kohya_ss/ # train_network.py 所在目录
│ ├── data/ # 发型素材(4.9G)
│ │ ├── ref_hairstyle/ # 换发型发型材质(183个发型)
│ │ ├── ref_haircolor/ # 换发色参考
│ │ ├── hair_template_material/ # 发型模板(matting+pkl
│ │ ├── upload_train_imgs/ # 上传发型训练图
│ │ ├── ref_user_imgs/ # ⚠️ 训练素材生成用的参考图(当前为空,见下文说明)
│ │ └── ...
│ └── logs/ # 所有服务日志
└── data/ # ← 原服务器 /data
└── train_material/ # 338个发型的LoRA训练素材(121G
└── <hair_id>/
├── images/<N>_hairstyle/*.png # 训练图片
└── model/hairstyle_hd_lora.safetensors # 训练产出的LoRA权重
```
conda 环境位于 `/home/xsl/miniconda3/envs/`
| 环境 | Python | 用途 |
|------|--------|------|
| my_hair | 3.10.13 | hair_service_sd 主服务 |
| sdwebui | 3.10.19 | webui SD 推理 |
| py310 | 3.10.14 | photo_service LoRA调度 |
| kohya | 3.10.20 | LoRA 训练(**本次部署新建**,原服务用 Docker) |
---
## 三、日常运维操作
### 启动 / 停止 / 查看状态
```bash
cd /home/xsl/change_hair
bash start_all.sh start # 启动全部服务(按依赖顺序)
bash start_all.sh stop # 停止全部服务
bash start_all.sh restart # 重启全部服务
bash start_all.sh status # 查看运行状态与端口
```
启动后 webui 加载 SD 模型需约 1-2 分钟,`status` 显示端口就绪即表示可用。
### 查看日志
```bash
tail -f project/logs/webui.log # webui 日志
tail -f project/logs/photo_service.log # photo_service 日志
tail -f project/logs/hair_service.log # 主服务日志
```
### 运行接口测试
```bash
# 换发色测试(生成红色头发)
/home/xsl/miniconda3/envs/my_hair/bin/python test_haircolor.py
# 换发型测试(用已有发型)
/home/xsl/miniconda3/envs/my_hair/bin/python test_swaphair.py
```
测试结果图保存在 `project/logs/test_*_result.jpg`
---
## 四、接口文档
主服务地址:`http://127.0.0.1:8801`
### 1. 换发色 `POST /hairColor/v2`
把照片中头发的颜色改为指定 RGB。
**请求参数**JSON):
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| img | string | 是 | 图片 base64,需带 `data:image/jpeg;base64,` 前缀 |
| userId | string | 是 | 用户标识 |
| rgb | [int,int,int] | 是 | 目标颜色,长度3的数组,如 `[255,0,0]` 红色 |
| output_format | string | 否 | `base64`(返回base64图片)或 `url`(上传OSS返回链接,默认) |
**请求示例**
```python
import base64, requests
with open("photo.jpg", "rb") as f:
img_b64 = "data:image/jpeg;base64," + base64.b64encode(f.read()).decode()
resp = requests.post("http://127.0.0.1:8801/hairColor/v2", json={
"img": img_b64,
"userId": "user001",
"rgb": [255, 0, 0], # 红色
"output_format": "base64"
})
result = resp.json()
# {"msg": "success", "state": 0, "result": "<base64图片>", "umd": ""}
```
### 2. 换发型 `POST /api/swapHair/v1`
把照片中人物的发型换成指定发型(需该发型已训练好 LoRA)。
**请求参数**JSON):
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| hair_id | string | 是 | 发型ID(对应 train_material 和 ref_hairstyle 中的目录名) |
| task_id | string | 是 | 任务ID(任意唯一字符串) |
| is_hr | string | 是 | `"true"` 高清版 / `"false"` 普通版 |
| user_img_path | string | 是 | 用户照片 base64,需带 `data:image/jpeg;base64,` 前缀 |
| output_format | string | 否 | `base64``url`(默认) |
**请求示例**
```python
resp = requests.post("http://127.0.0.1:8801/api/swapHair/v1", json={
"hair_id": "1907641420518580226",
"task_id": "task_001",
"is_hr": "true",
"user_img_path": img_b64, # data:image/jpeg;base64,...
"output_format": "base64"
})
result = resp.json()
# {"state": 0, "msg": "success", "data": "<base64图片>", "task_id": "task_001"}
```
**可用发型**:发型需同时具备 `ref_hairstyle/<hair_id>` 材质 + `train_material/<hair_id>/model/hairstyle_hd_lora.safetensors` 权重。查看可用发型:
```bash
# 列出材质+权重都齐全的发型
for hid in $(ls project/data/ref_hairstyle); do
[ -f data/train_material/$hid/model/hairstyle_hd_lora.safetensors ] && echo "$hid"
done
```
### 3. 新增发型训练 `POST /api/uploadHair/v1`
上传新发型图片,异步训练 LoRA。训练完成后发型即可用于换发型。
**请求参数**JSON):
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| hair_id | string | 是 | 新发型ID(自定义唯一ID) |
| img_lists | [string] | 是 | 发型图片 URL 列表(至少1张,需 HTTP 可访问的URL) |
**注意**:此接口 `img_lists` 必须是 HTTP URL(内部用 requests 下载),不支持 base64。如用本地图片,需先启动一个 HTTP 服务托管。
**响应**:立即返回 `{"msg": "OK", "state": 0}`,训练在后台异步进行(约 30-40 分钟)。
### 4. 训练回调 `POST /api/hair/trainCallBack`
photo_service 训练完成后自动调用此接口通知主服务,一般不由外部直接调用。
---
## 五、新增发型训练(LoRA Training
### 工作原理
原服务通过 Docker 容器(`chinatszrn/ubuntu:kohya_ss`)运行 kohya 的 `train_network.py` 训练 LoRA。**本机部署已改为本地 conda 环境直接执行**,无需 Docker。
完整链路:
```
uploadHair → 下载图片 → 生成训练素材(matting/pkl/训练图)
→ photo_service 入队
→ 后台 train_thread 执行:
accelerate launch train_network.py (1500步, 约30-40分钟)
→ 产出 hairstyle_hd_lora.safetensors
→ 回调 trainCallBack
→ 该发型即可用于换发型
```
### 训练配置(已适配本机)
photo_service 的训练命令(`lora_train_service_1.py``train_thread` 函数)关键参数:
| 配置项 | 值 | 说明 |
|--------|-----|------|
| 训练脚本 | `kohya_ss_home/kohya_ss/train_network.py` | kohya LoRA 训练 |
| conda 环境 | `/home/xsl/miniconda3/envs/kohya` | torch 2.0.1+cu118 |
| 基础模型 | `v1-5-pruned-emaonly.safetensors` | SD底模(原为 majicmix,本机不存在,已替换) |
| 优化器 | `AdamW` | 原为 AdamW8bit(依赖 bitsandbytes,有兼容问题,已改) |
| 训练步数 | 1500 | |
| LoRA维度 | 128 (network_dim) | |
| GPU | device 0 | 单卡 |
### 手动触发训练(不经过 uploadHair
如果已有训练素材(`train_material/<hair_id>/images/<N>_hairstyle/*.png`),可直接调用 photo_service 训练:
```bash
curl -X POST http://127.0.0.1:32678/api/hair/train \
-H "Content-Type: application/json" \
-d '{
"task_id": "manual_train_001",
"hair_id": "1905785164224868354",
"hair_material_dir": "/home/xsl/change_hair/data/train_material/1905785164224868354",
"is_tj": "1",
"device_id": "0",
"webui_addr": "http://0.0.0.0:32678/"
}'
```
训练进度查看:`tail -f project/logs/photo_service.log`(搜索 `steps:`
### ⚠️ 训练素材生成依赖说明
`uploadHair` 的完整流程中,**生成训练素材这一步依赖 `project/data/ref_user_imgs/` 参考图目录**(用于人脸对齐生成训练对)。该目录在原服务器上即为空(这是原项目状态)。
-`ref_user_imgs` 为空,uploadHair 能下载图片并触发 LoRA 训练,但**生成的新训练素材质量会受限**
- 要完整使用 uploadHair 上传全新发型训练,需往 `ref_user_imgs/` 补充参考人像图 + 对应的 `.pkl` 关键点文件
---
## 六、部署改动记录
以下是对原服务代码/配置的改动,供维护参考:
### 1. 路径适配(所有配置和代码)
原服务器路径 → 本机路径的映射:
| 原路径 | 本机路径 |
|--------|---------|
| `/home/szlc/project/...` | `/home/xsl/change_hair/project/...` |
| `/data/train_material` | `/home/xsl/change_hair/data/train_material` |
| `/home/szlc/miniconda3` | `/home/xsl/miniconda3` |
涉及文件:`hair_service_sd/config/configure.ini``hair_service_sd/*.py``photo_service/*.py``onediff/.../config.json` 等。
执行脚本:`adapt_paths.sh`(业务代码)+ `fix_conda_paths.py`conda环境)。
### 2. webui 配置改动
| 改动 | 说明 |
|------|------|
| `config.json` 的 onediff compiler 路径 | 指向本机 onediff 目录 |
| 启动加 `HF_HUB_OFFLINE=1` | 本机无法访问 huggingface.co,用本地缓存离线加载 CLIP |
| SD 模型 | 实际加载 `v1-5-pruned-emaonly`config 里写的 majicmix 不存在,webui 自动 fallback |
### 3. 换发型推理改动(`hair_service_sd/gen_super_image.py`
`refiner_checkpoint` 从不存在的 `majicmixRealistic_v7.safetensors` 改为 `v1-5-pruned-emaonly.safetensors`(第185、213行)。否则 webui 报 `Could not find checkpoint` 错误。
### 4. photo_service 训练改动(`photo_service/lora_train_service_1.py`
| 改动 | 原值 | 新值 |
|------|------|------|
| LoRA 输出目录 | `/gz-fs/Lora` | webui 的 models/Lora 目录(实际路径) |
| kohya 训练目录 | `/root/project/kohya_ss_home` | 本机 kohya_ss_home |
| 训练方式 | `sudo docker run chinatszrn/ubuntu:kohya_ss accelerate launch ...` | 本地 `accelerate launch ...`conda kohya 环境) |
| 基础模型 | `/mnt/nas_hdd/.../majicmixRealistic_v7.safetensors` | 本机 `v1-5-pruned-emaonly.safetensors` |
| 优化器 | AdamW8bitbitsandbytes | AdamWtorch 原生) |
| tokenizer | `--tokenizer_cache_dir=/home/chinatszrn/.cache/clip` | 删除(用 HF 缓存 + 离线模式) |
---
## 七、已知限制与注意事项
1. **OSS 上传**:换发色/换发型默认 `output_format=url` 会把结果图上传到**生产阿里云 OSS**(密钥硬编码在代码中)。仅本地测试时建议用 `output_format=base64` 避免污染生产环境。
2. **网络限制**:本机无法访问 `huggingface.co`。webui 和训练都依赖本地 HF 缓存(`~/.cache/huggingface`),已配置离线模式。如需新增 HF 模型需手动准备缓存。
3. **ref_user_imgs 为空**:影响 uploadHair 生成训练素材的完整性(见第五节说明)。
4. **单卡部署**GPU 固定使用 device 0。`CUDA_VISIBLE_DEVICES=0` 已写入启动脚本。如有多卡需求需调整配置。
5. **服务进程保活**:服务通过 nohup + wrapper 脚本启动。**切勿直接 `python xxx.py &` 启动**,会被 shell 会话退出时清理。请始终用 `start_all.sh start``start_*.sh` 脚本。
6. **webui 启动较慢**:加载 SD 模型需 1-2 分钟,启动后需等待 `start_all.sh status` 显示端口就绪才能正常服务。
---
## 八、快速排障
| 现象 | 可能原因 | 解决 |
|------|---------|------|
| 换发型报 `推理发型失败` | webui 未就绪或 LoRA 缺失 | `start_all.sh status` 检查 webui 端口;确认发型 LoRA 存在 |
| webui 报 `Could not find checkpoint` | refiner 模型名不对 | 确认 `gen_super_image.py` 的 refiner_checkpoint 为 `v1-5-pruned-emaonly.safetensors` |
| 训练报 bitsandbytes 错误 | bnb 版本/优化器问题 | 确认用 `AdamW`(非 AdamW8bit |
| webui 卡在 huggingface 重试 | HF 离线模式未启用 | 启动脚本已设 `HF_HUB_OFFLINE=1`,确认未丢失 |
| 服务启动后立即退出 | 未用 wrapper 脚本启动 | 用 `start_all.sh start` 启动 |
| conda 环境 python 段错误 | 环境二进制损坏 | 重新 rsync 环境并用 `fix_conda_paths.py` 修复(勿用 sed |