Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a7c3fa07b | ||
|
|
b61ea6f33b |
@@ -70,8 +70,11 @@ gateway.log
|
||||
# 仅忽略 out/ 与运行期文件;测试脚本与 HTML 报告仍入库
|
||||
image/compare_test/out/
|
||||
image/res_test/out/
|
||||
image/wave_test/out/
|
||||
image/compare_test/progress.json
|
||||
image/res_test/progress.json
|
||||
image/wave_test/progress.json
|
||||
image/compare_test/batch_test.log
|
||||
image/res_test/batch_test.log
|
||||
image/wave_test/batch_test.log
|
||||
image/compare_test/http.log
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"60": {
|
||||
"class_type": "JjkText",
|
||||
"inputs": {
|
||||
"text": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"
|
||||
"text": "填充遮罩区域的头发"
|
||||
}
|
||||
},
|
||||
"22": {
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -410,7 +410,7 @@
|
||||
},
|
||||
"60": {
|
||||
"inputs": {
|
||||
"text": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"
|
||||
"text": "填充遮罩区域的头发"
|
||||
},
|
||||
"class_type": "JjkText",
|
||||
"_meta": {
|
||||
|
||||
@@ -775,7 +775,7 @@ async def hair_grow(
|
||||
hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填),如 1,2,3。female:1-5 male:1-4"),
|
||||
beauty_enabled: bool = Form(default=False, description="是否开启美颜(本期不生效)"),
|
||||
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"),
|
||||
redraw_max_side: Optional[int] = Form(default=None, description="重绘压图长边像素。None=默认896;0=不缩图(原图直送);其他如 768/640/1024"),
|
||||
):
|
||||
@@ -890,7 +890,7 @@ async def hair_grow_b(
|
||||
marked_image_url: Optional[str] = Form(default=None, description="划线图片 URL"),
|
||||
marked_image_base64: Optional[str] = Form(default=None, description="划线图片 base64"),
|
||||
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)
|
||||
@@ -1019,10 +1019,13 @@ async def face_features(
|
||||
- 必填 `gender`(`male`/`female`),决定发型集合(female 5 / male 4)。
|
||||
- 必填 `hair_style`(发型序号,逗号分隔如 `1,2,3`),决定返回哪些发际线类型。缺失/越界/非法返回 `1007`。
|
||||
`female`:1=ellipse,2=flower,3=heart,4=straight,5=wave;`male`:1=ellipse,2=inverse_arc,3=m,4=straight。
|
||||
- 可选 `use_mask` / `prompt`:同接口2 的生发控制参数。
|
||||
注:生发黑模板固定取 `hairline_texture_black/`(middle 档),即三档叠图分别用各自贴图、但生发目标固定 middle。
|
||||
- 可选 `generate_grow_image`(默认 `true`):是否生成生发效果图(ComfyUI 生发,全流程最耗时)。
|
||||
- 可选 `use_mask` / `prompt`:同接口2 的生发控制参数(仅 male 路径生效)。
|
||||
- 可选 `generate_grow_image`(默认 `true`):是否生成生发效果图(最耗时)。
|
||||
`false` 时跳过生发,各发型 `grown_image_*` 恒为 `null`,仅返回三档发际线叠图与中心点,大幅降低耗时。
|
||||
- **生发机制(同接口2,按性别分流)**:
|
||||
`female` 走「换发型 + Flux-2 整帧重绘」(依赖 change_hair:8801 与 ComfyUI:8188);
|
||||
`male` 走 ComfyUI `add_hair` 原生 inpaint。
|
||||
- 可选 `flux_model` / `redraw_max_side`:同接口2(仅 female 路径生效)。
|
||||
|
||||
**返回说明**:
|
||||
|
||||
@@ -1099,9 +1102,11 @@ async def hairline_generate(
|
||||
image_base64: Optional[str] = Form(default=None, description="图片 base64(需带 data:image/...;base64, 前缀)"),
|
||||
gender: Optional[str] = Form(default=None, description="性别 male/female(必填)"),
|
||||
hair_style: Optional[str] = Form(default=None, description="发型序号逗号分隔(必填,如 1,2,3)。female:1-5 male:1-4"),
|
||||
use_mask: bool = Form(default=True, description="生发是否启用 inpaint 遮罩(同接口2,测试对比用)"),
|
||||
prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜", description="ComfyUI 提示词(同接口2),会替换工作流节点60的文本"),
|
||||
generate_grow_image: bool = Form(default=True, description="是否生成生发效果图(ComfyUI 生发,最耗时)。默认 true 出图;false 时跳过生发,各发型 grown_image 恒为 null,仅返回三档发际线叠图与中心点"),
|
||||
use_mask: bool = Form(default=True, description="生发是否启用 inpaint 遮罩(同接口2,仅 male 路径生效)"),
|
||||
prompt: str = Form(default="填充遮罩区域的头发", description="ComfyUI 提示词(同接口2,仅 male 路径生效),会替换工作流节点60的文本"),
|
||||
generate_grow_image: bool = Form(default=True, description="是否生成生发效果图(最耗时)。默认 true 出图;false 时跳过生发,各发型 grown_image 恒为 null,仅返回三档发际线叠图与中心点"),
|
||||
flux_model: Optional[str] = Form(default=None, description="Flux 模型文件名(同接口2,切换模型用)。None=工作流默认"),
|
||||
redraw_max_side: Optional[int] = Form(default=None, description="重绘压图长边像素(同接口2,仅 female 路径生效)。None=默认896;0=不缩图(原图直送);其他如 768/640/1024"),
|
||||
):
|
||||
if gender not in ("male", "female"):
|
||||
return err(1004, "gender 必填且只能为 male / female")
|
||||
@@ -1126,7 +1131,9 @@ async def hairline_generate(
|
||||
|
||||
res = await run_in_threadpool(
|
||||
generate_hairline_pngs, image, gender, hair_styles, use_mask, prompt,
|
||||
generate_grow_image=generate_grow_image)
|
||||
generate_grow_image=generate_grow_image,
|
||||
redraw_max_side=redraw_max_side, unet_name=flux_model,
|
||||
v2_defaults=_V2_FINAL_DEFAULTS)
|
||||
if res is None:
|
||||
return err(1001, "无法识别人像")
|
||||
|
||||
@@ -1494,7 +1501,7 @@ async def hairline_grow_v2(
|
||||
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_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"),
|
||||
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"),
|
||||
@@ -1660,7 +1667,7 @@ async def hairline_grow_v2_final_v2(
|
||||
async def api_redraw(
|
||||
image_file: UploadFile = File(..., description="人物图片(JPG/PNG)"),
|
||||
mask_file: UploadFile = File(..., description="遮罩图片(PNG,支持红/白/alpha 格式)"),
|
||||
prompt: str = Form(default="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜",
|
||||
prompt: str = Form(default="填充遮罩区域的头发",
|
||||
description="ComfyUI 提示词"),
|
||||
):
|
||||
image_bytes = await image_file.read()
|
||||
|
||||
@@ -55,7 +55,7 @@ def gpu_used():
|
||||
|
||||
def call(img_path, hair_num, model_file, res_val):
|
||||
fd = {"gender": "female", "hair_style": str(hair_num), "use_mask": "true",
|
||||
"prompt": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"}
|
||||
"prompt": "填充遮罩区域的头发"}
|
||||
if model_file:
|
||||
fd["flux_model"] = model_file
|
||||
if res_val != "":
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ def call(img_path, model_file, res_val):
|
||||
"gender": "female",
|
||||
"hair_style": "2", # 花瓣形
|
||||
"use_mask": "true",
|
||||
"prompt": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜",
|
||||
"prompt": "填充遮罩区域的头发",
|
||||
}
|
||||
if model_file:
|
||||
fd["flux_model"] = model_file
|
||||
|
||||
@@ -1107,7 +1107,7 @@ def generate_hairline_redraw(image_bgr, hairline_id, is_hr=False, seg_model="seg
|
||||
"hairline_id": hairline_id,
|
||||
"blend_method": blend_method,
|
||||
"hairline_push_cm": round(float(hairline_push_cm), 2),
|
||||
"comfyui_prompt": comfyui_prompt or "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜",
|
||||
"comfyui_prompt": comfyui_prompt or "填充遮罩区域的头发",
|
||||
"beauty_alpha": beauty_alpha,
|
||||
"px_per_cm": round(float(px_per_cm), 4),
|
||||
"mask_pixels": mask_viz["mask_pixels"],
|
||||
|
||||
+1
-1
@@ -410,7 +410,7 @@
|
||||
},
|
||||
"60": {
|
||||
"inputs": {
|
||||
"text": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"
|
||||
"text": "填充遮罩区域的头发"
|
||||
},
|
||||
"class_type": "JjkText",
|
||||
"_meta": {
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ from . import comfyui
|
||||
|
||||
logger = logging.getLogger("hair.worker")
|
||||
|
||||
_DEFAULT_PROMPT = "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"
|
||||
_DEFAULT_PROMPT = "填充遮罩区域的头发"
|
||||
_REPO = os.path.dirname(os.path.dirname(__file__))
|
||||
_REPAINT_WORKFLOW = os.path.join(_REPO, "0716add-hair-api.json")
|
||||
|
||||
@@ -62,7 +62,7 @@ def run_redraw(image_bytes: bytes, mask_bytes: bytes,
|
||||
Args:
|
||||
image_bytes: 人物图片字节(JPG/PNG)
|
||||
mask_bytes: 遮罩图片字节(支持红/白/alpha 遮罩格式)
|
||||
prompt: 提示词,None 用默认 "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"
|
||||
prompt: 提示词,None 用默认 "填充遮罩区域的头发"
|
||||
timeout: ComfyUI 超时秒数
|
||||
front: True 时任务插到 ComfyUI 队列最前(接口2 时延敏感路径用)
|
||||
unet_name: 非 None 时切换 Flux 模型(如 flux-2-klein-9b-Q5_K_M.gguf),None 用工作流默认
|
||||
|
||||
+42
-22
@@ -46,13 +46,14 @@ _BLACK_TEXTURE_DIR = os.path.join(_REPO, "hairline_texture_black")
|
||||
# 关键:ComfyUI 单卡显存装不下 Flux(7.7G)+qwen CLIP(3.9G) 同驻,靠缓存 CLIP 文本条件避免重载。
|
||||
# prompt 不同会使缓存失效 → 重载 CLIP 并挤出 Flux(每次 +4s)。三接口用同一字符串即可全程命中。
|
||||
# 与 app.py 接口2/接口3 的默认 prompt 保持一致;可用 REDRAW_PROMPT 覆盖。
|
||||
_REDRAW_PROMPT = os.getenv("REDRAW_PROMPT", "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜")
|
||||
_REDRAW_PROMPT = os.getenv("REDRAW_PROMPT", "填充遮罩区域的头发")
|
||||
|
||||
# 接口2 女重绘整条管线(swapHair + ComfyUI)送模型前限边。真实照片常达 1257x1495:
|
||||
# 全分辨率 ComfyUI 重绘要 13~21s 且激活显存把模型挤出。女性路径含 swapHair(SD WebUI ~5.3s
|
||||
# 固定地板) + ComfyUI 两段串行。1024 档画质更好但部分大图会踩 12s 线,
|
||||
# 默认压到 896 兜底(ComfyUI ~4s,女性总耗时 9~11s);追画质可设 REDRAW_MAX_SIDE=1024。
|
||||
_REDRAW_MAX_SIDE = int(os.getenv("REDRAW_MAX_SIDE", "896"))
|
||||
# 全分辨率 ComfyUI 重绘要 13~21s 且激活显存把模型挤出。
|
||||
# 策略:输入图长边 > REDRAW_MAX_SIDE 才等比缩到该长边;≤ 时原图分辨率直送(不放大)。
|
||||
# 默认 1024:大于 1024 的图压到 1024(画质/速度均衡),≤1024 的小图保持原分辨率重绘。
|
||||
# 可用 REDRAW_MAX_SIDE 覆盖;0=永不缩图(原图直送)。
|
||||
_REDRAW_MAX_SIDE = int(os.getenv("REDRAW_MAX_SIDE", "1024"))
|
||||
|
||||
def _call_local_redraw(image_png_bytes, mask_png_bytes, timeout=300.0,
|
||||
max_side=None, unet_name=None):
|
||||
@@ -410,14 +411,22 @@ def _grow_from_texture(image_bgr: np.ndarray, ctx: dict, white_path: str | None,
|
||||
def generate_hairline_pngs(image_bgr: np.ndarray, gender: str,
|
||||
hair_styles: list[int], use_mask: bool = True,
|
||||
prompt: str | None = None,
|
||||
generate_grow_image: bool = True):
|
||||
generate_grow_image: bool = True,
|
||||
redraw_max_side: int | None = None,
|
||||
unet_name: str | None = None,
|
||||
v2_defaults: dict | None = None):
|
||||
"""接口5:对选中发型返回 middle/high/low 三档发际线透明叠图 + 生发图(同接口2)。
|
||||
|
||||
入参同接口2:先选 gender,再多选 hair_styles(必填,1-indexed 按贴图排序)。
|
||||
每个选中发型返回三档叠图(middle/high/low,RGBA 透明层只含发际线曲线)与一张生发图;
|
||||
三档贴图同名,生发黑模板固定取自 hairline_texture_black/(middle),故生发目标固定 middle 档。
|
||||
use_mask/prompt:同接口2 的生发参数。
|
||||
generate_grow_image(默认 True):是否生成生发图(ComfyUI,最耗时)。False 时跳过生发,
|
||||
|
||||
生发机制(同接口2,按性别分流):
|
||||
- female:generate_grow_results_swap(swapHair + Flux-2 整帧重绘)
|
||||
- male:generate_grow_results(ComfyUI add_hair inpaint)
|
||||
redraw_max_side / unet_name / v2_defaults:female 路径参数,同接口2。
|
||||
male 路径仅用 unet_name;redraw_max_side/v2_defaults 对 male 无意义(忽略)。
|
||||
use_mask/prompt:仅 male 路径生效(同接口2 male)。
|
||||
generate_grow_image(默认 True):是否生成生发图(最耗时)。False 时跳过生发,
|
||||
各发型 grown_png 恒为 None,可大幅降低耗时(仅留三档发际线叠图与中心点)。
|
||||
Returns: {"images":[{hairline_type,order,overlays:{middle,high,low}((H,W,4) RGBA 透明层),grown_png}],
|
||||
"best_centers":{"middle":(x,y),"high":(x,y),"low":(x,y)}};无人脸 None。
|
||||
@@ -439,11 +448,27 @@ def generate_hairline_pngs(image_bgr: np.ndarray, gender: str,
|
||||
# 三档贴图表(同性别、同 key 顺序,因三个文件夹同名)
|
||||
tex_by_level = {lv: get_texture_map(lv)[gender] for lv in _TEXTURE_DIRS}
|
||||
|
||||
# use_mask=False:干净原图+空遮罩与贴图无关,只跑一次 ComfyUI,选中项复用
|
||||
# generate_grow_image=False:完全跳过生发(最耗时),grown_png 恒为 None
|
||||
shared_grown = None
|
||||
if generate_grow_image and not use_mask:
|
||||
shared_grown = _grow_from_texture(image_bgr, ctx, None, use_mask=False, prompt=prompt)
|
||||
# 生发图(同接口2,按性别分流):一次性算出所有选中发型的生发图,按 order 对应回叠图。
|
||||
# female→generate_grow_results_swap(swapHair+Flux-2 整帧重绘);
|
||||
# male→generate_grow_results(ComfyUI add_hair inpaint)。
|
||||
# generate_grow_image=False 时跳过,grown_by_order 为空 dict(各发型 grown_png 恒 None)。
|
||||
grown_by_order: dict[int, bytes | None] = {}
|
||||
if generate_grow_image:
|
||||
try:
|
||||
if gender == "female":
|
||||
items = generate_grow_results_swap(
|
||||
image_bgr, hair_styles, v2_defaults or {},
|
||||
redraw_max_side=redraw_max_side, unet_name=unet_name)
|
||||
else:
|
||||
items = generate_grow_results(
|
||||
image_bgr, gender, use_mask, prompt, hair_styles,
|
||||
unet_name=unet_name)
|
||||
if items is None:
|
||||
return None # 无人脸(同接口2 的 None 语义)
|
||||
for it in items:
|
||||
grown_by_order[it["order"]] = it.get("grown_png")
|
||||
except Exception as e: # noqa: BLE001 整批生发失败不拖垮叠图主结果
|
||||
logger.warning("接口5 生发批量失败(gender=%s):%s", gender, e)
|
||||
|
||||
def _center_of(overlay):
|
||||
"""从某档发际线透明叠图取面部中轴处的发际线中点 (x,y),无像素返回 None。"""
|
||||
@@ -456,18 +481,13 @@ def generate_hairline_pngs(image_bgr: np.ndarray, gender: str,
|
||||
|
||||
images, best_centers = [], None
|
||||
for s in hair_styles: # s = 1-indexed 发型序号
|
||||
key, mid_path = tex_by_level["middle"][s - 1]
|
||||
key, _mid_path = tex_by_level["middle"][s - 1]
|
||||
overlays = {}
|
||||
for lv in _TEXTURE_DIRS:
|
||||
white = load_texture_rgba(tex_by_level[lv][s - 1][1])
|
||||
overlays[lv] = build_overlay_layer(h, w, ctx["points"], ext_faces, uv, white)
|
||||
# 生发:固定 middle 黑模板(generate_grow_image=False 时跳过,恒 None)
|
||||
if not generate_grow_image:
|
||||
grown_png = None
|
||||
elif not use_mask:
|
||||
grown_png = shared_grown
|
||||
else:
|
||||
grown_png = _grow_from_texture(image_bgr, ctx, mid_path, use_mask=True, prompt=prompt)
|
||||
# 生发图:从按性别算好的结果里按 order 取(generate_grow_image=False 时缺省 None)
|
||||
grown_png = grown_by_order.get(s)
|
||||
images.append({"hairline_type": key, "order": s,
|
||||
"overlays": overlays, "grown_png": grown_png})
|
||||
# best_centers:首个选中发型三档(middle/high/low)发际线中点
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>测试报告索引</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif; background: #f3f4f6; color: #1f2937; padding: 40px 20px; }
|
||||
.wrap { max-width: 720px; margin: 0 auto; }
|
||||
h1 { font-size: 24px; margin-bottom: 4px; }
|
||||
.sub { color: #6b7280; font-size: 13px; margin-bottom: 28px; }
|
||||
.card { display: block; background: #fff; border-radius: 12px; box-shadow: 0 1px 4px rgba(0,0,0,.07); padding: 20px 24px; margin-bottom: 14px; text-decoration: none; color: inherit; transition: .15s; border-left: 4px solid #2563eb; }
|
||||
.card:hover { transform: translateX(4px); box-shadow: 0 4px 12px rgba(0,0,0,.1); }
|
||||
.card .title { font-size: 16px; font-weight: 700; margin-bottom: 4px; }
|
||||
.card .desc { font-size: 13px; color: #6b7280; }
|
||||
.card .url { font-size: 11px; color: #9ca3af; margin-top: 6px; font-family: monospace; }
|
||||
.card.wave { border-left-color: #16a34a; }
|
||||
.card.v2 { border-left-color: #f59e0b; }
|
||||
.card.v1 { border-left-color: #2563eb; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>📊 测试报告索引</h1>
|
||||
<p class="sub">接口2 / 接口5 分辨率对比测试报告合集</p>
|
||||
|
||||
<a class="card wave" href="wave/">
|
||||
<div class="title">💇 wave发型 · 5档分辨率对比(最新)</div>
|
||||
<div class="desc">21图 × 5档(原图/1024/896/768/640) = 105次 · wave发型 · female</div>
|
||||
<div class="url">/wave/</div>
|
||||
</a>
|
||||
|
||||
<a class="card v2" href="v2/">
|
||||
<div class="title">💄 5图 · 5档分辨率对比 v2</div>
|
||||
<div class="desc">5图 × 3发型(花瓣/心形/波浪) × 5档 = 75次 · female</div>
|
||||
<div class="url">/v2/</div>
|
||||
</a>
|
||||
|
||||
<a class="card v1" href="v1/">
|
||||
<div class="title">💄 4图 · 2档分辨率对比 v1</div>
|
||||
<div class="desc">4图 × 5发型 × 2档(默认896/原图) = 40次 · female</div>
|
||||
<div class="url">/v1/</div>
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=测试报告统一HTTP服务 (8850, 路径区分: /wave /v2 /v1)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=ubuntu
|
||||
WorkingDirectory=/home/ubuntu/hair/image/reports
|
||||
ExecStart=/home/ubuntu/miniconda3/envs/my_hair/bin/python -m http.server 8850 --bind 0.0.0.0
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/home/ubuntu/hair/image/compare_test/out
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/home/ubuntu/hair/image/res_test/out
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/home/ubuntu/hair/image/wave_test/out
|
||||
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env python3
|
||||
"""接口2 female wave发型 分辨率对比测试 v3: 21图 × 5档 = 105次。
|
||||
串行执行,记录耗时与成败,结果图按 图_档位 命名保存。支持断点续跑。
|
||||
"""
|
||||
import base64, csv, json, os, time, io
|
||||
import requests
|
||||
from PIL import Image
|
||||
|
||||
API = "http://127.0.0.1:8187/api/v1/hair/grow"
|
||||
TOKEN = "dev-shared-secret-2026"
|
||||
TIMEOUT = 600
|
||||
OUT = "/home/ubuntu/hair/image/wave_test/out"
|
||||
PROGRESS = "/home/ubuntu/hair/image/wave_test/progress.json"
|
||||
|
||||
IMG_DIR = "/home/ubuntu/hair/image"
|
||||
# 21张图:19张girl_img + asdf + qwer
|
||||
IMAGES = []
|
||||
for f in sorted(os.listdir(os.path.join(IMG_DIR, "girl_img"))):
|
||||
if f.lower().endswith((".jpg", ".jpeg", ".png")):
|
||||
IMAGES.append((os.path.splitext(f)[0], os.path.join(IMG_DIR, "girl_img", f)))
|
||||
IMAGES.append(("asdf", os.path.join(IMG_DIR, "asdf.jpg")))
|
||||
IMAGES.append(("qwer", os.path.join(IMG_DIR, "qwer.jpg")))
|
||||
|
||||
# wave = female hair_style 5
|
||||
STYLE_IDX = 5
|
||||
# 5档: 原图(0) / 1024 / 896 / 768 / 640
|
||||
SIDES = [
|
||||
("origin", 0),
|
||||
("s1024", 1024),
|
||||
("s896", 896),
|
||||
("s768", 768),
|
||||
("s640", 640),
|
||||
]
|
||||
|
||||
def load_progress():
|
||||
if os.path.exists(PROGRESS):
|
||||
try:
|
||||
return json.load(open(PROGRESS))
|
||||
except Exception:
|
||||
pass
|
||||
return {"done": [], "results": []}
|
||||
|
||||
def save_progress(prog):
|
||||
json.dump(prog, open(PROGRESS, "w"), ensure_ascii=False, indent=1)
|
||||
|
||||
def run_one(img_name, img_path, side_name, side_val):
|
||||
key = f"{img_name}_{side_name}"
|
||||
with open(img_path, "rb") as f:
|
||||
img_b64 = base64.b64encode(f.read()).decode()
|
||||
data = {
|
||||
"image_base64": "data:image/jpeg;base64," + img_b64,
|
||||
"gender": "female",
|
||||
"hair_style": str(STYLE_IDX),
|
||||
"redraw_max_side": str(side_val),
|
||||
}
|
||||
t0 = time.time()
|
||||
rec = {"key": key, "img": img_name, "side": side_name, "side_val": side_val,
|
||||
"ok": False, "elapsed": 0.0, "err": "", "out_w": 0, "out_h": 0, "bytes": 0}
|
||||
try:
|
||||
r = requests.post(API, data=data, headers={"X-Internal-Token": TOKEN}, timeout=TIMEOUT)
|
||||
rec["elapsed"] = round(time.time() - t0, 1)
|
||||
d = r.json()
|
||||
if d.get("code") != 0:
|
||||
rec["err"] = f"code={d.get('code')} {d.get('message','')}"[:200]
|
||||
return rec
|
||||
results = (d.get("data") or {}).get("results") or []
|
||||
if not results:
|
||||
rec["err"] = "空结果"
|
||||
return rec
|
||||
it = results[0]
|
||||
grown = it.get("grown_image_base64")
|
||||
if not grown:
|
||||
rec["err"] = "无生发图(重绘失败/OOM?)"
|
||||
return rec
|
||||
raw = base64.b64decode(grown)
|
||||
im = Image.open(io.BytesIO(raw))
|
||||
rec["out_w"], rec["out_h"] = im.size
|
||||
out_path = f"{OUT}/{key}.jpg"
|
||||
with open(out_path, "wb") as fo:
|
||||
fo.write(raw)
|
||||
rec["ok"] = True
|
||||
rec["bytes"] = len(raw)
|
||||
except requests.exceptions.Timeout:
|
||||
rec["elapsed"] = round(time.time() - t0, 1)
|
||||
rec["err"] = f"超时(>{TIMEOUT}s)"
|
||||
except Exception as e:
|
||||
rec["elapsed"] = round(time.time() - t0, 1)
|
||||
rec["err"] = f"{type(e).__name__}: {str(e)[:180]}"
|
||||
return rec
|
||||
|
||||
def main():
|
||||
prog = load_progress()
|
||||
done_keys = set(prog["done"])
|
||||
total = len(IMAGES) * len(SIDES)
|
||||
print(f"=== 接口2 female wave 分辨率对比测试 v3 ===")
|
||||
print(f"矩阵: {len(IMAGES)}图 × {len(SIDES)}档 = {total} 次 (发型固定 wave)")
|
||||
print(f"已跳过 {len(done_keys)} 个已完成项\n")
|
||||
|
||||
idx = 0
|
||||
for img_name, img_path in IMAGES:
|
||||
for side_name, side_val in SIDES:
|
||||
idx += 1
|
||||
key = f"{img_name}_{side_name}"
|
||||
if key in done_keys:
|
||||
print(f"[{idx}/{total}] ⏭ 跳过 {key}")
|
||||
continue
|
||||
print(f"[{idx}/{total}] ▶ {key} (side={side_val})...", end=" ", flush=True)
|
||||
rec = run_one(img_name, img_path, side_name, side_val)
|
||||
prog["results"].append(rec)
|
||||
prog["done"].append(key)
|
||||
save_progress(prog)
|
||||
if rec["ok"]:
|
||||
print(f"✅ {rec['elapsed']}s {rec['out_w']}x{rec['out_h']} ({rec['bytes']}B)")
|
||||
else:
|
||||
print(f"❌ {rec['elapsed']}s {rec['err']}")
|
||||
time.sleep(2)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("汇总")
|
||||
print("=" * 70)
|
||||
write_report(prog["results"])
|
||||
print(f"\n结果图: {OUT}/")
|
||||
print(f"CSV: {OUT}/report.csv JSON: {OUT}/report.json")
|
||||
|
||||
def write_report(results):
|
||||
with open(f"{OUT}/report.csv", "w", newline="") as f:
|
||||
w = csv.writer(f)
|
||||
w.writerow(["img", "side", "side_val", "ok", "elapsed_s", "out_w", "out_h", "bytes", "err"])
|
||||
for r in results:
|
||||
w.writerow([r["img"], r["side"], r["side_val"], r["ok"],
|
||||
r["elapsed"], r["out_w"], r["out_h"], r["bytes"], r["err"]])
|
||||
json.dump(results, open(f"{OUT}/report.json", "w"), ensure_ascii=False, indent=1)
|
||||
|
||||
# 各档平均耗时
|
||||
print("\n--- 各档平均耗时(秒)---")
|
||||
for side_name, side_val in SIDES:
|
||||
ts = [r["elapsed"] for r in results if r["side"] == side_name and r["ok"]]
|
||||
if ts:
|
||||
avg = sum(ts) / len(ts)
|
||||
print(f" {side_name:8s} (={side_val:>4}): 平均 {avg:5.1f}s [{min(ts):.1f}~{max(ts):.1f}] 成功 {len(ts)}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""生成 v3 wave发型 分辨率对比报告:每行=原图+5档,21行;含各档耗时统计。"""
|
||||
import json, os, html
|
||||
|
||||
OUT = "/home/ubuntu/hair/image/wave_test/out"
|
||||
REPORT = os.path.join(OUT, "report.html")
|
||||
results = json.load(open(os.path.join(OUT, "report.json")))
|
||||
|
||||
SIDES = [("origin", "原图直送", 0), ("s1024", "1024", 1024),
|
||||
("s896", "896", 896), ("s768", "768", 768), ("s640", "640", 640)]
|
||||
|
||||
# 图片顺序(与测试脚本一致):girl_img 排序 + asdf + qwer
|
||||
IMG_DIR = "/home/ubuntu/hair/image"
|
||||
IMAGES = []
|
||||
for f in sorted(os.listdir(os.path.join(IMG_DIR, "girl_img"))):
|
||||
if f.lower().endswith((".jpg", ".jpeg", ".png")):
|
||||
IMAGES.append(os.path.splitext(f)[0])
|
||||
IMAGES.append("asdf")
|
||||
IMAGES.append("qwer")
|
||||
|
||||
def get(img, side):
|
||||
for r in results:
|
||||
if r["img"] == img and r["side"] == side:
|
||||
return r
|
||||
return None
|
||||
|
||||
# 统计
|
||||
total = len(results)
|
||||
ok = sum(1 for r in results if r["ok"])
|
||||
|
||||
# 各档统计
|
||||
def avg(side):
|
||||
ts = [r["elapsed"] for r in results if r["side"] == side and r["ok"]]
|
||||
return sum(ts)/len(ts) if ts else 0
|
||||
avgs = {s[0]: avg(s[0]) for s in SIDES}
|
||||
|
||||
# 速度色阶
|
||||
all_t = sorted(r["elapsed"] for r in results if r["ok"])
|
||||
tmin, tmax = all_t[0], all_t[-1]
|
||||
def speed_color(t):
|
||||
if tmax == tmin: return "#16a34a"
|
||||
ratio = (t - tmin) / (tmax - tmin)
|
||||
if ratio < 0.33: return "#16a34a"
|
||||
elif ratio < 0.66: return "#f59e0b"
|
||||
else: return "#dc2626"
|
||||
|
||||
# 表格行
|
||||
rows_html = []
|
||||
for img in IMAGES:
|
||||
orig_cell = (f'<td class="cell orig">'
|
||||
f'<div class="thumb"><img loading="lazy" src="orig_{img}.jpg" '
|
||||
f'onclick="openImg(this.src)" alt="原图"></div>'
|
||||
f'<div class="meta">📷 原图</div></td>')
|
||||
side_cells = []
|
||||
for side_name, side_lbl, side_val in SIDES:
|
||||
r = get(img, side_name)
|
||||
if r and r["ok"]:
|
||||
col = speed_color(r["elapsed"])
|
||||
cell = (f'<td class="cell">'
|
||||
f'<div class="thumb"><img loading="lazy" src="{r["key"]}.jpg" '
|
||||
f'onclick="openImg(this.src)" alt="{html.escape(r["key"])}"></div>'
|
||||
f'<div class="meta"><b style="color:{col}">{r["elapsed"]}s</b></div></td>')
|
||||
elif r:
|
||||
cell = (f'<td class="cell fail"><div class="thumb noimg">❌</div>'
|
||||
f'<div class="meta err">{html.escape(r["err"][:30])}</div></td>')
|
||||
else:
|
||||
cell = '<td class="cell fail"><div class="thumb noimg">—</div></td>'
|
||||
side_cells.append(cell)
|
||||
rows_html.append("<tr>" + orig_cell + "".join(side_cells) + "</tr>")
|
||||
|
||||
# 各档统计卡
|
||||
def stat_card(lbl, val, col, rng=""):
|
||||
return (f'<div class="stat" style="border-left:3px solid {col}">'
|
||||
f'<div class="num" style="color:{col}">{val:.1f}s</div>'
|
||||
f'<div class="lbl">{lbl}{rng}</div></div>')
|
||||
|
||||
stat_cards = "".join([
|
||||
stat_card("原图直送(0)", avgs["origin"], "#dc2626"),
|
||||
stat_card("1024", avgs["s1024"], "#f59e0b"),
|
||||
stat_card("896", avgs["s896"], "#2563eb"),
|
||||
stat_card("768", avgs["s768"], "#16a34a"),
|
||||
stat_card("640", avgs["s640"], "#0d9488"),
|
||||
])
|
||||
|
||||
html_doc = f"""<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>wave发型 5档分辨率对比</title>
|
||||
<style>
|
||||
* {{ box-sizing: border-box; margin: 0; padding: 0; }}
|
||||
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "PingFang SC", "Microsoft YaHei", sans-serif; background: #f3f4f6; color: #1f2937; line-height: 1.5; padding: 16px; }}
|
||||
.wrap {{ max-width: 100%; margin: 0 auto; }}
|
||||
h1 {{ font-size: 22px; margin-bottom: 4px; }}
|
||||
.sub {{ color: #6b7280; font-size: 12px; margin-bottom: 16px; }}
|
||||
.stats {{ display: flex; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; }}
|
||||
.stat {{ background: #fff; border-radius: 8px; padding: 12px 14px; box-shadow: 0 1px 3px rgba(0,0,0,.06); flex: 1; min-width: 110px; }}
|
||||
.stat .num {{ font-size: 22px; font-weight: 700; }}
|
||||
.stat .lbl {{ font-size: 11px; color: #6b7280; margin-top: 2px; }}
|
||||
.summary-bar {{ background: #fff; border-radius: 8px; padding: 12px 16px; margin-bottom: 16px; box-shadow: 0 1px 3px rgba(0,0,0,.06); font-size: 13px; }}
|
||||
.summary-bar b {{ color: #dc2626; }}
|
||||
.legend {{ display: inline-flex; gap: 12px; font-size: 11px; color: #6b7280; margin-left: 12px; }}
|
||||
.legend span {{ display: inline-flex; align-items: center; gap: 4px; }}
|
||||
.legend i {{ width: 10px; height: 10px; border-radius: 2px; display: inline-block; }}
|
||||
.table-wrap {{ overflow-x: auto; background: #fff; border-radius: 10px; box-shadow: 0 1px 4px rgba(0,0,0,.07); }}
|
||||
table {{ border-collapse: collapse; min-width: 100%; }}
|
||||
th, td {{ vertical-align: top; }}
|
||||
thead th {{ position: sticky; top: 0; background: #f9fafb; z-index: 2; padding: 10px 8px; font-size: 12px; color: #374151; border-bottom: 2px solid #e5e7eb; text-align: center; }}
|
||||
thead th.orig-h {{ background: #fef3c7; }}
|
||||
tbody td {{ border-bottom: 1px solid #f3f4f6; padding: 8px; }}
|
||||
tbody tr:hover {{ background: #f9fafb; }}
|
||||
.cell {{ width: 200px; min-width: 200px; text-align: center; }}
|
||||
.cell.orig {{ background: #fffbeb; }}
|
||||
.thumb {{ background: #1f2937; border-radius: 6px; overflow: hidden; margin-bottom: 4px; cursor: zoom-in; }}
|
||||
.thumb img {{ width: 100%; height: 260px; object-fit: contain; display: block; }}
|
||||
.thumb.noimg {{ color: #d1d5db; font-size: 16px; padding: 110px 0; text-align: center; }}
|
||||
.meta {{ font-size: 11px; color: #6b7280; }}
|
||||
.meta.err {{ color: #dc2626; }}
|
||||
.overlay {{ display: none; position: fixed; inset: 0; background: rgba(0,0,0,.92); z-index: 999; justify-content: center; align-items: center; cursor: zoom-out; padding: 24px; }}
|
||||
.overlay.active {{ display: flex; }}
|
||||
.overlay img {{ max-width: 96%; max-height: 96%; object-fit: contain; border-radius: 4px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>💇 wave发型 · 5档分辨率对比报告</h1>
|
||||
<p class="sub">POST /api/v1/hair/grow · female · wave(波浪) · 21 图 × 5 档分辨率 = 105 次 · 串行 · 4090 (24G)</p>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat" style="border-left:3px solid #16a34a"><div class="num" style="color:#16a34a">{ok}/{total}</div><div class="lbl">成功 / 总数</div></div>
|
||||
{stat_cards}
|
||||
</div>
|
||||
|
||||
<div class="summary-bar">
|
||||
📊 <b>结论:</b>耗时随分辨率单调下降。<b>大图(asdf 1666/qwer 1678) 原图直送需 24~26s,是 1024 档(11s) 的 2.3 倍</b>;
|
||||
中小图(≤1024) 各档差异较小(6~13s),因小图本身不触发缩图。
|
||||
<b>4090 24G 全程无 OOM</b>,105/105 成功。<b>画质对比</b>见下表(横向滑动),点击任意图可放大。
|
||||
<span class="legend">
|
||||
<span><i style="background:#16a34a"></i>快(<{(tmin+(tmax-tmin)*0.33):.0f}s)</span>
|
||||
<span><i style="background:#f59e0b"></i>中等</span>
|
||||
<span><i style="background:#dc2626"></i>慢(>{(tmin+(tmax-tmin)*0.66):.0f}s)</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="table-wrap">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="orig-h">📷 原图</th>
|
||||
<th>原图直送 (0)<br><span class="dim">不缩放</span></th>
|
||||
<th>1024</th>
|
||||
<th>896<br><span class="dim">默认</span></th>
|
||||
<th>768</th>
|
||||
<th>640</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{"".join(rows_html)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overlay" id="overlay" onclick="this.classList.remove('active')"><img id="overlayImg" src=""></div>
|
||||
<script>
|
||||
function openImg(src) {{
|
||||
document.getElementById('overlayImg').src = src;
|
||||
document.getElementById('overlay').classList.add('active');
|
||||
}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
with open(REPORT, "w") as f:
|
||||
f.write(html_doc)
|
||||
print(f"已生成: {REPORT}")
|
||||
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=wave发型5档分辨率对比报告 (8850)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=ubuntu
|
||||
WorkingDirectory=/home/ubuntu/hair/image/wave_test/out
|
||||
ExecStart=/home/ubuntu/miniconda3/envs/my_hair/bin/python -m http.server 8850 --bind 0.0.0.0
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
+1
-1
@@ -206,7 +206,7 @@ def generate():
|
||||
return jsonify({"error": msg}), 400
|
||||
image_file = request.files["image"]
|
||||
mask_file = request.files["mask"]
|
||||
prompt_text = request.form.get("prompt", "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜")
|
||||
prompt_text = request.form.get("prompt", "填充遮罩区域的头发")
|
||||
log.info(
|
||||
"收到请求: image=%s mask=%s prompt=%r",
|
||||
image_file.filename, mask_file.filename, prompt_text,
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ def prep_and_upload():
|
||||
|
||||
|
||||
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"]["weight_dtype"] = dtype
|
||||
wf["1"]["inputs"]["steps"] = steps
|
||||
|
||||
@@ -33,7 +33,7 @@ def upload(scale=1.0):
|
||||
|
||||
|
||||
def run(fname, steps):
|
||||
wf = A.build_workflow(fname, "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜")
|
||||
wf = A.build_workflow(fname, "填充遮罩区域的头发")
|
||||
wf["16"]["inputs"]["unet_name"] = MODEL
|
||||
wf["16"]["inputs"]["weight_dtype"] = DTYPE
|
||||
wf["1"]["inputs"]["steps"] = steps
|
||||
|
||||
@@ -30,7 +30,7 @@ SEED = 123456789
|
||||
imgs = []
|
||||
labels = []
|
||||
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"]["weight_dtype"] = DTYPE
|
||||
wf["1"]["inputs"]["steps"] = steps
|
||||
|
||||
@@ -55,7 +55,7 @@ button { padding: 10px 24px; border: none; border-radius: 6px; cursor: pointer;
|
||||
</div>
|
||||
<div class="controls" style="margin-top:16px">
|
||||
<label>提示词:</label>
|
||||
<input type="text" id="promptInput" value="填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜">
|
||||
<input type="text" id="promptInput" value="填充遮罩区域的头发">
|
||||
</div>
|
||||
<div style="text-align:center; margin-top:16px">
|
||||
<button class="btn-generate" id="generateBtn" disabled>🚀 生成</button>
|
||||
|
||||
@@ -25,7 +25,7 @@ resp = requests.post(
|
||||
"image": ("original.jpg", img_data, "image/jpeg"),
|
||||
"mask": ("mask.png", mask_data, "image/png"),
|
||||
},
|
||||
data={"prompt": "填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜"},
|
||||
data={"prompt": "填充遮罩区域的头发"},
|
||||
timeout=600,
|
||||
)
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
</div>
|
||||
<div class="upload-row" style="margin-top:10px">
|
||||
<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 class="upload-row" style="margin-top:10px">
|
||||
<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() {
|
||||
if (!_finalDataUri || !_maskDataUri) { setStatus('缺少 final 或遮罩,请先生成', 'error'); return; }
|
||||
const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜';
|
||||
const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发';
|
||||
const btn = $('redrawBtn');
|
||||
btn.disabled = true; btn.textContent = '⏳ 重绘中...';
|
||||
flog('===== 调后端重绘 =====', 'info');
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</div>
|
||||
<div class="upload-row" style="margin-top:10px">
|
||||
<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 class="params">
|
||||
<div class="pf">
|
||||
@@ -169,7 +169,7 @@ async function submitTest() {
|
||||
|
||||
async function runLocalRedraw() {
|
||||
if (!_finalDataUri || !_maskDataUri) { setStatus('缺少 final 或遮罩,请先生成', 'error'); return; }
|
||||
const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜';
|
||||
const prompt = $('localTestPrompt').value.trim() || '填充遮罩区域的头发';
|
||||
const btn = $('redrawBtn');
|
||||
btn.disabled = true; btn.textContent = '⏳ 重绘中...';
|
||||
|
||||
|
||||
@@ -103,12 +103,12 @@
|
||||
</div>
|
||||
<div class="form-group" id="redrawSideGroup">
|
||||
<label>重绘分辨率</label>
|
||||
<select id="redrawMaxSide" title="仅 Female 生效:ComfyUI 重绘前把送图长边压到该像素。值越小越快越省显存,细节越少;0=原图直送">
|
||||
<option value="" selected>默认 896</option>
|
||||
<option value="1024">1024(高清,慢)</option>
|
||||
<select id="redrawMaxSide" title="仅 Female 生效:图长边超过该值才缩到该值,不超过则原图直送。默认1024=大于1024压到1024,小图保持原分辨率">
|
||||
<option value="" selected>默认 1024(>1024 才缩)</option>
|
||||
<option value="0">0 — 永不缩图(原图直送)</option>
|
||||
<option value="1280">1280(更高清,慢)</option>
|
||||
<option value="768">768(快)</option>
|
||||
<option value="640">640(最快,糊)</option>
|
||||
<option value="0">0 — 不缩图(原图直送)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
|
||||
@@ -117,7 +117,7 @@
|
||||
<div class="hint">JPG/PNG | 生发图生成较慢(数十秒~数分钟),请耐心等待</div>
|
||||
<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>
|
||||
<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 id="statusBar" class="status hidden"></div>
|
||||
</div>
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
</div>
|
||||
<div class="upload-group" style="margin-top:14px">
|
||||
<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 style="margin-top:14px;display:flex;gap:12px;align-items:center">
|
||||
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<div class="hint">JPG/PNG | 生发图生成较慢(数十秒~数分钟),请耐心等待 | 工作流: add_hair2.json</div>
|
||||
<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>
|
||||
<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 id="statusBar" class="status hidden"></div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user