初始化:换发型/换发色/训练发型服务

包含:
- 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密钥已脱敏为环境变量,原文件备份在本地
This commit is contained in:
xsl
2026-07-07 13:53:52 +08:00
commit 443cfa298f
312 changed files with 67065 additions and 0 deletions
+344
View File
@@ -0,0 +1,344 @@
# 换发型服务 - 本地部署文档
> 本文档说明换发型服务(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 |