- 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/ 及运行期文件忽略规则
177 lines
7.7 KiB
Python
177 lines
7.7 KiB
Python
#!/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}")
|