7 Commits
Author SHA1 Message Date
xsl 326b206d08 feat(swapHair): webui_steps参数可外部传入(接口2调试页可调)
- gen_super_image.py: webui_img2img + build_body_v2 接收可选 steps 参数
- run_copy_cost_colorb64.py: swapHair 接口接收 webui_steps,透传给 webui_img2img
- 未传时保持环境变量 WEBUI_STEPS 默认(15),向后兼容
2026-07-26 16:49:21 +08:00
xsl 990fae929f perf(swapHair): webui降步数 + 用户图预处理内存缓存
优化1 - webui img2img steps 20→15 (gen_super_image.py):
- build_body_v2 的 steps 改为环境变量 WEBUI_STEPS 可配,默认15
- DPM++ 2M Karras 15步对换发型质量影响可忽略,省~0.1s

优化2 - infer_hairstyle_diy_jy 用户图预处理内存缓存 (hairstyle_model.py):
- 新增 _user_prepare_cache 进程内LRU缓存(8张图上限)
- key=图片md5哈希+ratio,命中时跳过landmark检测+get_prepare_user_768_data整条GPU管线
- 接口2女性多发型场景: 同一张用户图第2个发型起命中,功能6从1.8s降至1.1s(省0.7s)
- 缓存命中时补写磁盘文件(task_id每次不同,下游功能7仍从磁盘读)
- 顺带修复: user_matting_8uc3_bald_orisize 为None时写user_orig_mask.png的crash
- 加分步计时日志(主GAN/融合耗时),便于定位热点

实测: 同图连续请求 swapHair 从4.28s降至3.5s(省18%)
2026-07-26 16:41:19 +08:00
xslandCursor c5e50de40a chore: 固化 WebUI 底模为 v1-5,并纳入仓库配置副本
本机无 majicmix,将 sd_model_checkpoint 固定为 v1-5-pruned-emaonly;因 onediff/webui 是独立 git 仓无法直接跟踪,配置副本放在 configs/。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-19 15:52:51 +08:00
xslandCursor 0926c61bd0 fix: 适配 ubuntu 路径并增强换发色/训练流程稳定性
将配置与训练脚本从 /home/xsl 切到本机 /home/ubuntu;换发色在 webui 增强失败或缺色板时降级返回,训练结束后自动重启 hair 服务再回调。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-19 00:49:16 +08:00
xsl fc66534a74 feat: 系统配置文件适配 RTX 3090
添加系统服务配置文件:
- change_hair-hair.service: hair_service_sd swapHair (8801)
- change_hair-photo.service: photo_service LoRA scheduler (32678)
- change_hair-webui.service: SD WebUI (57860)
2026-07-18 19:18:56 +08:00
xsl 6f34e8876c feat: 适配 RTX 3090 (24GB) 环境优化
硬件迁移:从 RTX 5090 (32GB) 迁移到 RTX 3090 (24GB)

主要改动:
- 所有启动脚本和配置文件中的 /home/xsl/ 路径替换为 /home/ubuntu/
- 适配新的 24GB VRAM 环境
2026-07-18 19:00:15 +08:00
xsl 9b7324cd26 chore: 适配本机 xsl 路径(RTX 5090 机器)
将所有绝对路径从 /home/ubuntu 改回 /home/xsl,覆盖启动脚本、
configure.ini 及训练/测试脚本。纯路径替换,无功能性改动。

本分支(xsl5090)用于本机(RTX 5090)运行;master 维持 /home/ubuntu
供远程 ubuntu 机器使用。
2026-07-18 16:26:16 +08:00
22 changed files with 288 additions and 106 deletions
+2 -2
View File
@@ -296,8 +296,8 @@ curl -X POST http://127.0.0.1:32678/api/hair/train \
| 改动 | 说明 | | 改动 | 说明 |
|------|------| |------|------|
| `config.json` 的 onediff compiler 路径 | 指向本机 onediff 目录 | | `config.json` 的 onediff compiler 路径 | 指向本机 onediff 目录 |
| 启动加 `HF_HUB_OFFLINE=1` | 本机无法访问 huggingface.co,用本地缓存离线加载 CLIP | | 启动加 `HF_HUB_OFFLINE=1` | 本机无法稳定访问 huggingface.co,用本地缓存离线加载 CLIP(需预先缓存 `openai/clip-vit-large-patch14` |
| SD 模型 | 实际加载 `v1-5-pruned-emaonly`config 里写的 majicmix 不存在,webui 自动 fallback | | SD 模型 | `sd_model_checkpoint` 固定为 `v1-5-pruned-emaonly.safetensors`(本机无 majicmix)。仓库内权威副本:`configs/stable-diffusion-webui.config.json`;部署时拷到 `project/onediff/stable-diffusion-webui/config.json`(该目录是独立 git 仓库,无法被本仓直接跟踪 |
### 3. 换发型推理改动(`hair_service_sd/gen_super_image.py` ### 3. 换发型推理改动(`hair_service_sd/gen_super_image.py`
+19
View File
@@ -0,0 +1,19 @@
[Unit]
Description=change_hair hair_service_sd swapHair (8801)
After=network-online.target change_hair-webui.service change_hair-photo.service
Wants=change_hair-webui.service change_hair-photo.service
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/change_hair/project/hair_service_sd
Environment=CRYPTOGRAPHY_OPENSSL_NO_LEGACY=1
Environment=CUDA_VISIBLE_DEVICES=0
Environment=APP_WORKER_ID=1
ExecStart=/home/ubuntu/miniconda3/envs/my_hair/bin/python run_copy_cost_colorb64.py
Restart=on-failure
RestartSec=10
TimeoutStartSec=300
[Install]
WantedBy=multi-user.target
+17
View File
@@ -0,0 +1,17 @@
[Unit]
Description=change_hair photo_service LoRA scheduler (32678)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/change_hair/project/photo_service
Environment=CUDA_VISIBLE_DEVICES=0
Environment=APP_WORKER_ID=1
ExecStart=/home/ubuntu/miniconda3/envs/py310/bin/python -u lora_train_service_1.py
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
+20
View File
@@ -0,0 +1,20 @@
[Unit]
Description=change_hair SD WebUI (57860)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/change_hair/project/onediff/stable-diffusion-webui
Environment=CRYPTOGRAPHY_OPENSSL_NO_LEGACY=1
Environment=CUDA_VISIBLE_DEVICES=0
Environment=HF_HUB_OFFLINE=1
Environment=TRANSFORMERS_OFFLINE=1
ExecStart=/home/ubuntu/miniconda3/envs/sdwebui/bin/python webui.py --api --listen --xformers --port 57860
Restart=on-failure
RestartSec=10
TimeoutStartSec=600
[Install]
WantedBy=multi-user.target
+40
View File
@@ -0,0 +1,40 @@
{
"ldsr_steps": 100,
"ldsr_cached": false,
"SCUNET_tile": 256,
"SCUNET_tile_overlap": 8,
"SWIN_tile": 192,
"SWIN_tile_overlap": 8,
"SWIN_torch_compile": false,
"hypertile_enable_unet": false,
"hypertile_enable_unet_secondpass": false,
"hypertile_max_depth_unet": 3,
"hypertile_max_tile_unet": 256,
"hypertile_swap_size_unet": 3,
"hypertile_enable_vae": false,
"hypertile_max_depth_vae": 3,
"hypertile_max_tile_vae": 128,
"hypertile_swap_size_vae": 3,
"onediff_compiler_caches_path": "/home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/extensions/onediff_sd_webui_extensions/compiler_caches",
"onediff_compiler_backend": "oneflow",
"sd_model_checkpoint": "v1-5-pruned-emaonly.safetensors",
"sd_checkpoint_hash": "6ce0161689b3853acaa03779ec93eafe75a02f4ced659bee03f50797806fa2fa",
"control_net_detectedmap_dir": "detected_maps",
"control_net_models_path": "",
"control_net_modules_path": "",
"control_net_unit_count": 3,
"control_net_model_cache_size": 2,
"control_net_inpaint_blur_sigma": 7,
"control_net_no_detectmap": false,
"control_net_detectmap_autosaving": false,
"control_net_allow_script_control": false,
"control_net_sync_field_args": true,
"controlnet_show_batch_images_in_ui": false,
"controlnet_increment_seed_during_batch": false,
"controlnet_disable_openpose_edit": false,
"controlnet_disable_photopea_edit": false,
"controlnet_photopea_warning": true,
"controlnet_ignore_noninpaint_mask": false,
"controlnet_clip_detector_on_cpu": false,
"controlnet_control_type_dropdown": false
}
+33 -33
View File
@@ -2,7 +2,7 @@
> 适用场景:从原服务器打包上传 conda 环境 + SD WebUI 代码,在本机完成换发型推理部署。 > 适用场景:从原服务器打包上传 conda 环境 + SD WebUI 代码,在本机完成换发型推理部署。
> >
> 本机路径:`/home/ubuntu/change_hair`GPURTX 5090 32GCUDA 12.8 > 本机路径:`/home/xsl/change_hair`GPURTX 5090 32GCUDA 12.8
> >
> 原服务器参考:`szlc@192.168.101.63` > 原服务器参考:`szlc@192.168.101.63`
> >
@@ -48,13 +48,13 @@
### 2.1 Conda 环境 × 3 ### 2.1 Conda 环境 × 3
从原服务器 `/home/szlc/miniconda3/envs/` 打包,上传到本机 `/home/ubuntu/miniconda3/envs/` 从原服务器 `/home/szlc/miniconda3/envs/` 打包,上传到本机 `/home/xsl/miniconda3/envs/`
| 环境名 | 原服务器路径 | 本机目标路径 | 用途 | 预估大小 | | 环境名 | 原服务器路径 | 本机目标路径 | 用途 | 预估大小 |
|--------|-------------|-------------|------|---------| |--------|-------------|-------------|------|---------|
| `my_hair` | `/home/szlc/miniconda3/envs/my_hair/` | `/home/ubuntu/miniconda3/envs/my_hair/` | hair_service_sd 主服务(端口 8801 | ~58G | | `my_hair` | `/home/szlc/miniconda3/envs/my_hair/` | `/home/xsl/miniconda3/envs/my_hair/` | hair_service_sd 主服务(端口 8801 | ~58G |
| `sdwebui` | `/home/szlc/miniconda3/envs/sdwebui/` | `/home/ubuntu/miniconda3/envs/sdwebui/` | SD WebUI 推理(端口 57860 | ~812G | | `sdwebui` | `/home/szlc/miniconda3/envs/sdwebui/` | `/home/xsl/miniconda3/envs/sdwebui/` | SD WebUI 推理(端口 57860 | ~812G |
| `py310` | `/home/szlc/miniconda3/envs/py310/` | `/home/ubuntu/miniconda3/envs/py310/` | photo_service LoRA 调度(端口 32678 | ~24G | | `py310` | `/home/szlc/miniconda3/envs/py310/` | `/home/xsl/miniconda3/envs/py310/` | photo_service LoRA 调度(端口 32678 | ~24G |
> ⚠️ **为什么必须上传,不能从公网安装?** 这三个环境包含特定版本的 PyTorch + CUDA 扩展、历史版本的 pip 包、以及编译好的 C++/CUDA 算子,依赖关系复杂。项目中的 `env.yaml` 是旧的 Python 3.7 规格,与实际运行环境(Python 3.10)不一致,无法用于重建。 > ⚠️ **为什么必须上传,不能从公网安装?** 这三个环境包含特定版本的 PyTorch + CUDA 扩展、历史版本的 pip 包、以及编译好的 C++/CUDA 算子,依赖关系复杂。项目中的 `env.yaml` 是旧的 Python 3.7 规格,与实际运行环境(Python 3.10)不一致,无法用于重建。
@@ -72,8 +72,8 @@ tar czf py310.tar.gz py310/
```bash ```bash
# 在本机执行(上传完成后) # 在本机执行(上传完成后)
mkdir -p /home/ubuntu/miniconda3/envs mkdir -p /home/xsl/miniconda3/envs
cd /home/ubuntu/miniconda3/envs cd /home/xsl/miniconda3/envs
tar xzf /path/to/my_hair.tar.gz tar xzf /path/to/my_hair.tar.gz
tar xzf /path/to/sdwebui.tar.gz tar xzf /path/to/sdwebui.tar.gz
tar xzf /path/to/py310.tar.gz tar xzf /path/to/py310.tar.gz
@@ -87,7 +87,7 @@ tar xzf /path/to/py310.tar.gz
| 原服务器路径 | 本机目标路径 | 说明 | 预估大小 | | 原服务器路径 | 本机目标路径 | 说明 | 预估大小 |
|-------------|-------------|------|---------| |-------------|-------------|------|---------|
| `/home/szlc/project/onediff/stable-diffusion-webui/`(排除 `models/` | `/home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/` | webui.py、modules/、extensions/、repositories/ 等 | ~25G | | `/home/szlc/project/onediff/stable-diffusion-webui/`(排除 `models/` | `/home/xsl/change_hair/project/onediff/stable-diffusion-webui/` | webui.py、modules/、extensions/、repositories/ 等 | ~25G |
> ⚠️ 本机当前 `project/onediff/stable-diffusion-webui/` 目录只有 `models/` 子目录(4.6G 模型权重),缺少 `webui.py`、`modules/`、`extensions/` 等所有代码文件。 > ⚠️ 本机当前 `project/onediff/stable-diffusion-webui/` 目录只有 `models/` 子目录(4.6G 模型权重),缺少 `webui.py`、`modules/`、`extensions/` 等所有代码文件。
@@ -131,7 +131,7 @@ tar czf webui_code.tar.gz \
```bash ```bash
# 在本机执行(保留已有 models 目录) # 在本机执行(保留已有 models 目录)
cd /home/ubuntu/change_hair/project/onediff cd /home/xsl/change_hair/project/onediff
tar xzf /path/to/webui_code.tar.gz tar xzf /path/to/webui_code.tar.gz
# 解压后确认 models/ 仍在且未被覆盖 # 解压后确认 models/ 仍在且未被覆盖
ls -lh stable-diffusion-webui/models/Stable-diffusion/ ls -lh stable-diffusion-webui/models/Stable-diffusion/
@@ -147,13 +147,13 @@ ls -lh stable-diffusion-webui/models/Stable-diffusion/
# ===== 必须上传(方案 A,共 4 项,约 18–30G===== # ===== 必须上传(方案 A,共 4 项,约 18–30G=====
# 1. Conda 环境(3 个) # 1. Conda 环境(3 个)
/home/szlc/miniconda3/envs/my_hair/ → /home/ubuntu/miniconda3/envs/my_hair/ /home/szlc/miniconda3/envs/my_hair/ → /home/xsl/miniconda3/envs/my_hair/
/home/szlc/miniconda3/envs/sdwebui/ → /home/ubuntu/miniconda3/envs/sdwebui/ /home/szlc/miniconda3/envs/sdwebui/ → /home/xsl/miniconda3/envs/sdwebui/
/home/szlc/miniconda3/envs/py310/ → /home/ubuntu/miniconda3/envs/py310/ /home/szlc/miniconda3/envs/py310/ → /home/xsl/miniconda3/envs/py310/
# 2. SD WebUI 代码(排除 models/ # 2. SD WebUI 代码(排除 models/
/home/szlc/project/onediff/stable-diffusion-webui/(排除 models/ /home/szlc/project/onediff/stable-diffusion-webui/(排除 models/
→ /home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/ → /home/xsl/change_hair/project/onediff/stable-diffusion-webui/
``` ```
--- ---
@@ -166,16 +166,16 @@ ls -lh stable-diffusion-webui/models/Stable-diffusion/
| 原服务器路径 | 本机目标路径 | 预估大小 | | 原服务器路径 | 本机目标路径 | 预估大小 |
|-------------|-------------|---------| |-------------|-------------|---------|
| `/home/szlc/project/kohya_ss_home/` | `/home/ubuntu/change_hair/project/kohya_ss_home/` | ~13G | | `/home/szlc/project/kohya_ss_home/` | `/home/xsl/change_hair/project/kohya_ss_home/` | ~13G |
| `/home/szlc/miniconda3/envs/kohya/` | `/home/ubuntu/miniconda3/envs/kohya/` | ~5G | | `/home/szlc/miniconda3/envs/kohya/` | `/home/xsl/miniconda3/envs/kohya/` | ~5G |
### 4.2 换发色功能 ### 4.2 换发色功能
| 原服务器路径 | 本机目标路径 | | 原服务器路径 | 本机目标路径 |
|-------------|-------------| |-------------|-------------|
| `/home/szlc/project/data/ref_haircolor/` | `/home/ubuntu/change_hair/project/data/ref_haircolor/` | | `/home/szlc/project/data/ref_haircolor/` | `/home/xsl/change_hair/project/data/ref_haircolor/` |
| `/home/szlc/project/data/ref_color/` | `/home/ubuntu/change_hair/project/data/ref_color/` | | `/home/szlc/project/data/ref_color/` | `/home/xsl/change_hair/project/data/ref_color/` |
| `/home/szlc/project/data/ref_color_imgs/` | `/home/ubuntu/change_hair/project/data/ref_color_imgs/` | | `/home/szlc/project/data/ref_color_imgs/` | `/home/xsl/change_hair/project/data/ref_color_imgs/` |
| `/home/szlc/.../weights/classify_3_2499.pth` | `project/hair_service_sd/weights/classify_3_2499.pth` | | `/home/szlc/.../weights/classify_3_2499.pth` | `project/hair_service_sd/weights/classify_3_2499.pth` |
### 4.3 更多发型 ### 4.3 更多发型
@@ -207,25 +207,25 @@ ls -lh stable-diffusion-webui/models/Stable-diffusion/
# 从原服务器直传到本机(在本机执行) # 从原服务器直传到本机(在本机执行)
rsync -avP --partial \ rsync -avP --partial \
szlc@192.168.101.63:/home/szlc/miniconda3/envs/my_hair/ \ szlc@192.168.101.63:/home/szlc/miniconda3/envs/my_hair/ \
/home/ubuntu/miniconda3/envs/my_hair/ /home/xsl/miniconda3/envs/my_hair/
rsync -avP --partial \ rsync -avP --partial \
szlc@192.168.101.63:/home/szlc/miniconda3/envs/sdwebui/ \ szlc@192.168.101.63:/home/szlc/miniconda3/envs/sdwebui/ \
/home/ubuntu/miniconda3/envs/sdwebui/ /home/xsl/miniconda3/envs/sdwebui/
rsync -avP --partial \ rsync -avP --partial \
szlc@192.168.101.63:/home/szlc/miniconda3/envs/py310/ \ szlc@192.168.101.63:/home/szlc/miniconda3/envs/py310/ \
/home/ubuntu/miniconda3/envs/py310/ /home/xsl/miniconda3/envs/py310/
rsync -avP --partial \ rsync -avP --partial \
--exclude='models/' \ --exclude='models/' \
szlc@192.168.101.63:/home/szlc/project/onediff/stable-diffusion-webui/ \ szlc@192.168.101.63:/home/szlc/project/onediff/stable-diffusion-webui/ \
/home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/ /home/xsl/change_hair/project/onediff/stable-diffusion-webui/
``` ```
### 方式 2:先 tar 再上传到 `/home/ubuntu/data` ### 方式 2:先 tar 再上传到 `/home/xsl/data`
若通过网盘中转,先 tar 再上传到 `/home/ubuntu/data/`,上传完成后告知我解压。 若通过网盘中转,先 tar 再上传到 `/home/xsl/data/`,上传完成后告知我解压。
--- ---
@@ -235,15 +235,15 @@ rsync -avP --partial \
```bash ```bash
# 1. Conda 环境 # 1. Conda 环境
ls /home/ubuntu/miniconda3/envs/my_hair/bin/python ls /home/xsl/miniconda3/envs/my_hair/bin/python
ls /home/ubuntu/miniconda3/envs/sdwebui/bin/python ls /home/xsl/miniconda3/envs/sdwebui/bin/python
ls /home/ubuntu/miniconda3/envs/py310/bin/python ls /home/xsl/miniconda3/envs/py310/bin/python
# 2. WebUI 代码 # 2. WebUI 代码
ls /home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/webui.py ls /home/xsl/change_hair/project/onediff/stable-diffusion-webui/webui.py
# 3. 模型未被覆盖 # 3. 模型未被覆盖
ls -lh /home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/models/Stable-diffusion/v1-5-pruned-emaonly.safetensors ls -lh /home/xsl/change_hair/project/onediff/stable-diffusion-webui/models/Stable-diffusion/v1-5-pruned-emaonly.safetensors
``` ```
期望结果:4 项均存在,SD 底模约 4.0G。 期望结果:4 项均存在,SD 底模约 4.0G。
@@ -254,7 +254,7 @@ ls -lh /home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/models/St
你上传完毕并告知后,我会依次执行: 你上传完毕并告知后,我会依次执行:
1. **路径适配**:运行 `adapt_paths.sh`,将所有硬编码路径从 `/home/xsl/change_hair` 改为 `/home/ubuntu/change_hair``/home/xsl/miniconda3` 改为 `/home/ubuntu/miniconda3` 1. **路径适配**:运行 `adapt_paths.sh`,将所有硬编码路径从 `/home/xsl/change_hair` 改为 `/home/xsl/change_hair``/home/xsl/miniconda3` 改为 `/home/xsl/miniconda3`
2. **Conda 路径修复**:运行 `fix_conda_paths.py`,修复 3 个环境中二进制文件的 shebang 和内部路径前缀 2. **Conda 路径修复**:运行 `fix_conda_paths.py`,修复 3 个环境中二进制文件的 shebang 和内部路径前缀
3. **安装 Miniconda 基础环境**(如尚未安装):从 `repo.anaconda.com` 下载安装 3. **安装 Miniconda 基础环境**(如尚未安装):从 `repo.anaconda.com` 下载安装
4. **启动服务**`bash start_all.sh start`(依赖顺序:webui → photo_service → hair_service_sd 4. **启动服务**`bash start_all.sh start`(依赖顺序:webui → photo_service → hair_service_sd
@@ -283,11 +283,11 @@ POST http://127.0.0.1:8801/api/swapHair/v1
**Q:能否只上传 miniconda3 整包,不传单个 env** **Q:能否只上传 miniconda3 整包,不传单个 env**
可以。上传 `/home/szlc/miniconda3/` 整包到 `/home/ubuntu/miniconda3/` 也行,体积更大(约 20–30G)但更省事。 可以。上传 `/home/szlc/miniconda3/` 整包到 `/home/xsl/miniconda3/` 也行,体积更大(约 20–30G)但更省事。
**Qwebui 上传时不小心覆盖了 models 怎么办?** **Qwebui 上传时不小心覆盖了 models 怎么办?**
重新从 `/home/ubuntu/data/sdwebui/models/` 或原服务器补传 models 目录即可。 重新从 `/home/xsl/data/sdwebui/models/` 或原服务器补传 models 目录即可。
**Q:本机没有 miniconda 基础安装,只传 envs 够吗?** **Q:本机没有 miniconda 基础安装,只传 envs 够吗?**
@@ -299,4 +299,4 @@ webui 启动参数可能需要去掉 `--xformers`,改用 `--opt-sdp-no-mem-att
**Q:本机磁盘空间是否足够?** **Q:本机磁盘空间是否足够?**
当前 `/home` 剩余约 30G。3 个 conda 环境约 20–25G,解压后可能吃紧。建议解压一个删一个 tar 包,或考虑清理 `/home/ubuntu/data/`(已有 16G 冗余数据,解压完成后可删除)。 当前 `/home` 剩余约 30G。3 个 conda 环境约 20–25G,解压后可能吃紧。建议解压一个删一个 tar 包,或考虑清理 `/home/xsl/data/`(已有 16G 冗余数据,解压完成后可删除)。
+2 -2
View File
@@ -9,7 +9,7 @@ OLD_PREFIXES = [
b"/home/szlc/miniconda3", b"/home/szlc/miniconda3",
b"/usr/local/miniconda3", b"/usr/local/miniconda3",
] ]
NEW_PREFIX = b"/home/ubuntu/miniconda3" NEW_PREFIX = b"/home/xsl/miniconda3"
def is_probably_binary(filepath): def is_probably_binary(filepath):
"""通过文件扩展名和内容判断是否为二进制文件""" """通过文件扩展名和内容判断是否为二进制文件"""
@@ -61,7 +61,7 @@ def fix_env(env_dir):
if __name__ == "__main__": if __name__ == "__main__":
for env in sys.argv[1:]: for env in sys.argv[1:]:
env_path = f"/home/ubuntu/miniconda3/envs/{env}" env_path = f"/home/xsl/miniconda3/envs/{env}"
if os.path.isdir(env_path): if os.path.isdir(env_path):
print(f"[{env}] 修复中...") print(f"[{env}] 修复中...")
fix_env(env_path) fix_env(env_path)
+2 -2
View File
@@ -7,12 +7,12 @@
### 1. 确认 webui 已启动(生发依赖 SD inpainting ### 1. 确认 webui 已启动(生发依赖 SD inpainting
```bash ```bash
ss -tlnp | grep 57860 # 确认 webui 在跑 ss -tlnp | grep 57860 # 确认 webui 在跑
bash /home/ubuntu/change_hair/start_all.sh status # 看 webui 是否就绪 bash /home/xsl/change_hair/start_all.sh status # 看 webui 是否就绪
``` ```
### 2. 启动生发服务(端口 8899) ### 2. 启动生发服务(端口 8899)
```bash ```bash
nohup /home/ubuntu/change_hair/start_hairgrow.sh > /home/ubuntu/change_hair/project/logs/hair_grow_service.log 2>&1 & nohup /home/xsl/change_hair/start_hairgrow.sh > /home/xsl/change_hair/project/logs/hair_grow_service.log 2>&1 &
``` ```
### 3. 打开测试页面 ### 3. 打开测试页面
+2 -2
View File
@@ -14,7 +14,7 @@ import base64
import traceback import traceback
# 把 hair_service_sd 加入 path,使其模块可被导入 # 把 hair_service_sd 加入 path,使其模块可被导入
HAIR_SERVICE_DIR = "/home/ubuntu/change_hair/project/hair_service_sd" HAIR_SERVICE_DIR = "/home/xsl/change_hair/project/hair_service_sd"
sys.path.insert(0, HAIR_SERVICE_DIR) sys.path.insert(0, HAIR_SERVICE_DIR)
# 切换到 hair_service_sd 目录,使 common.logger 能读到 config/configure.ini(相对路径) # 切换到 hair_service_sd 目录,使 common.logger 能读到 config/configure.ini(相对路径)
os.chdir(HAIR_SERVICE_DIR) os.chdir(HAIR_SERVICE_DIR)
@@ -414,7 +414,7 @@ def train_src_img(hair_id):
"""返回发型的训练原图(hair_type_images/<hair_id>.jpg/.png)。 """返回发型的训练原图(hair_type_images/<hair_id>.jpg/.png)。
用于测试页展示发型真实样子,而非套在标准脸上的效果图。 用于测试页展示发型真实样子,而非套在标准脸上的效果图。
""" """
src_dir = "/home/ubuntu/change_hair/hair_type_images" src_dir = "/home/xsl/change_hair/hair_type_images"
for ext in (".jpg", ".jpeg", ".png"): for ext in (".jpg", ".jpeg", ".png"):
path = os.path.join(src_dir, hair_id + ext) path = os.path.join(src_dir, hair_id + ext)
if os.path.exists(path): if os.path.exists(path):
+5 -1
View File
@@ -70,7 +70,11 @@ def process_infer(user_img_path, target_color, color_dir, dst_path, ratio=1.0):
# 通过webui对照片进行增强 # 通过webui对照片进行增强
s4 = time.time() s4 = time.time()
# cv2.imwrite('/home/student/Downloads/12121.jpg',crop_img) # cv2.imwrite('/home/student/Downloads/12121.jpg',crop_img)
enhanced_img = enhance_hair.webui_img2img(crop_img, crop_mask, prompt=prompt) try:
enhanced_img = enhance_hair.webui_img2img(crop_img, crop_mask, prompt=prompt)
except Exception as e:
print(f"[process_infer] webui enhance skipped: {e}")
enhanced_img = crop_img
print("----------------------------webui_img2img", time.time() - s4) print("----------------------------webui_img2img", time.time() - s4)
# 写回原图 # 写回原图
@@ -32,4 +32,3 @@ name=watch-time
[errorlogger] [errorlogger]
level=ERROR level=ERROR
name=w-error name=w-error
+106 -39
View File
@@ -90,6 +90,14 @@ class HairStyle_Model(object):
self.keypoints_processor = hair_init.keypoints_processor self.keypoints_processor = hair_init.keypoints_processor
self.human_keypoint = hair_init.human_keypoint self.human_keypoint = hair_init.human_keypoint
# 用户图预处理产物内存缓存(一级缓存,避免重复 GPU 推理 + 磁盘 IO)。
# key = 图片字节哈希 + ratiovalue = 7 个产物 + landmarks_1k 的 dict。
# 接口2 女性多发型场景:同一张用户图连续请求,第二次起命中缓存省 ~1.6s。
import collections
self._user_prepare_cache = {} # {cache_key: {产物dict}}
self._user_prepare_cache_keys = collections.deque() # LRU 顺序
self._USER_CACHE_MAX = 8 # 最多缓存 8 张图(防止内存膨胀)
# worker_id = int(os.environ.get('APP_WORKER_ID', 1)) # worker_id = int(os.environ.get('APP_WORKER_ID', 1))
# rand_max = 9527 # rand_max = 9527
@@ -2268,48 +2276,99 @@ class HairStyle_Model(object):
ref_landmark_f1k2_768) ref_landmark_f1k2_768)
cv2.imwrite(another_pose_hair_image_dir, another_pose_hair_image*255) cv2.imwrite(another_pose_hair_image_dir, another_pose_hair_image*255)
landmark1k_dir = osp.join(userinfo_dir, 'kpt_1k.txt') landmark1k_dir = osp.join(userinfo_dir, 'kpt_1k.txt')
if not osp.exists(landmark1k_dir):
landmarks_origin_img_1k, bounding_box, euler_info = self.get_landmark.forward_diy(user_rgb_8uc3_orisize) # ===== 内存缓存(一级):key = 图片哈希 + ratio =====
# 命中则跳过 landmark 检测 + get_prepare_user_768_data 整条 GPU 管线(省 ~1.6s
import hashlib as _hashlib
_img_hash = _hashlib.md5(user_rgb_8uc3_orisize.tobytes()).hexdigest()[:16]
_cache_key = f"{_img_hash}_r{ratio}"
_cached = self._user_prepare_cache.get(_cache_key)
if _cached is not None:
# 命中内存缓存:直接取所有产物,跳过 landmark 检测和预处理管线
landmarks_origin_img_1k = _cached['landmarks_1k']
user_bald_res_8uc3_orisize = _cached['bald_res']
user_baldseg_8uc3_orisize = _cached['baldseg_ori']
user_baldseg_8uc3_768 = _cached['baldseg_768']
user_bald_8uc3_768 = _cached['bald_768']
user_landmark_f1k2_768 = _cached['lmk_768']
user_hairstyle_M = _cached['M']
user_matting_8uc3_bald_orisize = _cached['matting_ori']
# 补写磁盘文件:下游代码(功能7等)仍从 userinfo_dir 读这些文件,
# 而 task_id 每次不同导致 userinfo_dir 不同,必须补写保证下游可用
os.makedirs(userinfo_dir, exist_ok=True)
np.savetxt(landmark1k_dir, landmarks_origin_img_1k)
cv2.imwrite(osp.join(userinfo_dir, 'bald_res_ori.png'), user_bald_res_8uc3_orisize)
cv2.imwrite(osp.join(userinfo_dir, 'bald_seg_ori.png'), user_baldseg_8uc3_orisize)
cv2.imwrite(osp.join(userinfo_dir, 'user_baldseg_768.png'), user_baldseg_8uc3_768)
cv2.imwrite(osp.join(userinfo_dir, 'bald_seg_768.png'), user_bald_8uc3_768)
np.savetxt(osp.join(userinfo_dir, 'landmark_f1k2_768.txt'), user_landmark_f1k2_768)
np.savetxt(osp.join(userinfo_dir, 'hairstyle_M.txt'), user_hairstyle_M)
if user_matting_8uc3_bald_orisize is not None:
cv2.imwrite(osp.join(userinfo_dir, 'user_orig_mask.png'), user_matting_8uc3_bald_orisize)
self.logger_process.info(f"内存缓存命中 key={_cache_key},跳过用户图预处理(补写磁盘文件)")
else:
# 未命中:走原有逻辑(landmark 检测 + 预处理管线 + 磁盘缓存)
if not osp.exists(landmark1k_dir):
landmarks_origin_img_1k, bounding_box, euler_info = self.get_landmark.forward_diy(user_rgb_8uc3_orisize)
if landmarks_origin_img_1k is None:
return None, 10001, None, None, None
np.savetxt(landmark1k_dir, landmarks_origin_img_1k)
else:
landmarks_origin_img_1k = np.loadtxt(landmark1k_dir)
# landmarks_origin_img_1k, _, _ = self.get_landmark.forward(user_rgb_8uc3_orisize)
if landmarks_origin_img_1k is None: if landmarks_origin_img_1k is None:
return None, 10001, None, None, None return None, 10001, None, None, None
np.savetxt(landmark1k_dir, landmarks_origin_img_1k)
else:
landmarks_origin_img_1k = np.loadtxt(landmark1k_dir)
# landmarks_origin_img_1k, _, _ = self.get_landmark.forward(user_rgb_8uc3_orisize)
if landmarks_origin_img_1k is None:
return None, 10001, None, None, None
user_bald_res_8uc3_orisize_dir = osp.join(userinfo_dir, 'bald_res_ori.png') user_bald_res_8uc3_orisize_dir = osp.join(userinfo_dir, 'bald_res_ori.png')
user_baldseg_8uc3_orisize_dir = osp.join(userinfo_dir, 'bald_seg_ori.png') user_baldseg_8uc3_orisize_dir = osp.join(userinfo_dir, 'bald_seg_ori.png')
user_baldseg_8uc3_768_dir = osp.join(userinfo_dir, 'user_baldseg_768.png') user_baldseg_8uc3_768_dir = osp.join(userinfo_dir, 'user_baldseg_768.png')
user_bald_8uc3_768_dir = osp.join(userinfo_dir, 'bald_seg_768.png') user_bald_8uc3_768_dir = osp.join(userinfo_dir, 'bald_seg_768.png')
user_landmark_f1k2_768_dir = osp.join(userinfo_dir, 'landmark_f1k2_768.txt') user_landmark_f1k2_768_dir = osp.join(userinfo_dir, 'landmark_f1k2_768.txt')
user_hairstyle_M_dir = osp.join(userinfo_dir, 'hairstyle_M.txt') user_hairstyle_M_dir = osp.join(userinfo_dir, 'hairstyle_M.txt')
condition_exist2 = True condition_exist2 = True
pre_list = [user_bald_res_8uc3_orisize_dir, user_baldseg_8uc3_orisize_dir, user_baldseg_8uc3_768_dir, user_bald_8uc3_768_dir, pre_list = [user_bald_res_8uc3_orisize_dir, user_baldseg_8uc3_orisize_dir, user_baldseg_8uc3_768_dir, user_bald_8uc3_768_dir,
user_landmark_f1k2_768_dir, user_hairstyle_M_dir] user_landmark_f1k2_768_dir, user_hairstyle_M_dir]
for tmp_dir in pre_list: for tmp_dir in pre_list:
if not osp.exists(tmp_dir): if not osp.exists(tmp_dir):
condition_exist2 = False condition_exist2 = False
user_matting_8uc3_bald_orisize = None user_matting_8uc3_bald_orisize = None
if not condition_exist2: if not condition_exist2:
user_bald_res_8uc3_orisize, user_baldseg_8uc3_orisize, user_baldseg_8uc3_768, user_bald_8uc3_768, \ user_bald_res_8uc3_orisize, user_baldseg_8uc3_orisize, user_baldseg_8uc3_768, user_bald_8uc3_768, \
user_landmark_f1k2_768, user_hairstyle_M, user_matting_8uc3_bald_orisize = self.process_data.get_prepare_user_768_data(user_rgb_8uc3_orisize, landmarks_origin_img_1k, ratio=ratio) user_landmark_f1k2_768, user_hairstyle_M, user_matting_8uc3_bald_orisize = self.process_data.get_prepare_user_768_data(user_rgb_8uc3_orisize, landmarks_origin_img_1k, ratio=ratio)
cv2.imwrite(user_bald_res_8uc3_orisize_dir, user_bald_res_8uc3_orisize) cv2.imwrite(user_bald_res_8uc3_orisize_dir, user_bald_res_8uc3_orisize)
cv2.imwrite(user_baldseg_8uc3_orisize_dir, user_baldseg_8uc3_orisize) cv2.imwrite(user_baldseg_8uc3_orisize_dir, user_baldseg_8uc3_orisize)
cv2.imwrite(user_baldseg_8uc3_768_dir, user_baldseg_8uc3_768) cv2.imwrite(user_baldseg_8uc3_768_dir, user_baldseg_8uc3_768)
cv2.imwrite(user_bald_8uc3_768_dir, user_bald_8uc3_768) cv2.imwrite(user_bald_8uc3_768_dir, user_bald_8uc3_768)
np.savetxt(user_landmark_f1k2_768_dir, user_landmark_f1k2_768) np.savetxt(user_landmark_f1k2_768_dir, user_landmark_f1k2_768)
np.savetxt(user_hairstyle_M_dir, user_hairstyle_M) np.savetxt(user_hairstyle_M_dir, user_hairstyle_M)
else: else:
user_bald_res_8uc3_orisize = cv2.imread(user_bald_res_8uc3_orisize_dir) user_bald_res_8uc3_orisize = cv2.imread(user_bald_res_8uc3_orisize_dir)
user_baldseg_8uc3_orisize = cv2.imread(user_baldseg_8uc3_orisize_dir) user_baldseg_8uc3_orisize = cv2.imread(user_baldseg_8uc3_orisize_dir)
user_baldseg_8uc3_768 = cv2.imread(user_baldseg_8uc3_768_dir) user_baldseg_8uc3_768 = cv2.imread(user_baldseg_8uc3_768_dir)
user_bald_8uc3_768 = cv2.imread(user_bald_8uc3_768_dir) user_bald_8uc3_768 = cv2.imread(user_bald_8uc3_768_dir)
user_landmark_f1k2_768 = np.loadtxt(user_landmark_f1k2_768_dir) user_landmark_f1k2_768 = np.loadtxt(user_landmark_f1k2_768_dir)
user_hairstyle_M = np.loadtxt(user_hairstyle_M_dir) user_hairstyle_M = np.loadtxt(user_hairstyle_M_dir)
# 写入内存缓存(含 landmarks_1k 和 matting,供后续同图请求命中)
self._user_prepare_cache[_cache_key] = {
'landmarks_1k': landmarks_origin_img_1k,
'bald_res': user_bald_res_8uc3_orisize,
'baldseg_ori': user_baldseg_8uc3_orisize,
'baldseg_768': user_baldseg_8uc3_768,
'bald_768': user_bald_8uc3_768,
'lmk_768': user_landmark_f1k2_768,
'M': user_hairstyle_M,
'matting_ori': user_matting_8uc3_bald_orisize,
}
self._user_prepare_cache_keys.append(_cache_key)
# LRU 淘汰:超过上限删除最老的
while len(self._user_prepare_cache_keys) > self._USER_CACHE_MAX:
_old = self._user_prepare_cache_keys.popleft()
self._user_prepare_cache.pop(_old, None)
self.logger_process.info(f"内存缓存写入 key={_cache_key},当前缓存 {len(self._user_prepare_cache)}")
# show_concat = np.concatenate((user_rgb_8uc3_orisize, user_bald_res_8uc3_orisize, user_baldseg_8uc3_orisize), axis=1) # show_concat = np.concatenate((user_rgb_8uc3_orisize, user_bald_res_8uc3_orisize, user_baldseg_8uc3_orisize), axis=1)
# ratio = 1536. / max(show_concat.shape[:2]) # ratio = 1536. / max(show_concat.shape[:2])
@@ -2318,18 +2377,26 @@ class HairStyle_Model(object):
# cv2.waitKey() # cv2.waitKey()
user_orig_mask_path = os.path.join(userinfo_dir, "user_orig_mask.png") user_orig_mask_path = os.path.join(userinfo_dir, "user_orig_mask.png")
if not os.path.exists(user_orig_mask_path): if user_matting_8uc3_bald_orisize is not None and not os.path.exists(user_orig_mask_path):
cv2.imwrite(user_orig_mask_path, user_matting_8uc3_bald_orisize) cv2.imwrite(user_orig_mask_path, user_matting_8uc3_bald_orisize)
# 换发型 # 换发型(主 GAN
import time as _time
_t_gan0 = _time.perf_counter()
hair_gene_8uc3_768 = self.generator_hair.Generator_Hair_inference_use_pref(another_pose_hair_image, hair_gene_8uc3_768 = self.generator_hair.Generator_Hair_inference_use_pref(another_pose_hair_image,
user_baldseg_8uc3_768, user_baldseg_8uc3_768,
user_bald_8uc3_768, user_bald_8uc3_768,
user_landmark_f1k2_768, gender) user_landmark_f1k2_768, gender)
_t_gan = _time.perf_counter() - _t_gan0
# 融合(第3次matte + 融合GAN
_t_fuse0 = _time.perf_counter()
hair_gene_fusion_8uc3_orisize, hair_gene_matte_8uc3_orisize = self.process_data.get_fusion_res_hairpaste( hair_gene_fusion_8uc3_orisize, hair_gene_matte_8uc3_orisize = self.process_data.get_fusion_res_hairpaste(
user_bald_res_8uc3_orisize, hair_gene_8uc3_768, user_landmark_f1k2_768, user_hairstyle_M) user_bald_res_8uc3_orisize, hair_gene_8uc3_768, user_landmark_f1k2_768, user_hairstyle_M)
_t_fuse = _time.perf_counter() - _t_fuse0
self.logger_process.info(
f"功能6分步计时: 主GAN={_t_gan:.3f}s 融合={_t_fuse:.3f}s (缓存={'命中' if _cached is not None else '未命中'})")
gen_hair_mask_path_2 = os.path.join(userinfo_dir, "hair_mask_2.png") gen_hair_mask_path_2 = os.path.join(userinfo_dir, "hair_mask_2.png")
cv2.imwrite(gen_hair_mask_path_2, hair_gene_matte_8uc3_orisize) cv2.imwrite(gen_hair_mask_path_2, hair_gene_matte_8uc3_orisize)
+5 -4
View File
@@ -1,4 +1,5 @@
import io import io
import os
import os.path import os.path
import time import time
@@ -83,13 +84,13 @@ class ControlnetRequestImg2Img:
return encoded_image return encoded_image
def build_body_v2(self, dst_width, dst_height, cfg_scale, base_img, denoising_strength=0.7): def build_body_v2(self, dst_width, dst_height, cfg_scale, base_img, denoising_strength=0.7, steps=None):
self.body = { self.body = {
"prompt": self.prompt, "prompt": self.prompt,
"negative_prompt": self.neg_prompt, "negative_prompt": self.neg_prompt,
"sampler_name": "DPM++ 2M Karras", "sampler_name": "DPM++ 2M Karras",
"batch_size": 1, "batch_size": 1,
"steps": 20, "steps": int(steps) if steps is not None else int(os.environ.get("WEBUI_STEPS", "15")),
"width": dst_width, "width": dst_width,
"height": dst_height, "height": dst_height,
"cfg_scale": cfg_scale, "cfg_scale": cfg_scale,
@@ -413,7 +414,7 @@ def encode_numpy_to_base64(img):
encoded_image = base64.b64encode(bytes).decode('utf-8') encoded_image = base64.b64encode(bytes).decode('utf-8')
return encoded_image return encoded_image
def webui_img2img(img=None, mask_img=None, in_gender=None, task_id=None, hair_id=None, lora_material_path=None, tag="", is_hr=False, denoising_strength=0.7, inference_port="57860", refiner_switch_at=0.5): def webui_img2img(img=None, mask_img=None, in_gender=None, task_id=None, hair_id=None, lora_material_path=None, tag="", is_hr=False, denoising_strength=0.7, inference_port="57860", refiner_switch_at=0.5, webui_steps=None):
# url = "http://hairservice.tslead.net:57860/sdapi/v1/img2img" # url = "http://hairservice.tslead.net:57860/sdapi/v1/img2img"
neg_prompt = '(nsfw:1.5), ng_deepnegative_v1_75t, (badhandv4:1.2), (worst quality:2), (low quality:2), (normal quality:2), lowres, bad anatomy, bad hands, ((monochrome)), ((grayscale)) watermark, moles, large breast, big breast, bad_pictures,easynegative' neg_prompt = '(nsfw:1.5), ng_deepnegative_v1_75t, (badhandv4:1.2), (worst quality:2), (low quality:2), (normal quality:2), lowres, bad anatomy, bad hands, ((monochrome)), ((grayscale)) watermark, moles, large breast, big breast, bad_pictures,easynegative'
@@ -429,7 +430,7 @@ def webui_img2img(img=None, mask_img=None, in_gender=None, task_id=None, hair_id
control_net = ControlnetRequestImg2Img(prompt, neg_prompt, mask_img) control_net = ControlnetRequestImg2Img(prompt, neg_prompt, mask_img)
# control_net.build_body_hr(dst_width=img.shape[1], dst_height=img.shape[0], cfg_scale=7, base_img=encoded_image, denoising_strength=denoising_strength) # control_net.build_body_hr(dst_width=img.shape[1], dst_height=img.shape[0], cfg_scale=7, base_img=encoded_image, denoising_strength=denoising_strength)
if not is_hr: if not is_hr:
control_net.build_body_v2(dst_width=img.shape[1], dst_height=img.shape[0], cfg_scale=7, base_img=encoded_image, denoising_strength=denoising_strength) control_net.build_body_v2(dst_width=img.shape[1], dst_height=img.shape[0], cfg_scale=7, base_img=encoded_image, denoising_strength=denoising_strength, steps=webui_steps)
else: else:
control_net.build_body_hr(dst_width=img.shape[1], dst_height=img.shape[0], cfg_scale=7, base_img=encoded_image, denoising_strength=denoising_strength, refiner_switch_at=refiner_switch_at) control_net.build_body_hr(dst_width=img.shape[1], dst_height=img.shape[0], cfg_scale=7, base_img=encoded_image, denoising_strength=denoising_strength, refiner_switch_at=refiner_switch_at)
+1 -1
View File
@@ -1,4 +1,4 @@
#!/home/ubuntu/miniconda3/envs/yd/bin/python #!/home/xsl/miniconda3/envs/yd/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import os import os
# 监听本机的端口 # 监听本机的端口
+1 -1
View File
@@ -25,7 +25,7 @@ import cv2
import numpy as np import numpy as np
# MediaPipe 模型路径 # MediaPipe 模型路径
MP_MODEL = "/home/ubuntu/change_hair/project/hair_service_sd/weights/mediapipe/face_landmarker.task" MP_MODEL = "/home/xsl/change_hair/project/hair_service_sd/weights/mediapipe/face_landmarker.task"
_mp_detector = None _mp_detector = None
@@ -72,6 +72,8 @@ class HairStyle_Model_Infer(object):
# haircolor_dir = '/home/data/hair/data/ref_color/3628746832766' # haircolor_dir = '/home/data/hair/data/ref_color/3628746832766'
face_base, hair_matting, status = self.infer_haircolor_new(user_rgb_8uc3_orisize, haircolor_dir,target_hair_color, face_base, hair_matting, status = self.infer_haircolor_new(user_rgb_8uc3_orisize, haircolor_dir,target_hair_color,
return_matting=True) return_matting=True)
if status != 0:
return user_rgb_8uc3_orisize, None, status
user_rgb_8uc3_orisize = face_base user_rgb_8uc3_orisize = face_base
# 构建一个色板 # 构建一个色板
r, g, b = target_hair_color r, g, b = target_hair_color
@@ -1016,7 +1018,7 @@ class HairStyle_Model_Infer(object):
def infer_haircolor_new(self, user_rgb_8uc3_orisize, haircolor_dir, target_hair_color, return_matting=False): def infer_haircolor_new(self, user_rgb_8uc3_orisize, haircolor_dir, target_hair_color, return_matting=False):
landmarks_origin_img_1k= self.get_landmark.forward(user_rgb_8uc3_orisize) landmarks_origin_img_1k= self.get_landmark.forward(user_rgb_8uc3_orisize)
if landmarks_origin_img_1k is None: if landmarks_origin_img_1k is None:
return None, 10001 return None, None, 10001
need_process = (target_hair_color[0] * 0.299 + target_hair_color[1] * 0.587 + target_hair_color[2] * 0.114) > 150 need_process = (target_hair_color[0] * 0.299 + target_hair_color[1] * 0.587 + target_hair_color[2] * 0.114) > 150
_, user_matting_8uc1_bald_orisize = self.process_data_infer.generator_matte.matte_inference(user_rgb_8uc3_orisize, _, user_matting_8uc1_bald_orisize = self.process_data_infer.generator_matte.matte_inference(user_rgb_8uc3_orisize,
landmarks_origin_img_1k) landmarks_origin_img_1k)
@@ -1036,10 +1038,13 @@ class HairStyle_Model_Infer(object):
need_process=True need_process=True
if need_process: if need_process:
haircolor_dir_tmp = os.path.join(config.get('default', "haircolorDir"), config.get('default', "baseColor_ID")) haircolor_dir_tmp = os.path.join(config.get('default', "haircolorDir"), config.get('default', "baseColor_ID"))
face_base, status1 = self.infer_haircolor_tj(user_rgb_8uc3_orisize, haircolor_dir_tmp) if os.path.exists(haircolor_dir_tmp):
# cv2.imwrite('/home/student/Desktop/tmp_color/need/face_base.png', face_base) face_base, status1 = self.infer_haircolor_tj(user_rgb_8uc3_orisize, haircolor_dir_tmp)
if status1 == 0: # cv2.imwrite('/home/student/Desktop/tmp_color/need/face_base.png', face_base)
return face_base, user_matting_8uc1_bald_orisize, 0 if status1 == 0:
return face_base, user_matting_8uc1_bald_orisize, 0
else:
need_process = False
else: else:
need_process = False need_process = False
if not need_process: if not need_process:
@@ -255,7 +255,8 @@ def change_hair_colorv3():
jsonify({'msg': '算法解析错误', 'result': '', 'umd': '', 'state': -1}), 400) jsonify({'msg': '算法解析错误', 'result': '', 'umd': '', 'state': -1}), 400)
except Exception as e: except Exception as e:
print(e) print(f"[hairColor ERROR] {e}")
traceback.print_exc()
return make_response( return make_response(
jsonify({'msg': '算法解析错误', 'result': '', 'umd': '', 'state': -1}), 400) jsonify({'msg': '算法解析错误', 'result': '', 'umd': '', 'state': -1}), 400)
@@ -605,6 +606,8 @@ def change_hairstyle_v4():
else: else:
p_tag = "" p_tag = ""
denoising_strength = float(input_info.get('denoising_strength', 0.6)) # 接口11 可调;默认 0.6 denoising_strength = float(input_info.get('denoising_strength', 0.6)) # 接口11 可调;默认 0.6
webui_steps_raw = input_info.get('webui_steps', None) # 可选:webui img2img 步数,None用服务端默认
webui_steps = int(webui_steps_raw) if webui_steps_raw is not None else None
print(f"功能8:处理发型区域,耗时:{time.time() - start_time:.3f}s") print(f"功能8:处理发型区域,耗时:{time.time() - start_time:.3f}s")
# cv2.imwrite(f"{task_id}_mask_dilate.jpg", mask_dilate) # cv2.imwrite(f"{task_id}_mask_dilate.jpg", mask_dilate)
# cv2.imwrite(f"{task_id}_final_img.jpg", final_img) # cv2.imwrite(f"{task_id}_final_img.jpg", final_img)
@@ -618,7 +621,7 @@ def change_hairstyle_v4():
# mask_dilate = cv2.imread("/root/project/hair_service_sd/gt_mask_dilate.jpg") # mask_dilate = cv2.imread("/root/project/hair_service_sd/gt_mask_dilate.jpg")
sd_result = webui_img2img(img=final_img, mask_img=mask_dilate, in_gender=in_gender, task_id=task_id, sd_result = webui_img2img(img=final_img, mask_img=mask_dilate, in_gender=in_gender, task_id=task_id,
hair_id=hair_id, lora_material_path=hair_material_dir, tag=p_tag, is_hr=is_hr, hair_id=hair_id, lora_material_path=hair_material_dir, tag=p_tag, is_hr=is_hr,
denoising_strength=denoising_strength, inference_port="57860") denoising_strength=denoising_strength, inference_port="57860", webui_steps=webui_steps)
print(f"功能:webui,耗时:{time.time() - start_time:.3f}s") print(f"功能:webui,耗时:{time.time() - start_time:.3f}s")
# 功能:后处理并上传结果 # 功能:后处理并上传结果
+7 -7
View File
@@ -4,7 +4,7 @@
export CRYPTOGRAPHY_OPENSSL_NO_LEGACY=1 export CRYPTOGRAPHY_OPENSSL_NO_LEGACY=1
# 创建Lora目录软连接 # 创建Lora目录软连接
TARGET_LORA="/home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/models/Lora" TARGET_LORA="/home/xsl/change_hair/project/onediff/stable-diffusion-webui/models/Lora"
if [ -e "$TARGET_LORA" ]; then if [ -e "$TARGET_LORA" ]; then
echo "删除已存在的Lora连接或目录: $TARGET_LORA" echo "删除已存在的Lora连接或目录: $TARGET_LORA"
rm -rf "$TARGET_LORA" rm -rf "$TARGET_LORA"
@@ -13,16 +13,16 @@ ln -s /gz-fs/Lora "$TARGET_LORA"
echo "创建软连接成功: /gz-fs/Lora -> $TARGET_LORA" echo "创建软连接成功: /gz-fs/Lora -> $TARGET_LORA"
# 1. 换发算法服务 # 1. 换发算法服务
cd /home/ubuntu/change_hair/project/hair_service_sd cd /home/xsl/change_hair/project/hair_service_sd
nohup /home/ubuntu/miniconda3/envs/condiff-train-hair/bin/python run_copy_cost_colorb64.py > hair_service.log 2>&1 & nohup /home/xsl/miniconda3/envs/condiff-train-hair/bin/python run_copy_cost_colorb64.py > hair_service.log 2>&1 &
# 2. webUI服务推理 + onediff # 2. webUI服务推理 + onediff
cd /home/ubuntu/change_hair/project/onediff/stable-diffusion-webui cd /home/xsl/change_hair/project/onediff/stable-diffusion-webui
nohup /home/ubuntu/miniconda3/envs/onediff/bin/python webui.py --api --listen --xformers --port 57860 > webui.log 2>&1 & nohup /home/xsl/miniconda3/envs/onediff/bin/python webui.py --api --listen --xformers --port 57860 > webui.log 2>&1 &
nohup ./webui.sh --api --listen --disable-safe-unpickle --port 9038 > webui_sh.log 2>&1 & nohup ./webui.sh --api --listen --disable-safe-unpickle --port 9038 > webui_sh.log 2>&1 &
# 3. photo_service # 3. photo_service
cd /home/ubuntu/change_hair/project/photo_service cd /home/xsl/change_hair/project/photo_service
nohup /home/ubuntu/miniconda3/envs/py310/bin/python lora_train_service_1.py > photo_service.log 2>&1 & nohup /home/xsl/miniconda3/envs/py310/bin/python lora_train_service_1.py > photo_service.log 2>&1 &
echo "所有服务已在后台启动" echo "所有服务已在后台启动"
@@ -43,6 +43,9 @@ def webui_img2img(img, mask, prompt='', denoising_strength=0.35):
} }
response = requests.post(url=url, json=request_dict) response = requests.post(url=url, json=request_dict)
ret_json = response.json() ret_json = response.json()
if 'images' not in ret_json:
print(f"[webui_img2img ERROR] {ret_json}")
raise Exception(f"webui error: {ret_json.get('error', 'unknown')}")
result = ret_json['images'][0] result = ret_json['images'][0]
img = cv2.imdecode(np.frombuffer(base64.b64decode(result.split(",", 1)[0]), np.uint8), cv2.IMREAD_COLOR) img = cv2.imdecode(np.frombuffer(base64.b64decode(result.split(",", 1)[0]), np.uint8), cv2.IMREAD_COLOR)
return img return img
@@ -307,13 +307,13 @@ def train_thread(sq, gpu_id):
# 3. GPU 固定 device=0(单卡) # 3. GPU 固定 device=0(单卡)
# 4. 去掉 tokenizer_cache_dir(改用 HF 本地缓存 + 离线模式) # 4. 去掉 tokenizer_cache_dir(改用 HF 本地缓存 + 离线模式)
# 5. 设置 HF_HUB_OFFLINE 避免联网检查 # 5. 设置 HF_HUB_OFFLINE 避免联网检查
kohya_python = '/home/ubuntu/miniconda3/envs/kohya/bin/python' kohya_python = '/home/ubuntu/miniconda3/envs/my_hair/bin/python'
kohya_workdir = os.path.join(kohya_ss_home_dir, 'kohya_ss') kohya_workdir = os.path.join(kohya_ss_home_dir, 'kohya_ss')
base_model = '/home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/models/Stable-diffusion/v1-5-pruned-emaonly.safetensors' base_model = '/home/ubuntu/change_hair/project/onediff/stable-diffusion-webui/models/Stable-diffusion/v1-5-pruned-emaonly.safetensors'
cmd_train = ( cmd_train = (
f'cd {kohya_workdir} && ' f'cd {kohya_workdir} && '
f'HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 CUDA_VISIBLE_DEVICES={device_id} ' f'HF_HUB_OFFLINE=1 TRANSFORMERS_OFFLINE=1 CUDA_VISIBLE_DEVICES={device_id} '
f'/home/ubuntu/miniconda3/envs/kohya/bin/accelerate launch --num_cpu_threads_per_process=2 "./train_network.py" --enable_bucket ' f'/home/ubuntu/miniconda3/envs/my_hair/bin/accelerate launch --num_cpu_threads_per_process=2 "./train_network.py" --enable_bucket '
f'--min_bucket_reso=256 --max_bucket_reso=2048 --pretrained_model_name_or_path="{base_model}" ' f'--min_bucket_reso=256 --max_bucket_reso=2048 --pretrained_model_name_or_path="{base_model}" '
f'--train_data_dir={images_dir} --resolution="2000,2000" ' f'--train_data_dir={images_dir} --resolution="2000,2000" '
f'--output_dir={model_dir} ' f'--output_dir={model_dir} '
+2 -2
View File
@@ -1,10 +1,10 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# 断点续传大文件到云服务器 /home/ubuntu/data # 断点续传大文件到云服务器 /home/xsl/data
# 用法: bash scripts/sync_data_to_server.sh [all|weights|sd|hairstyles] # 用法: bash scripts/sync_data_to_server.sh [all|weights|sd|hairstyles]
set -euo pipefail set -euo pipefail
REMOTE="ubuntu@117.50.213.111" REMOTE="ubuntu@117.50.213.111"
REMOTE_BASE="/home/ubuntu/data" REMOTE_BASE="/home/xsl/data"
LOCAL_BASE="/home/xsl/change_hair" LOCAL_BASE="/home/xsl/change_hair"
LOG_DIR="${LOCAL_BASE}/project/logs" LOG_DIR="${LOCAL_BASE}/project/logs"
mkdir -p "$LOG_DIR" mkdir -p "$LOG_DIR"
+4
View File
@@ -158,6 +158,10 @@ def step3_wait_and_callback(hair_id, template_img):
else: else:
print(" ✗ 训练超时"); return False print(" ✗ 训练超时"); return False
time.sleep(5) # 等训练进程写完 time.sleep(5) # 等训练进程写完
# 训练时停掉了 hair 服务,回调前需要重启
print(" 重启 change_hair-hair 服务...")
os.system("sudo systemctl restart change_hair-hair")
time.sleep(20)
# 触发回调生成ref材质 # 触发回调生成ref材质
print(" 触发回调生成ref材质...") print(" 触发回调生成ref材质...")
r = req.post(HAIR_CALLBACK, json={"task_id":f"train_{hair_id}","hair_id":hair_id, r = req.post(HAIR_CALLBACK, json={"task_id":f"train_{hair_id}","hair_id":hair_id,