feat(接口2): ComfyUI重绘分辨率默认改为1024 + wave测试套件 + 报告统一部署
- 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/ 及运行期文件忽略规则
This commit is contained in:
@@ -70,8 +70,11 @@ gateway.log
|
|||||||
# 仅忽略 out/ 与运行期文件;测试脚本与 HTML 报告仍入库
|
# 仅忽略 out/ 与运行期文件;测试脚本与 HTML 报告仍入库
|
||||||
image/compare_test/out/
|
image/compare_test/out/
|
||||||
image/res_test/out/
|
image/res_test/out/
|
||||||
|
image/wave_test/out/
|
||||||
image/compare_test/progress.json
|
image/compare_test/progress.json
|
||||||
image/res_test/progress.json
|
image/res_test/progress.json
|
||||||
|
image/wave_test/progress.json
|
||||||
image/compare_test/batch_test.log
|
image/compare_test/batch_test.log
|
||||||
image/res_test/batch_test.log
|
image/res_test/batch_test.log
|
||||||
|
image/wave_test/batch_test.log
|
||||||
image/compare_test/http.log
|
image/compare_test/http.log
|
||||||
|
|||||||
+5
-4
@@ -49,10 +49,11 @@ _BLACK_TEXTURE_DIR = os.path.join(_REPO, "hairline_texture_black")
|
|||||||
_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 且激活显存把模型挤出。
|
||||||
# 固定地板) + ComfyUI 两段串行。1024 档画质更好但部分大图会踩 12s 线,
|
# 策略:输入图长边 > REDRAW_MAX_SIDE 才等比缩到该长边;≤ 时原图分辨率直送(不放大)。
|
||||||
# 默认压到 896 兜底(ComfyUI ~4s,女性总耗时 9~11s);追画质可设 REDRAW_MAX_SIDE=1024。
|
# 默认 1024:大于 1024 的图压到 1024(画质/速度均衡),≤1024 的小图保持原分辨率重绘。
|
||||||
_REDRAW_MAX_SIDE = int(os.getenv("REDRAW_MAX_SIDE", "896"))
|
# 可用 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,
|
def _call_local_redraw(image_png_bytes, mask_png_bytes, timeout=300.0,
|
||||||
max_side=None, unet_name=None):
|
max_side=None, unet_name=None):
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -103,12 +103,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-group" id="redrawSideGroup">
|
<div class="form-group" id="redrawSideGroup">
|
||||||
<label>重绘分辨率</label>
|
<label>重绘分辨率</label>
|
||||||
<select id="redrawMaxSide" title="仅 Female 生效:ComfyUI 重绘前把送图长边压到该像素。值越小越快越省显存,细节越少;0=原图直送">
|
<select id="redrawMaxSide" title="仅 Female 生效:图长边超过该值才缩到该值,不超过则原图直送。默认1024=大于1024压到1024,小图保持原分辨率">
|
||||||
<option value="" selected>默认 896</option>
|
<option value="" selected>默认 1024(>1024 才缩)</option>
|
||||||
<option value="1024">1024(高清,慢)</option>
|
<option value="0">0 — 永不缩图(原图直送)</option>
|
||||||
|
<option value="1280">1280(更高清,慢)</option>
|
||||||
<option value="768">768(快)</option>
|
<option value="768">768(快)</option>
|
||||||
<option value="640">640(最快,糊)</option>
|
<option value="640">640(最快,糊)</option>
|
||||||
<option value="0">0 — 不缩图(原图直送)</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
|
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user