- test_interface2.html: 新增「重绘分辨率」下拉(默认896/1024/768/640/不缩图0), 仅 female 生效,append redraw_max_side 字段;male 时自动隐藏 - 新增分辨率对比测试工具 (image/compare_test, image/res_test): - batch_test.py: 串行批量测试脚本,支持断点续跑 - gen_report.py: 生成自包含 HTML 对比报告(速度色阶+缩略图+点击放大) - 两轮测试结果: - v1: 4图×5发型×2档=40次 (compare_test) - v2: 5图×3发型×5档=75次 (res_test) - 结论: 大图(长边>1600)原图直送比896档慢~3倍; 中小图各档差异小 - .gitignore: 忽略测试结果图/原图副本/运行日志(out/, *.log, progress.json)
205 lines
9.3 KiB
Python
205 lines
9.3 KiB
Python
#!/usr/bin/env python3
|
||
"""生成 v2 分辨率对比报告:每行=原图+5档,按图×发型组织15行。"""
|
||
import json, os, html
|
||
|
||
OUT = "/home/ubuntu/hair/image/res_test/out"
|
||
REPORT = os.path.join(OUT, "report.html")
|
||
results = json.load(open(os.path.join(OUT, "report.json")))
|
||
|
||
IMAGES = ["girl2", "girl5", "qwer", "asdf", "girl7"]
|
||
STYLES = [("flower", "花瓣"), ("heart", "心形"), ("wave", "波浪")]
|
||
SIDES = [("origin", "原图直送", 0), ("s1024", "1024", 1024),
|
||
("s896", "896", 896), ("s768", "768", 768), ("s640", "640", 640)]
|
||
LONGSIDE = {"girl2": 1082, "girl5": 767, "qwer": 1678, "asdf": 1666, "girl7": 925}
|
||
|
||
def get(img, style, side):
|
||
for r in results:
|
||
if r["img"] == img and r["style"] == style and r["side"] == side:
|
||
return r
|
||
return None
|
||
|
||
# 统计
|
||
total = len(results)
|
||
ok = sum(1 for r in results if r["ok"])
|
||
|
||
# 每档平均耗时(排除冷启动异常值:girl2_flower_origin=135s 明显是冷启动)
|
||
def avg(side, exclude_first_cold=False):
|
||
ts = []
|
||
for r in results:
|
||
if r["side"] == side and r["ok"]:
|
||
if exclude_first_cold and r["key"] == "girl2_flower_origin":
|
||
continue # 跳过冷启动
|
||
ts.append(r["elapsed"])
|
||
return sum(ts) / len(ts) if ts else 0
|
||
|
||
avg_origin = avg("origin", exclude_first_cold=True)
|
||
avg_1024 = avg("s1024")
|
||
avg_896 = avg("s896")
|
||
avg_768 = avg("s768")
|
||
avg_640 = avg("s640")
|
||
avgs = [("origin", "原图直送", avg_origin, 0),
|
||
("s1024", "1024", avg_1024, 1024),
|
||
("s896", "896", avg_896, 896),
|
||
("s768", "768", avg_768, 768),
|
||
("s640", "640", avg_640, 640)]
|
||
|
||
# 速度色阶:以全部耗时的 min-max 映射颜色(绿→黄→红)
|
||
all_t = sorted([r["elapsed"] for r in results if r["ok"] and r["key"] != "girl2_flower_origin"])
|
||
tmin, tmax = all_t[0], all_t[-1]
|
||
def speed_color(t):
|
||
if tmax == tmin:
|
||
return "#16a34a"
|
||
ratio = (t - tmin) / (tmax - tmin) # 0=最快(绿) 1=最慢(红)
|
||
if ratio < 0.33:
|
||
return "#16a34a" # 绿
|
||
elif ratio < 0.66:
|
||
return "#f59e0b" # 橙
|
||
else:
|
||
return "#dc2626" # 红
|
||
|
||
# 表格行
|
||
rows_html = []
|
||
for img in IMAGES:
|
||
for style_key, style_cn in STYLES:
|
||
# 原图格
|
||
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>')
|
||
# 5档格
|
||
side_cells = []
|
||
for side_name, side_lbl, side_val in SIDES:
|
||
r = get(img, style_key, 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> · '
|
||
f'{r["out_w"]}×{r["out_h"]}</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)
|
||
row_label = (f'<td class="rowlabel">'
|
||
f'<div class="rimg">{html.escape(img)}</div>'
|
||
f'<div class="rs">{html.escape(style_cn)}</div>'
|
||
f'<div class="rl">长边 {LONGSIDE[img]}</div></td>')
|
||
rows_html.append("<tr>" + row_label + orig_cell + "".join(side_cells) + "</tr>")
|
||
|
||
# 顶部平均耗时卡
|
||
def stat_card(lbl, val, col, sub=""):
|
||
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}{sub}</div></div>')
|
||
|
||
stat_cards = "".join([
|
||
stat_card("原图直送", avg_origin, "#dc2626", "<br><span class='dim'>排除冷启动</span>"),
|
||
stat_card("1024", avg_1024, "#f59e0b"),
|
||
stat_card("896 (默认)", avg_896, "#2563eb"),
|
||
stat_card("768", avg_768, "#16a34a"),
|
||
stat_card("640", avg_640, "#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>接口2 分辨率对比测试 v2</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; }}
|
||
.stat .dim {{ color: #9ca3af; font-size: 10px; }}
|
||
.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; }}
|
||
.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; }}
|
||
td.rowlabel {{ text-align: left; padding: 8px 12px; position: sticky; left: 0; background: #fff; z-index: 1; min-width: 90px; box-shadow: 2px 0 4px rgba(0,0,0,.04); }}
|
||
tbody tr:hover td.rowlabel {{ background: #f9fafb; }}
|
||
.rimg {{ font-weight: 700; font-size: 14px; }}
|
||
.rs {{ font-size: 12px; color: #6b7280; }}
|
||
.rl {{ font-size: 10px; color: #9ca3af; margin-top: 2px; }}
|
||
.cell {{ width: 180px; min-width: 180px; text-align: center; }}
|
||
.cell.orig {{ width: 180px; background: #fffbeb; }}
|
||
.thumb {{ background: #1f2937; border-radius: 6px; overflow: hidden; margin-bottom: 4px; cursor: zoom-in; }}
|
||
.thumb img {{ width: 100%; height: 240px; object-fit: contain; display: block; }}
|
||
.thumb.noimg {{ color: #d1d5db; font-size: 16px; padding: 100px 0; text-align: center; }}
|
||
.meta {{ font-size: 11px; color: #6b7280; }}
|
||
.meta.err {{ 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; }}
|
||
.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>💄 接口2 生发 · 5档分辨率对比报告</h1>
|
||
<p class="sub">POST /api/v1/hair/grow · female · 5 图 × 3 发型(花瓣/心形/波浪) × 5 档分辨率 = 75 次 · 串行 · 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>大图(qwer/asdf 长边~1670) 原图直送需 ~27-30s,是 896 档(10s) 的近 3 倍</b>;
|
||
中小图(girl2/girl5/girl7 长边 767-1082)各档差异较小(6-15s)。
|
||
<b>4090 24G 全程无 OOM</b>,75/75 成功。<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>图 · 发型</th>
|
||
<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}")
|