批量测试:仅非高清(20脸×5发型=100张)+ 报告原图大图并排对比

- batch_grow_v2.py: HR_OPTIONS 仅保留非高清档,去掉高清
- gen_report_hairline_v2.py: 每张脸 grid 改为「原图大图 + 5发型」6列并排,
  原图不再是缩略图;去掉高清/非高清行标签和失败表高清列
This commit is contained in:
xsl
2026-07-12 00:02:42 +08:00
parent 1e6c2e54de
commit e17677158a
2 changed files with 36 additions and 45 deletions
+3 -3
View File
@@ -1,7 +1,7 @@
"""批量调用接口12/api/v1/hairline/grow_v2)生成对比素材。 """批量调用接口12/api/v1/hairline/grow_v2)生成对比素材。
20 张女生照片 × 5 种发际线发型 × {高清, 非高清} = 200 张输出。 20 张女生照片 × 5 种发际线发型(仅非高清= 100 张输出。
并发 4,失败的跳过并记录原因。结果图落盘到 static/report_hairline_v2/img/ 并发 2,失败的跳过并记录原因。结果图落盘到 static/report_hairline_v2/img/
元数据落盘 static/report_hairline_v2/results.json,供生成报告用。 元数据落盘 static/report_hairline_v2/results.json,供生成报告用。
用法: python scripts/batch_grow_v2.py 用法: python scripts/batch_grow_v2.py
@@ -31,7 +31,7 @@ HAIRSTYLES = [
("chang_xinxing", "心形"), ("chang_xinxing", "心形"),
("chang_huaban", "花瓣"), ("chang_huaban", "花瓣"),
] ]
HR_OPTIONS = [(True, "hr"), (False, "nohr")] HR_OPTIONS = [(False, "nohr")] # 仅非高清
def list_inputs(): def list_inputs():
+21 -30
View File
@@ -78,12 +78,11 @@ def main():
.face-head h2 {{ margin:0; font-size:18px; }} .face-head h2 {{ margin:0; font-size:18px; }}
.face-head .orig-thumb {{ width:64px; height:64px; object-fit:cover; .face-head .orig-thumb {{ width:64px; height:64px; object-fit:cover;
border-radius:8px; border:1px solid var(--border); }} border-radius:8px; border:1px solid var(--border); }}
.grid {{ display:grid; grid-template-columns:140px repeat({len(hairstyles)*2}, 1fr); .grid {{ display:grid; grid-template-columns:repeat({len(hairstyles)+1}, 1fr);
gap:8px; align-items:start; }} gap:10px; align-items:start; }}
.col-hdr {{ font-size:12px; color:var(--muted); text-align:center; padding:6px 4px; .col-hdr {{ font-size:12px; color:var(--muted); text-align:center; padding:6px 4px;
border-bottom:1px solid var(--border); }} border-bottom:1px solid var(--border); }}
.row-lbl {{ font-size:13px; color:var(--accent); padding:8px 4px; .col-hdr.orig {{ color:var(--accent); }}
border-right:1px solid var(--border); }}
.cell {{ position:relative; }} .cell {{ position:relative; }}
.cell img {{ width:100%; border-radius:6px; display:block; .cell img {{ width:100%; border-radius:6px; display:block;
border:1px solid var(--border); }} border:1px solid var(--border); }}
@@ -117,7 +116,7 @@ def main():
<body> <body>
<header> <header>
<h1>发际线生发对比报告 <span class="tag">接口12 · grow_v2</span></h1> <h1>发际线生发对比报告 <span class="tag">接口12 · grow_v2</span></h1>
<div class="sub">固定参数:blend_method=multiband · mb_levels=5 · erode_cm=0.6 · 生成后端=swaphair</div> <div class="sub">固定pushed 遮罩 + multiband 融合 · mb_levels=5 · erode_cm=0.6 · 非高清</div>
<div class="sub">生成时间:{html.escape(meta['generated_at'])} · 并发 {meta['concurrency']} · 耗时 {meta['elapsed_sec']}s</div> <div class="sub">生成时间:{html.escape(meta['generated_at'])} · 并发 {meta['concurrency']} · 耗时 {meta['elapsed_sec']}s</div>
<div class="stats"> <div class="stats">
<div class="stat"><div class="n">{meta['total']}</div><div class="l">总调用</div></div> <div class="stat"><div class="n">{meta['total']}</div><div class="l">总调用</div></div>
@@ -131,40 +130,34 @@ def main():
""") """)
# 图例 # 图例
parts.append('<div class="legend">每张脸横向为 5 发际线发型,每种发型分 ' parts.append('<div class="legend">每张脸:第一列为原图,其余 5 列为发际线生发结果(非高清)。点击任意图片可放大。</div>')
'<span class="tag">高清 is_hr=true</span>'
'<span class="tag no">非高清 is_hr=false</span> 两列。</div>')
# 每张脸一个区块 # 每张脸一个区块:原图 + 5 发型横向并排
for stem in faces: for stem in faces:
orig_rel = f"orig/{stem}.jpg" orig_rel = f"orig/{stem}.jpg"
parts.append('<div class="face-block">') parts.append('<div class="face-block">')
parts.append(f' <div class="face-head">' parts.append(f' <div class="face-head"><h2>{html.escape(stem)}</h2></div>')
f'<img class="orig-thumb zoomable" src="{url(orig_rel)}" ' # 网格:第一列原图,其余 5 列发型结果
f'data-full="{url(orig_rel)}" alt="原图 {html.escape(stem)}">'
f'<h2>{html.escape(stem)}</h2>'
f'</div>')
# 网格表头
parts.append('<div class="grid">') parts.append('<div class="grid">')
parts.append('<div class="col-hdr"></div>') # 表头
parts.append('<div class="col-hdr orig">原图</div>')
for h in hairstyles: for h in hairstyles:
parts.append(f'<div class="col-hdr" style="grid-column:span 2;">{html.escape(h["cn"])}<br><span style="opacity:.6">{html.escape(h["id"])}</span></div>') parts.append(f'<div class="col-hdr">{html.escape(h["cn"])}<br><span style="opacity:.6">{html.escape(h["id"])}</span></div>')
# 行:原图占满左侧第一列的高度由第一行承载;每行 = 一个 hr 选项 # 第一列:原图大图(可点击放大)
for hro in hr_opts: parts.append(
hr_tag = hro["tag"] f'<div class="cell"><img class="zoomable" src="{url(orig_rel)}" '
is_hr = hro["is_hr"] f'data-full="{url(orig_rel)}" loading="lazy" alt="原图 {html.escape(stem)}">'
tag_cls = "" if is_hr else " no" f'<div class="cap">原图</div></div>')
tag_txt = "高清" if is_hr else "非高清" # 5 发型结果(hr_opts 现在只有 nohr 一档,直接取)
parts.append(f'<div class="row-lbl">{tag_txt}<span class="tag{tag_cls}">{hr_tag}</span></div>') hr_tag = hr_opts[0]["tag"] if hr_opts else "nohr"
for h in hairstyles: for h in hairstyles:
r = idx.get((stem, h["id"], hr_tag)) r = idx.get((stem, h["id"], hr_tag))
if r and r["ok"]: if r and r["ok"]:
ms = r["ms"] ms = r["ms"]
cap = f"{html.escape(h['cn'])} · {tag_txt} · {ms}ms"
parts.append( parts.append(
f'<div class="cell"><img class="zoomable" src="{url(r["out"])}" ' f'<div class="cell"><img class="zoomable" src="{url(r["out"])}" '
f'data-full="{url(r["out"])}" loading="lazy" ' f'data-full="{url(r["out"])}" loading="lazy" '
f'alt="{html.escape(stem)} {html.escape(h["cn"])} {hr_tag}">' f'alt="{html.escape(stem)} {html.escape(h["cn"])}">'
f'<div class="cap">{ms}ms</div></div>') f'<div class="cap">{ms}ms</div></div>')
else: else:
err = (r or {}).get("error", "未执行") err = (r or {}).get("error", "未执行")
@@ -179,21 +172,19 @@ def main():
parts.append('<div class="face-block">') parts.append('<div class="face-block">')
parts.append(f'<h2>失败用例({len(fails)}</h2>') parts.append(f'<h2>失败用例({len(fails)}</h2>')
parts.append('<table class="fail-table"><thead><tr>' parts.append('<table class="fail-table"><thead><tr>'
'<th>人脸</th><th>发型</th><th>高清</th><th>耗时(ms)</th><th>原因</th>' '<th>人脸</th><th>发型</th><th>耗时(ms)</th><th>原因</th>'
'</tr></thead><tbody>') '</tr></thead><tbody>')
for r in fails: for r in fails:
parts.append( parts.append(
f'<tr><td>{html.escape(r["stem"])}</td>' f'<tr><td>{html.escape(r["stem"])}</td>'
f'<td>{html.escape(r["hair_cn"])}</td>' f'<td>{html.escape(r["hair_cn"])}</td>'
f'<td>{r["hr_tag"]}</td>'
f'<td>{r["ms"]}</td>' f'<td>{r["ms"]}</td>'
f'<td>{html.escape(r["error"] or "")}</td></tr>') f'<td>{html.escape(r["error"] or "")}</td></tr>')
parts.append('</tbody></table></div>') parts.append('</tbody></table></div>')
parts.append(f""" parts.append(f"""
<footer> <footer>
接口:<code>POST /api/v1/hairline/grow_v2</code> · 接口:<code>POST /api/v1/hairline/grow_v2</code> · 固定 pushed 遮罩 + multiband 融合 ·
固定 multiband / mb_levels=5 / erode_cm=0.6 ·
数据源 results.json · 点击任意图片可放大查看 数据源 results.json · 点击任意图片可放大查看
</footer> </footer>
</main> </main>