From 3a7c3fa07b9305149a9b64956d6d862fa2d09c22 Mon Sep 17 00:00:00 2001 From: xsl Date: Mon, 27 Jul 2026 23:38:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A32):=20ComfyUI=E9=87=8D?= =?UTF-8?q?=E7=BB=98=E5=88=86=E8=BE=A8=E7=8E=87=E9=BB=98=E8=AE=A4=E6=94=B9?= =?UTF-8?q?=E4=B8=BA1024=20+=20wave=E6=B5=8B=E8=AF=95=E5=A5=97=E4=BB=B6=20?= =?UTF-8?q?+=20=E6=8A=A5=E5=91=8A=E7=BB=9F=E4=B8=80=E9=83=A8=E7=BD=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - hairline/service.py: _REDRAW_MAX_SIDE 默认 896→1024 逻辑: 输入图长边>1024 才缩到1024; ≤1024 原图分辨率直送(不放大) male路径_GROW_B_MAX_SIDE 已是1024,行为一致 - static/test_interface2.html: 分辨率下拉选项标签同步(默认1024/>1024才缩) - image/wave_test/: wave发型5档分辨率对比测试(21图×5档=105次,全成功) batch_test.py/gen_report.py/report-server-wave.service - image/reports/: 报告统一HTTP服务(单端口8850,路径区分/wave /v2 /v1) 含索引页index.html + report-server.service + 三报告软链接 - .gitignore: 补充 wave_test/out/ 及运行期文件忽略规则 --- .gitignore | 3 + hairline/service.py | 9 +- image/reports/index.html | 47 ++++++ image/reports/report-server.service | 15 ++ image/reports/v1 | 1 + image/reports/v2 | 1 + image/reports/wave | 1 + image/wave_test/batch_test.py | 143 +++++++++++++++++ image/wave_test/gen_report.py | 176 +++++++++++++++++++++ image/wave_test/report-server-wave.service | 15 ++ static/test_interface2.html | 8 +- 11 files changed, 411 insertions(+), 8 deletions(-) create mode 100644 image/reports/index.html create mode 100644 image/reports/report-server.service create mode 120000 image/reports/v1 create mode 120000 image/reports/v2 create mode 120000 image/reports/wave create mode 100644 image/wave_test/batch_test.py create mode 100644 image/wave_test/gen_report.py create mode 100644 image/wave_test/report-server-wave.service diff --git a/.gitignore b/.gitignore index 6acd943..4e51a75 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/hairline/service.py b/hairline/service.py index 382432d..fe16171 100644 --- a/hairline/service.py +++ b/hairline/service.py @@ -49,10 +49,11 @@ _BLACK_TEXTURE_DIR = os.path.join(_REPO, "hairline_texture_black") _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): diff --git a/image/reports/index.html b/image/reports/index.html new file mode 100644 index 0000000..55d461b --- /dev/null +++ b/image/reports/index.html @@ -0,0 +1,47 @@ + + + + + +测试报告索引 + + + +
+

📊 测试报告索引

+

接口2 / 接口5 分辨率对比测试报告合集

+ + +
💇 wave发型 · 5档分辨率对比(最新)
+
21图 × 5档(原图/1024/896/768/640) = 105次 · wave发型 · female
+
/wave/
+
+ + +
💄 5图 · 5档分辨率对比 v2
+
5图 × 3发型(花瓣/心形/波浪) × 5档 = 75次 · female
+
/v2/
+
+ + +
💄 4图 · 2档分辨率对比 v1
+
4图 × 5发型 × 2档(默认896/原图) = 40次 · female
+
/v1/
+
+
+ + diff --git a/image/reports/report-server.service b/image/reports/report-server.service new file mode 100644 index 0000000..1caac3d --- /dev/null +++ b/image/reports/report-server.service @@ -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 diff --git a/image/reports/v1 b/image/reports/v1 new file mode 120000 index 0000000..5d9e5ef --- /dev/null +++ b/image/reports/v1 @@ -0,0 +1 @@ +/home/ubuntu/hair/image/compare_test/out \ No newline at end of file diff --git a/image/reports/v2 b/image/reports/v2 new file mode 120000 index 0000000..6117c2e --- /dev/null +++ b/image/reports/v2 @@ -0,0 +1 @@ +/home/ubuntu/hair/image/res_test/out \ No newline at end of file diff --git a/image/reports/wave b/image/reports/wave new file mode 120000 index 0000000..454eb76 --- /dev/null +++ b/image/reports/wave @@ -0,0 +1 @@ +/home/ubuntu/hair/image/wave_test/out \ No newline at end of file diff --git a/image/wave_test/batch_test.py b/image/wave_test/batch_test.py new file mode 100644 index 0000000..f50a918 --- /dev/null +++ b/image/wave_test/batch_test.py @@ -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() diff --git a/image/wave_test/gen_report.py b/image/wave_test/gen_report.py new file mode 100644 index 0000000..4a727ae --- /dev/null +++ b/image/wave_test/gen_report.py @@ -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'' + f'
原图
' + f'
📷 原图
') + 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'' + f'
{html.escape(r[
' + f'
{r["elapsed"]}s
') + elif r: + cell = (f'
' + f'
{html.escape(r["err"][:30])}
') + else: + cell = '
' + side_cells.append(cell) + rows_html.append("" + orig_cell + "".join(side_cells) + "") + +# 各档统计卡 +def stat_card(lbl, val, col, rng=""): + return (f'
' + f'
{val:.1f}s
' + f'
{lbl}{rng}
') + +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""" + + + + +wave发型 5档分辨率对比 + + + +
+

💇 wave发型 · 5档分辨率对比报告

+

POST /api/v1/hair/grow · female · wave(波浪) · 21 图 × 5 档分辨率 = 105 次 · 串行 · 4090 (24G)

+ +
+
{ok}/{total}
成功 / 总数
+ {stat_cards} +
+ +
+ 📊 结论:耗时随分辨率单调下降。大图(asdf 1666/qwer 1678) 原图直送需 24~26s,是 1024 档(11s) 的 2.3 倍; + 中小图(≤1024) 各档差异较小(6~13s),因小图本身不触发缩图。 + 4090 24G 全程无 OOM,105/105 成功。画质对比见下表(横向滑动),点击任意图可放大。 + + 快(<{(tmin+(tmax-tmin)*0.33):.0f}s) + 中等 + 慢(>{(tmin+(tmax-tmin)*0.66):.0f}s) + +
+ +
+ + + + + + + + + + + + + {"".join(rows_html)} + +
📷 原图原图直送 (0)
不缩放
1024896
默认
768640
+
+
+
+ + + +""" +with open(REPORT, "w") as f: + f.write(html_doc) +print(f"已生成: {REPORT}") diff --git a/image/wave_test/report-server-wave.service b/image/wave_test/report-server-wave.service new file mode 100644 index 0000000..a48ce68 --- /dev/null +++ b/image/wave_test/report-server-wave.service @@ -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 diff --git a/static/test_interface2.html b/static/test_interface2.html index 86bb3d1..28ee1ad 100644 --- a/static/test_interface2.html +++ b/static/test_interface2.html @@ -103,12 +103,12 @@
- + + + -