feat: ComfyUI 默认提示词统一改为「填充遮罩区域的头发,人物增加一点磨皮」

- app.py 各接口 Form 默认值与说明、hairline/service.py _REDRAW_PROMPT、
  hairline/redraw.py _DEFAULT_PROMPT、face_analysis/hairline_grow.py 兜底值
- add_hair.json / 0716add-hair-api.json / 0716add-hair.json / hair_repaint.json
  工作流节点60内置文本
- static 测试页、local_test、benchmark 脚本、接口文档同步
注意:新提示词首次请求会因 CLIP 缓存失效变慢一次,之后恢复
This commit is contained in:
xsl
2026-09-16 00:27:33 +08:00
parent 59e01d3230
commit 752630e4f5
26 changed files with 44 additions and 44 deletions
+1 -1
View File
@@ -29,7 +29,7 @@
"60": { "60": {
"class_type": "JjkText", "class_type": "JjkText",
"inputs": { "inputs": {
"text": "填充遮罩区域的头发" "text": "填充遮罩区域的头发,人物增加一点磨皮"
} }
}, },
"22": { "22": {
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -410,7 +410,7 @@
}, },
"60": { "60": {
"inputs": { "inputs": {
"text": "填充遮罩区域的头发" "text": "填充遮罩区域的头发,人物增加一点磨皮"
}, },
"class_type": "JjkText", "class_type": "JjkText",
"_meta": { "_meta": {
+6 -6
View File
@@ -1186,7 +1186,7 @@ async def hair_grow(
hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填),如 1,2,3。female:1-7 male:1-7"), hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填),如 1,2,3。female:1-7 male:1-7"),
beauty_enabled: bool = Form(default=False, description="是否开启美颜(本期不生效)"), beauty_enabled: bool = Form(default=False, description="是否开启美颜(本期不生效)"),
use_mask: bool = Form(default=True, description="是否启用 inpaint 遮罩(测试对比用)。false 时用干净原图生成(空遮罩,不烧模板线)"), use_mask: bool = Form(default=True, description="是否启用 inpaint 遮罩(测试对比用)。false 时用干净原图生成(空遮罩,不烧模板线)"),
prompt: str = Form(default="填充遮罩区域的头发", description="ComfyUI 提示词,会替换工作流节点60的文本"), prompt: str = Form(default="填充遮罩区域的头发,人物增加一点磨皮", description="ComfyUI 提示词,会替换工作流节点60的文本"),
flux_model: Optional[str] = Form(default=None, description="Flux 模型文件名(切换模型用)。None=工作流默认;如 flux-2-klein-9b-Q5_K_M.gguf / flux-2-klein-9b-Q4_K_M.gguf / flux2.0/flux-2-klein-9b-fp8.safetensors"), flux_model: Optional[str] = Form(default=None, description="Flux 模型文件名(切换模型用)。None=工作流默认;如 flux-2-klein-9b-Q5_K_M.gguf / flux-2-klein-9b-Q4_K_M.gguf / flux2.0/flux-2-klein-9b-fp8.safetensors"),
redraw_max_side: Optional[int] = Form(default=None, description="重绘压图长边像素。None=默认896;0=不缩图(原图直送);其他如 768/640/1024"), redraw_max_side: Optional[int] = Form(default=None, description="重绘压图长边像素。None=默认896;0=不缩图(原图直送);其他如 768/640/1024"),
): ):
@@ -1258,7 +1258,7 @@ async def debug_grow_timing(
hair_style: str = Form(default="2", description="发型序号(花瓣=2),逗号分隔多选"), hair_style: str = Form(default="2", description="发型序号(花瓣=2),逗号分隔多选"),
webui_steps: Optional[int] = Form(default=None, description="swapHair webui img2img 采样步数,None=服务端默认(15),可填10/15/20/25对比"), webui_steps: Optional[int] = Form(default=None, description="swapHair webui img2img 采样步数,None=服务端默认(15),可填10/15/20/25对比"),
redraw_max_side: Optional[int] = Form(default=None, description="ComfyUI重绘分辨率(长边像素)。None=默认896;0=原图不缩;其他如640/768/1024"), redraw_max_side: Optional[int] = Form(default=None, description="ComfyUI重绘分辨率(长边像素)。None=默认896;0=原图不缩;其他如640/768/1024"),
redraw_prompt: Optional[str] = Form(default=None, description="ComfyUI重绘提示词,None=默认'填充遮罩区域的头发'"), redraw_prompt: Optional[str] = Form(default=None, description="ComfyUI重绘提示词,None=默认'填充遮罩区域的头发,人物增加一点磨皮'"),
): ):
"""单图跑接口2女性生发,返回每个步骤的耗时 + 结果图,用于定位性能瓶颈。 """单图跑接口2女性生发,返回每个步骤的耗时 + 结果图,用于定位性能瓶颈。
@@ -1444,7 +1444,7 @@ async def hair_grow_b(
marked_image_url: Optional[str] = Form(default=None, description="划线图片 URL"), marked_image_url: Optional[str] = Form(default=None, description="划线图片 URL"),
marked_image_base64: Optional[str] = Form(default=None, description="划线图片 base64"), marked_image_base64: Optional[str] = Form(default=None, description="划线图片 base64"),
use_mask: bool = Form(default=True, description="是否画发际线(测试对比用)。false 时跳过划线检测、直接送划线图"), use_mask: bool = Form(default=True, description="是否画发际线(测试对比用)。false 时跳过划线检测、直接送划线图"),
prompt: str = Form(default="填充遮罩区域的头发", description="ComfyUI 提示词,会替换工作流节点60的文本"), prompt: str = Form(default="填充遮罩区域的头发,人物增加一点磨皮", description="ComfyUI 提示词,会替换工作流节点60的文本"),
): ):
# 划线图三选一取图(只需这一张) # 划线图三选一取图(只需这一张)
marked_raw, e = await resolve_image_bytes(marked_image_file, marked_image_url, marked_image_base64) marked_raw, e = await resolve_image_bytes(marked_image_file, marked_image_url, marked_image_base64)
@@ -1655,7 +1655,7 @@ async def hairline_generate(
gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"), gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"),
hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填,如 1,2,3)。female:1-7 male:1-7"), hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填,如 1,2,3)。female:1-7 male:1-7"),
use_mask: bool = Form(default=True, description="生发是否启用 inpaint 遮罩(同接口2,测试对比用)"), use_mask: bool = Form(default=True, description="生发是否启用 inpaint 遮罩(同接口2,测试对比用)"),
prompt: str = Form(default="填充遮罩区域的头发", description="ComfyUI 提示词(同接口2),会替换工作流节点60的文本"), prompt: str = Form(default="填充遮罩区域的头发,人物增加一点磨皮", description="ComfyUI 提示词(同接口2),会替换工作流节点60的文本"),
generate_grow_image: bool = Form(default=True, description="是否生成生发效果图(ComfyUI 生发,最耗时)。默认 true 出图;false 时跳过生发,各发型 grown_image 恒为 null,仅返回三档发际线叠图与中心点"), generate_grow_image: bool = Form(default=True, description="是否生成生发效果图(ComfyUI 生发,最耗时)。默认 true 出图;false 时跳过生发,各发型 grown_image 恒为 null,仅返回三档发际线叠图与中心点"),
): ):
if gender not in ("male", "female"): if gender not in ("male", "female"):
@@ -2113,7 +2113,7 @@ async def hairline_grow_v2(
inpainting_fill: int = Form(default=1, description="change_hair服务端重绘填充:0=保留原图 | 1=噪声 | 2=纯色 | 3=潜变量。默认 1"), inpainting_fill: int = Form(default=1, description="change_hair服务端重绘填充:0=保留原图 | 1=噪声 | 2=纯色 | 3=潜变量。默认 1"),
mask_blur: int = Form(default=11, description="change_hair服务端遮罩边缘模糊像素,默认 11"), mask_blur: int = Form(default=11, description="change_hair服务端遮罩边缘模糊像素,默认 11"),
mask_dilate_scale: float = Form(default=1.0, description="change_hair服务端遮罩膨胀缩放,默认 1.0"), mask_dilate_scale: float = Form(default=1.0, description="change_hair服务端遮罩膨胀缩放,默认 1.0"),
comfyui_prompt: Optional[str] = Form(default=None, description="Flux-2 重绘提示词,None 用默认「填充遮罩区域的头发」"), comfyui_prompt: Optional[str] = Form(default=None, description="Flux-2 重绘提示词,None 用默认「填充遮罩区域的头发,人物增加一点磨皮」"),
beauty_alpha: float = Form(default=0.6, description="redraw_band 版 band 外的全脸美颜融入强度(0=band外无美颜纯用final,1≈整帧版),默认 0.6"), beauty_alpha: float = Form(default=0.6, description="redraw_band 版 band 外的全脸美颜融入强度(0=band外无美颜纯用final,1≈整帧版),默认 0.6"),
band_lo_mult: float = Form(default=0.5, description="重绘带外推倍率下限(相对 hairline_push_cm,内轮廓=0×、原外推线=1.0×),默认 0.5"), band_lo_mult: float = Form(default=0.5, description="重绘带外推倍率下限(相对 hairline_push_cm,内轮廓=0×、原外推线=1.0×),默认 0.5"),
band_hi_mult: float = Form(default=1.5, description="重绘带外推倍率上限(相对 hairline_push_cm),默认 1.5"), band_hi_mult: float = Form(default=1.5, description="重绘带外推倍率上限(相对 hairline_push_cm),默认 1.5"),
@@ -2279,7 +2279,7 @@ async def hairline_grow_v2_final_v2(
async def api_redraw( async def api_redraw(
image_file: UploadFile = File(..., description="人物图片(JPG/PNG)"), image_file: UploadFile = File(..., description="人物图片(JPG/PNG)"),
mask_file: UploadFile = File(..., description="遮罩图片(PNG,支持红/白/alpha 格式)"), mask_file: UploadFile = File(..., description="遮罩图片(PNG,支持红/白/alpha 格式)"),
prompt: str = Form(default="填充遮罩区域的头发", prompt: str = Form(default="填充遮罩区域的头发,人物增加一点磨皮",
description="ComfyUI 提示词"), description="ComfyUI 提示词"),
): ):
image_bytes = await image_file.read() image_bytes = await image_file.read()
+2 -2
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""快速测试:3张图×花瓣发型×896分辨率,新提示词"填充遮罩区域的头发"。 """快速测试:3张图×花瓣发型×896分辨率,新提示词"填充遮罩区域的头发,人物增加一点磨皮"。
预热1次+正式1次。 预热1次+正式1次。
""" """
import base64, json, os, time import base64, json, os, time
@@ -9,7 +9,7 @@ import requests
API = "http://127.0.0.1:8187/api/v1/debug/grow-timing" API = "http://127.0.0.1:8187/api/v1/debug/grow-timing"
TOKEN = "dev-shared-secret-2026" TOKEN = "dev-shared-secret-2026"
PROMPT = "填充遮罩区域的头发" PROMPT = "填充遮罩区域的头发,人物增加一点磨皮"
OUT = Path("/home/ubuntu/hair/benchmark_out/bench6") OUT = Path("/home/ubuntu/hair/benchmark_out/bench6")
OUT.mkdir(parents=True, exist_ok=True) OUT.mkdir(parents=True, exist_ok=True)
+1 -1
View File
@@ -55,7 +55,7 @@ def gpu_used():
def call(img_path, hair_num, model_file, res_val): def call(img_path, hair_num, model_file, res_val):
fd = {"gender": "female", "hair_style": str(hair_num), "use_mask": "true", fd = {"gender": "female", "hair_style": str(hair_num), "use_mask": "true",
"prompt": "填充遮罩区域的头发"} "prompt": "填充遮罩区域的头发,人物增加一点磨皮"}
if model_file: if model_file:
fd["flux_model"] = model_file fd["flux_model"] = model_file
if res_val != "": if res_val != "":
+1 -1
View File
@@ -54,7 +54,7 @@ def call(img_path, model_file, res_val):
"gender": "female", "gender": "female",
"hair_style": "2", # 花瓣形 "hair_style": "2", # 花瓣形
"use_mask": "true", "use_mask": "true",
"prompt": "填充遮罩区域的头发", "prompt": "填充遮罩区域的头发,人物增加一点磨皮",
} }
if model_file: if model_file:
fd["flux_model"] = model_file fd["flux_model"] = model_file
+2 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""分辨率对比测试(新提示词版):4图×5发型=20行,每行4种分辨率,steps=15。 """分辨率对比测试(新提示词版):4图×5发型=20行,每行4种分辨率,steps=15。
提示词固定为 "填充遮罩区域的头发"。 提示词固定为 "填充遮罩区域的头发,人物增加一点磨皮"。
热数据:预热1次+正式1次。 热数据:预热1次+正式1次。
""" """
import base64 import base64
@@ -14,7 +14,7 @@ import requests
API = "http://127.0.0.1:8187/api/v1/debug/grow-timing" API = "http://127.0.0.1:8187/api/v1/debug/grow-timing"
TOKEN = "dev-shared-secret-2026" TOKEN = "dev-shared-secret-2026"
PROMPT = "填充遮罩区域的头发" PROMPT = "填充遮罩区域的头发,人物增加一点磨皮"
OUT = Path("/home/ubuntu/hair/benchmark_out/bench7") OUT = Path("/home/ubuntu/hair/benchmark_out/bench7")
OUT.mkdir(parents=True, exist_ok=True) OUT.mkdir(parents=True, exist_ok=True)
+5 -5
View File
@@ -16,7 +16,7 @@
Handler 签名新增: Handler 签名新增:
```python ```python
prompt: str = Form(default="补充遮罩区域的头发", description="ComfyUI 提示词,会替换工作流节点60的文本"), prompt: str = Form(default=填充遮罩区域的头发,人物增加一点磨皮, description="ComfyUI 提示词,会替换工作流节点60的文本"),
``` ```
透传链路: 透传链路:
@@ -31,7 +31,7 @@ handler → generate_grow_results(image, gender, use_mask, prompt)
Handler 签名新增: Handler 签名新增:
```python ```python
prompt: str = Form(default="补充遮罩区域的头发", description="ComfyUI 提示词,会替换工作流节点60的文本"), prompt: str = Form(default=填充遮罩区域的头发,人物增加一点磨皮, description="ComfyUI 提示词,会替换工作流节点60的文本"),
``` ```
透传链路: 透传链路:
@@ -80,17 +80,17 @@ if prompt is not None:
| 2 | `image_base64` | string | — | 同上 | | 2 | `image_base64` | string | — | 同上 |
| 2 | `gender` | string | `female` | `male` / `female` | | 2 | `gender` | string | `female` | `male` / `female` |
| 2 | `use_mask` | bool | `true` | inpaint 遮罩开关 | | 2 | `use_mask` | bool | `true` | inpaint 遮罩开关 |
| 2 | `prompt` | string | `补充遮罩区域的头发` | ComfyUI 提示词 | | 2 | `prompt` | string | 填充遮罩区域的头发,人物增加一点磨皮 | ComfyUI 提示词 |
| 3 | `marked_image_file` | file | — | 划线图(三选一) | | 3 | `marked_image_file` | file | — | 划线图(三选一) |
| 3 | `marked_image_url` | string | — | 同上 | | 3 | `marked_image_url` | string | — | 同上 |
| 3 | `marked_image_base64` | string | — | 同上 | | 3 | `marked_image_base64` | string | — | 同上 |
| 3 | `use_mask` | bool | `true` | 画发际线开关 | | 3 | `use_mask` | bool | `true` | 画发际线开关 |
| 3 | `prompt` | string | `补充遮罩区域的头发` | ComfyUI 提示词 | | 3 | `prompt` | string | 填充遮罩区域的头发,人物增加一点磨皮 | ComfyUI 提示词 |
## 3. 当前工作流提示词节点 ## 3. 当前工作流提示词节点
`add_hair.json` 中节点 **60**(`JjkText`)为提示词节点,当前硬编码文本: `add_hair.json` 中节点 **60**(`JjkText`)为提示词节点,当前硬编码文本:
> 补充遮罩区补充遮罩区域内的头发,头发填满遮罩区域。发际线往下挡住额头 > 填充遮罩区域的头发,人物增加一点磨皮
`comfyui.run()` 收到 `prompt` 参数后将替换此文本。 `comfyui.run()` 收到 `prompt` 参数后将替换此文本。
+2 -2
View File
@@ -268,7 +268,7 @@
| hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),最多不超过该性别的预设数。female:1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave, 6=bigflower, 7=clasicalflower;male:1=ellipse, 2=inverse_arc, 3=m, 4=straight, 5=heart, 6=Softpetal, 7=wave。越界/非法返回 `1007` | | hair_style | string | **是** | 发型序号,**逗号分隔多选**(如 `1,2,3`),最多不超过该性别的预设数。female:1=ellipse, 2=flower, 3=heart, 4=straight, 5=wave, 6=bigflower, 7=clasicalflower;male:1=ellipse, 2=inverse_arc, 3=m, 4=straight, 5=heart, 6=Softpetal, 7=wave。越界/非法返回 `1007` |
| beauty_enabled | bool | 否 | 生发图是否带美颜效果,默认 false(当前阶段不生效) | | beauty_enabled | bool | 否 | 生发图是否带美颜效果,默认 false(当前阶段不生效) |
| use_mask | bool | 否 | 是否启用 inpaint 遮罩,默认 `true`。`false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比。仅对原生发(male / female 6/7)管线生效 | | use_mask | bool | 否 | 是否启用 inpaint 遮罩,默认 `true`。`false` 时用干净原图生成(空遮罩、不烧模板黑线),供测试对比。仅对原生发(male / female 6/7)管线生效 |
| prompt | string | 否 | ComfyUI 提示词,默认「填充遮罩区域的头发」,会替换工作流节点 60 的文本。所有分支生效(male / female 6/7 原生生发,female 1..5 换发型后的发际线带重绘;不进 change_hair)。注:非默认提示词会使 ComfyUI 的 CLIP 条件缓存失效,换词后首次请求明显变慢(实测 45s vs 命中缓存 8s),相同提示词的后续请求恢复正常 | | prompt | string | 否 | ComfyUI 提示词,默认「填充遮罩区域的头发,人物增加一点磨皮」,会替换工作流节点 60 的文本。所有分支生效(male / female 6/7 原生生发,female 1..5 换发型后的发际线带重绘;不进 change_hair)。注:非默认提示词会使 ComfyUI 的 CLIP 条件缓存失效,换词后首次请求明显变慢(实测 45s vs 命中缓存 8s),相同提示词的后续请求恢复正常 |
| flux_model | string | 否 | Flux 模型文件名(如 `flux-2-klein-9b-Q5_K_M.gguf`)。缺省用工作流默认(9B-Q4_K_M);`.gguf` 自动切 UnetLoaderGGUF 并同步配套 qwen CLIP | | flux_model | string | 否 | Flux 模型文件名(如 `flux-2-klein-9b-Q5_K_M.gguf`)。缺省用工作流默认(9B-Q4_K_M);`.gguf` 自动切 UnetLoaderGGUF 并同步配套 qwen CLIP |
| redraw_max_side | int | 否 | female 1..5 重绘管线送模型前的长边上限像素。缺省=服务端默认(`REDRAW_MAX_SIDE`,当前 1024);`0`=不缩图(原图直送,更慢) | | redraw_max_side | int | 否 | female 1..5 重绘管线送模型前的长边上限像素。缺省=服务端默认(`REDRAW_MAX_SIDE`,当前 1024);`0`=不缩图(原图直送,更慢) |
@@ -634,7 +634,7 @@
| 参数 | 类型 | 必填 | 说明 | | 参数 | 类型 | 必填 | 说明 |
|------|------|------|------| |------|------|------|------|
| comfyui_prompt | string | 否 | Flux-2 重绘提示词,缺省「填充遮罩区域的头发」 | | comfyui_prompt | string | 否 | Flux-2 重绘提示词,缺省「填充遮罩区域的头发,人物增加一点磨皮」 |
| beauty_alpha | float | 否 | redraw_band 版 band 外的全脸美颜融入强度(0=band 外纯用 final,1≈整帧版),默认 0.6 | | beauty_alpha | float | 否 | redraw_band 版 band 外的全脸美颜融入强度(0=band 外纯用 final,1≈整帧版),默认 0.6 |
| band_lo_mult | float | 否 | 重绘带外推倍率下限(相对 hairline_push_cm),默认 0.5 | | band_lo_mult | float | 否 | 重绘带外推倍率下限(相对 hairline_push_cm),默认 0.5 |
| band_hi_mult | float | 否 | 重绘带外推倍率上限(相对 hairline_push_cm),默认 1.5 | | band_hi_mult | float | 否 | 重绘带外推倍率上限(相对 hairline_push_cm),默认 1.5 |
+1 -1
View File
@@ -1110,7 +1110,7 @@ def generate_hairline_redraw(image_bgr, hairline_id, is_hr=False, seg_model="seg
"hairline_id": hairline_id, "hairline_id": hairline_id,
"blend_method": blend_method, "blend_method": blend_method,
"hairline_push_cm": round(float(hairline_push_cm), 2), "hairline_push_cm": round(float(hairline_push_cm), 2),
"comfyui_prompt": comfyui_prompt or "填充遮罩区域的头发", "comfyui_prompt": comfyui_prompt or "填充遮罩区域的头发,人物增加一点磨皮",
"beauty_alpha": beauty_alpha, "beauty_alpha": beauty_alpha,
"px_per_cm": round(float(px_per_cm), 4), "px_per_cm": round(float(px_per_cm), 4),
"mask_pixels": mask_viz["mask_pixels"], "mask_pixels": mask_viz["mask_pixels"],
+1 -1
View File
@@ -410,7 +410,7 @@
}, },
"60": { "60": {
"inputs": { "inputs": {
"text": "填充遮罩区域的头发" "text": "填充遮罩区域的头发,人物增加一点磨皮"
}, },
"class_type": "JjkText", "class_type": "JjkText",
"_meta": { "_meta": {
+2 -2
View File
@@ -16,7 +16,7 @@ from . import comfyui
logger = logging.getLogger("hair.worker") logger = logging.getLogger("hair.worker")
_DEFAULT_PROMPT = "填充遮罩区域的头发" _DEFAULT_PROMPT = "填充遮罩区域的头发,人物增加一点磨皮"
_REPO = os.path.dirname(os.path.dirname(__file__)) _REPO = os.path.dirname(os.path.dirname(__file__))
_REPAINT_WORKFLOW = os.path.join(_REPO, "0716add-hair-api.json") _REPAINT_WORKFLOW = os.path.join(_REPO, "0716add-hair-api.json")
@@ -62,7 +62,7 @@ def run_redraw(image_bytes: bytes, mask_bytes: bytes,
Args: Args:
image_bytes: 人物图片字节(JPG/PNG) image_bytes: 人物图片字节(JPG/PNG)
mask_bytes: 遮罩图片字节(支持红/白/alpha 遮罩格式) mask_bytes: 遮罩图片字节(支持红/白/alpha 遮罩格式)
prompt: 提示词,None 用默认 "填充遮罩区域的头发" prompt: 提示词,None 用默认 "填充遮罩区域的头发,人物增加一点磨皮"
timeout: ComfyUI 超时秒数 timeout: ComfyUI 超时秒数
front: True 时任务插到 ComfyUI 队列最前(接口2 时延敏感路径用) front: True 时任务插到 ComfyUI 队列最前(接口2 时延敏感路径用)
unet_name: 非 None 时切换 Flux 模型(如 flux-2-klein-9b-Q5_K_M.gguf),None 用工作流默认 unet_name: 非 None 时切换 Flux 模型(如 flux-2-klein-9b-Q5_K_M.gguf),None 用工作流默认
+2 -2
View File
@@ -58,7 +58,7 @@ _BLACK_TEXTURE_DIR = os.path.join(_REPO, "hairline_texture_black")
# 关键:ComfyUI 单卡显存装不下 Flux(7.7G)+qwen CLIP(3.9G) 同驻,靠缓存 CLIP 文本条件避免重载。 # 关键:ComfyUI 单卡显存装不下 Flux(7.7G)+qwen CLIP(3.9G) 同驻,靠缓存 CLIP 文本条件避免重载。
# prompt 不同会使缓存失效 → 重载 CLIP 并挤出 Flux(每次 +4s)。三接口用同一字符串即可全程命中。 # prompt 不同会使缓存失效 → 重载 CLIP 并挤出 Flux(每次 +4s)。三接口用同一字符串即可全程命中。
# 与 app.py 接口2/接口3 的默认 prompt 保持一致;可用 REDRAW_PROMPT 覆盖。 # 与 app.py 接口2/接口3 的默认 prompt 保持一致;可用 REDRAW_PROMPT 覆盖。
_REDRAW_PROMPT = os.getenv("REDRAW_PROMPT", "填充遮罩区域的头发") _REDRAW_PROMPT = os.getenv("REDRAW_PROMPT", "填充遮罩区域的头发,人物增加一点磨皮")
# 接口2 女重绘整条管线(swapHair + ComfyUI)送模型前限边。真实照片常达 1257x1495: # 接口2 女重绘整条管线(swapHair + ComfyUI)送模型前限边。真实照片常达 1257x1495:
# 全分辨率 ComfyUI 重绘要 13~21s 且激活显存把模型挤出。女性路径含 swapHair(SD WebUI ~5.3s # 全分辨率 ComfyUI 重绘要 13~21s 且激活显存把模型挤出。女性路径含 swapHair(SD WebUI ~5.3s
@@ -316,7 +316,7 @@ def generate_grow_results_swap(image_bgr: np.ndarray, hair_styles: list[int] | N
overlay 仍是发际线曲线透明层(与 generate_grow_results 完全一致)。 overlay 仍是发际线曲线透明层(与 generate_grow_results 完全一致)。
prompt 两条分支都生效:换发型分支传入 _call_local_redraw,原生生发分支传给 prompt 两条分支都生效:换发型分支传入 _call_local_redraw,原生生发分支传给
_grow_native_one。None/默认「填充遮罩区域的头发」时与全局 _REDRAW_PROMPT 一致, _grow_native_one。None/默认「填充遮罩区域的头发,人物增加一点磨皮」时与全局 _REDRAW_PROMPT 一致,
不破坏 CLIP 缓存命中的快路径;传不同提示词会使缓存失效并重载 CLIP/Flux 不破坏 CLIP 缓存命中的快路径;传不同提示词会使缓存失效并重载 CLIP/Flux
(实测换词后首次请求重绘 45s vs 缓存命中 8s),后续相同提示词恢复快路径。 (实测换词后首次请求重绘 45s vs 缓存命中 8s),后续相同提示词恢复快路径。
Returns: list[dict] {"hairline_type","order","overlay","grown_png"(jpg bytes 或 None)}; Returns: list[dict] {"hairline_type","order","overlay","grown_png"(jpg bytes 或 None)};
+3 -3
View File
@@ -48,7 +48,7 @@ cd /home/ubuntu/hair/local_test
|--------|------|------|------| |--------|------|------|------|
| image | File | 是 | 人物图片(支持 jpg, png 等常见格式) | | image | File | 是 | 人物图片(支持 jpg, png 等常见格式) |
| mask | File | 是 | 遮罩图片(支持 jpg, png,遮罩区域可用红色/白色/alpha 通道标识) | | mask | File | 是 | 遮罩图片(支持 jpg, png,遮罩区域可用红色/白色/alpha 通道标识) |
| prompt | String | 否 | 提示词,默认值:"填充遮罩区域的头发" | | prompt | String | 否 | 提示词,默认值:"填充遮罩区域的头发,人物增加一点磨皮" |
#### 遮罩图片格式说明 #### 遮罩图片格式说明
@@ -70,7 +70,7 @@ cd /home/ubuntu/hair/local_test
curl -X POST http://127.0.0.1:8899/api/generate \ curl -X POST http://127.0.0.1:8899/api/generate \
-F "image=@/path/to/person.jpg" \ -F "image=@/path/to/person.jpg" \
-F "mask=@/path/to/mask.png" \ -F "mask=@/path/to/mask.png" \
-F "prompt=填充遮罩区域的头发" \ -F "prompt=填充遮罩区域的头发,人物增加一点磨皮" \
--output result.png --output result.png
``` ```
@@ -85,7 +85,7 @@ files = {
"mask": open("mask.png", "rb"), "mask": open("mask.png", "rb"),
} }
data = { data = {
"prompt": "填充遮罩区域的头发" "prompt": "填充遮罩区域的头发,人物增加一点磨皮"
} }
resp = requests.post(url, files=files, data=data, timeout=600) resp = requests.post(url, files=files, data=data, timeout=600)
+1 -1
View File
@@ -206,7 +206,7 @@ def generate():
return jsonify({"error": msg}), 400 return jsonify({"error": msg}), 400
image_file = request.files["image"] image_file = request.files["image"]
mask_file = request.files["mask"] mask_file = request.files["mask"]
prompt_text = request.form.get("prompt", "填充遮罩区域的头发") prompt_text = request.form.get("prompt", "填充遮罩区域的头发,人物增加一点磨皮")
log.info( log.info(
"收到请求: image=%s mask=%s prompt=%r", "收到请求: image=%s mask=%s prompt=%r",
image_file.filename, mask_file.filename, prompt_text, image_file.filename, mask_file.filename, prompt_text,
+1 -1
View File
@@ -27,7 +27,7 @@ def prep_and_upload():
def run_once(fname, model, dtype, steps): def run_once(fname, model, dtype, steps):
wf = A.build_workflow(fname, "填充遮罩区域的头发") wf = A.build_workflow(fname, "填充遮罩区域的头发,人物增加一点磨皮")
wf["16"]["inputs"]["unet_name"] = model wf["16"]["inputs"]["unet_name"] = model
wf["16"]["inputs"]["weight_dtype"] = dtype wf["16"]["inputs"]["weight_dtype"] = dtype
wf["1"]["inputs"]["steps"] = steps wf["1"]["inputs"]["steps"] = steps
+1 -1
View File
@@ -33,7 +33,7 @@ def upload(scale=1.0):
def run(fname, steps): def run(fname, steps):
wf = A.build_workflow(fname, "填充遮罩区域的头发") wf = A.build_workflow(fname, "填充遮罩区域的头发,人物增加一点磨皮")
wf["16"]["inputs"]["unet_name"] = MODEL wf["16"]["inputs"]["unet_name"] = MODEL
wf["16"]["inputs"]["weight_dtype"] = DTYPE wf["16"]["inputs"]["weight_dtype"] = DTYPE
wf["1"]["inputs"]["steps"] = steps wf["1"]["inputs"]["steps"] = steps
+1 -1
View File
@@ -30,7 +30,7 @@ SEED = 123456789
imgs = [] imgs = []
labels = [] labels = []
for steps in [2, 3, 4, 6]: for steps in [2, 3, 4, 6]:
wf = A.build_workflow(fname, "填充遮罩区域的头发", seed=SEED) wf = A.build_workflow(fname, "填充遮罩区域的头发,人物增加一点磨皮", seed=SEED)
wf["16"]["inputs"]["unet_name"] = MODEL wf["16"]["inputs"]["unet_name"] = MODEL
wf["16"]["inputs"]["weight_dtype"] = DTYPE wf["16"]["inputs"]["weight_dtype"] = DTYPE
wf["1"]["inputs"]["steps"] = steps wf["1"]["inputs"]["steps"] = steps
+1 -1
View File
@@ -55,7 +55,7 @@ button { padding: 10px 24px; border: none; border-radius: 6px; cursor: pointer;
</div> </div>
<div class="controls" style="margin-top:16px"> <div class="controls" style="margin-top:16px">
<label>提示词:</label> <label>提示词:</label>
<input type="text" id="promptInput" value="填充遮罩区域的头发"> <input type="text" id="promptInput" value="填充遮罩区域的头发,人物增加一点磨皮">
</div> </div>
<div style="text-align:center; margin-top:16px"> <div style="text-align:center; margin-top:16px">
<button class="btn-generate" id="generateBtn" disabled>🚀 生成</button> <button class="btn-generate" id="generateBtn" disabled>🚀 生成</button>
+1 -1
View File
@@ -25,7 +25,7 @@ resp = requests.post(
"image": ("original.jpg", img_data, "image/jpeg"), "image": ("original.jpg", img_data, "image/jpeg"),
"mask": ("mask.png", mask_data, "image/png"), "mask": ("mask.png", mask_data, "image/png"),
}, },
data={"prompt": "填充遮罩区域的头发"}, data={"prompt": "填充遮罩区域的头发,人物增加一点磨皮"},
timeout=600, timeout=600,
) )
+2 -2
View File
@@ -69,7 +69,7 @@
</div> </div>
<div class="upload-row" style="margin-top:10px"> <div class="upload-row" style="margin-top:10px">
<label style="font-size:13px;font-weight:600;white-space:nowrap">重绘提示词</label> <label style="font-size:13px;font-weight:600;white-space:nowrap">重绘提示词</label>
<input type="text" id="localTestPrompt" value="填充遮罩区域的头发" style="flex:1;min-width:200px;padding:8px;border:1px solid #ddd;border-radius:8px"> <input type="text" id="localTestPrompt" value="填充遮罩区域的头发,人物增加一点磨皮" style="flex:1;min-width:200px;padding:8px;border:1px solid #ddd;border-radius:8px">
</div> </div>
<div class="upload-row" style="margin-top:10px"> <div class="upload-row" style="margin-top:10px">
<label style="font-size:13px;font-weight:600;white-space:nowrap">X-Internal-Token</label> <label style="font-size:13px;font-weight:600;white-space:nowrap">X-Internal-Token</label>
@@ -354,7 +354,7 @@ function dataUriToBlob(dataUri) {
async function runLocalRedraw() { async function runLocalRedraw() {
if (!_finalDataUri || !_maskDataUri) { setStatus('缺少 final 或遮罩,请先生成', 'error'); return; } if (!_finalDataUri || !_maskDataUri) { setStatus('缺少 final 或遮罩,请先生成', 'error'); return; }
const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发'; const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发,人物增加一点磨皮';
const btn = $('redrawBtn'); const btn = $('redrawBtn');
btn.disabled = true; btn.textContent = '⏳ 重绘中...'; btn.disabled = true; btn.textContent = '⏳ 重绘中...';
flog('===== 调后端重绘 =====', 'info'); flog('===== 调后端重绘 =====', 'info');
+2 -2
View File
@@ -56,7 +56,7 @@
</div> </div>
<div class="upload-row" style="margin-top:10px"> <div class="upload-row" style="margin-top:10px">
<label style="font-size:13px;font-weight:600;white-space:nowrap">重绘提示词</label> <label style="font-size:13px;font-weight:600;white-space:nowrap">重绘提示词</label>
<input type="text" id="localTestPrompt" value="填充遮罩区域的头发" style="flex:1;min-width:200px;padding:8px;border:1px solid #ddd;border-radius:8px"> <input type="text" id="localTestPrompt" value="填充遮罩区域的头发,人物增加一点磨皮" style="flex:1;min-width:200px;padding:8px;border:1px solid #ddd;border-radius:8px">
</div> </div>
<div class="params"> <div class="params">
<div class="pf"> <div class="pf">
@@ -169,7 +169,7 @@ async function submitTest() {
async function runLocalRedraw() { async function runLocalRedraw() {
if (!_finalDataUri || !_maskDataUri) { setStatus('缺少 final 或遮罩,请先生成', 'error'); return; } if (!_finalDataUri || !_maskDataUri) { setStatus('缺少 final 或遮罩,请先生成', 'error'); return; }
const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发'; const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发,人物增加一点磨皮';
const btn = $('redrawBtn'); const btn = $('redrawBtn');
btn.disabled = true; btn.textContent = '⏳ 重绘中...'; btn.disabled = true; btn.textContent = '⏳ 重绘中...';
+1 -1
View File
@@ -107,7 +107,7 @@
<div class="hint">JPG/PNG &nbsp;|&nbsp; 生发图生成较慢(数十秒~数分钟),请耐心等待</div> <div class="hint">JPG/PNG &nbsp;|&nbsp; 生发图生成较慢(数十秒~数分钟),请耐心等待</div>
<div style="margin-top:10px;display:flex;align-items:center;gap:8px"> <div style="margin-top:10px;display:flex;align-items:center;gap:8px">
<label style="font-size:13px;font-weight:600;color:#374151;white-space:nowrap">💬 提示词</label> <label style="font-size:13px;font-weight:600;color:#374151;white-space:nowrap">💬 提示词</label>
<input type="text" id="promptInput" value="填充遮罩区域的头发" style="flex:1;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;max-width:500px"> <input type="text" id="promptInput" value="填充遮罩区域的头发,人物增加一点磨皮" style="flex:1;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;max-width:500px">
</div> </div>
<div id="statusBar" class="status hidden"></div> <div id="statusBar" class="status hidden"></div>
</div> </div>
+1 -1
View File
@@ -94,7 +94,7 @@
</div> </div>
<div class="upload-group" style="margin-top:14px"> <div class="upload-group" style="margin-top:14px">
<div class="label">💬 提示词(prompt)</div> <div class="label">💬 提示词(prompt)</div>
<input type="text" id="promptInput" value="填充遮罩区域的头发" style="width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:14px;max-width:500px"> <input type="text" id="promptInput" value="填充遮罩区域的头发,人物增加一点磨皮" style="width:100%;padding:8px 12px;border:1px solid #d1d5db;border-radius:8px;font-size:14px;max-width:500px">
</div> </div>
<div style="margin-top:14px;display:flex;gap:12px;align-items:center"> <div style="margin-top:14px;display:flex;gap:12px;align-items:center">
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button> <button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
+1 -1
View File
@@ -104,7 +104,7 @@
<div class="hint">JPG/PNG &nbsp;|&nbsp; 生发图生成较慢(数十秒~数分钟),请耐心等待 &nbsp;|&nbsp; 工作流: add_hair2.json</div> <div class="hint">JPG/PNG &nbsp;|&nbsp; 生发图生成较慢(数十秒~数分钟),请耐心等待 &nbsp;|&nbsp; 工作流: add_hair2.json</div>
<div style="margin-top:10px;display:flex;align-items:center;gap:8px"> <div style="margin-top:10px;display:flex;align-items:center;gap:8px">
<label style="font-size:13px;font-weight:600;color:#374151;white-space:nowrap">💬 提示词</label> <label style="font-size:13px;font-weight:600;color:#374151;white-space:nowrap">💬 提示词</label>
<input type="text" id="promptInput" value="填充遮罩区域的头发" style="flex:1;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;max-width:500px"> <input type="text" id="promptInput" value="填充遮罩区域的头发,人物增加一点磨皮" style="flex:1;padding:8px 12px;border:1px solid #d1d5db;border-radius:6px;font-size:13px;max-width:500px">
</div> </div>
<div id="statusBar" class="status hidden"></div> <div id="statusBar" class="status hidden"></div>
</div> </div>