feat(接口2): 测试页暴露重绘分辨率选项 + 分辨率对比测试套件
- 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)
This commit is contained in:
+10
@@ -65,3 +65,13 @@ gateway.log
|
||||
|
||||
# 工作流备份文件(不入 git)
|
||||
*.json.bak.*
|
||||
|
||||
# 分辨率对比测试产物(out/ 下结果图+原图副本+日志,体积大,不入 git)
|
||||
# 仅忽略 out/ 与运行期文件;测试脚本与 HTML 报告仍入库
|
||||
image/compare_test/out/
|
||||
image/res_test/out/
|
||||
image/compare_test/progress.json
|
||||
image/res_test/progress.json
|
||||
image/compare_test/batch_test.log
|
||||
image/res_test/batch_test.log
|
||||
image/compare_test/http.log
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
=== 接口2 female 批量对比测试 ===
|
||||
矩阵: 4图 × 5发型 × 2档 = 40 次
|
||||
已跳过 0 个已完成项
|
||||
|
||||
[1/40] ▶ girl2_ellipse_default896 (side=None)... ✅ 10.8s 出图 1082x1081 (204589B)
|
||||
[2/40] ▶ girl2_ellipse_origin0 (side=0)... ✅ 13.3s 出图 1088x1088 (232252B)
|
||||
[3/40] ▶ girl2_flower_default896 (side=None)... ✅ 11.0s 出图 1082x1081 (210875B)
|
||||
[4/40] ▶ girl2_flower_origin0 (side=0)... ✅ 12.5s 出图 1088x1088 (236073B)
|
||||
[5/40] ▶ girl2_heart_default896 (side=None)... ✅ 11.9s 出图 1082x1081 (198911B)
|
||||
[6/40] ▶ girl2_heart_origin0 (side=0)... ✅ 12.2s 出图 1088x1088 (249893B)
|
||||
[7/40] ▶ girl2_straight_default896 (side=None)... ✅ 12.1s 出图 1082x1081 (211803B)
|
||||
[8/40] ▶ girl2_straight_origin0 (side=0)... ✅ 12.3s 出图 1088x1088 (235377B)
|
||||
[9/40] ▶ girl2_wave_default896 (side=None)... ✅ 12.7s 出图 1082x1081 (207736B)
|
||||
[10/40] ▶ girl2_wave_origin0 (side=0)... ✅ 11.9s 出图 1088x1088 (238081B)
|
||||
[11/40] ▶ girl5_ellipse_default896 (side=None)... ✅ 9.6s 出图 768x752 (105867B)
|
||||
[12/40] ▶ girl5_ellipse_origin0 (side=0)... ✅ 7.3s 出图 768x752 (112963B)
|
||||
[13/40] ▶ girl5_flower_default896 (side=None)... ✅ 8.0s 出图 768x752 (114378B)
|
||||
[14/40] ▶ girl5_flower_origin0 (side=0)... ✅ 7.2s 出图 768x752 (108495B)
|
||||
[15/40] ▶ girl5_heart_default896 (side=None)... ✅ 8.1s 出图 768x752 (113874B)
|
||||
[16/40] ▶ girl5_heart_origin0 (side=0)... ✅ 7.6s 出图 768x752 (103160B)
|
||||
[17/40] ▶ girl5_straight_default896 (side=None)... ✅ 8.4s 出图 768x752 (104492B)
|
||||
[18/40] ▶ girl5_straight_origin0 (side=0)... ✅ 7.2s 出图 768x752 (100941B)
|
||||
[19/40] ▶ girl5_wave_default896 (side=None)... ✅ 8.4s 出图 768x752 (120122B)
|
||||
[20/40] ▶ girl5_wave_origin0 (side=0)... ✅ 7.7s 出图 768x752 (113282B)
|
||||
[21/40] ▶ qwer_ellipse_default896 (side=None)... ✅ 10.2s 出图 1288x1678 (261812B)
|
||||
[22/40] ▶ qwer_ellipse_origin0 (side=0)... ✅ 30.0s 出图 1280x1680 (331013B)
|
||||
[23/40] ▶ qwer_flower_default896 (side=None)... ✅ 10.2s 出图 1288x1678 (312834B)
|
||||
[24/40] ▶ qwer_flower_origin0 (side=0)... ✅ 25.0s 出图 1280x1680 (328605B)
|
||||
[25/40] ▶ qwer_heart_default896 (side=None)... ✅ 11.1s 出图 1288x1678 (331337B)
|
||||
[26/40] ▶ qwer_heart_origin0 (side=0)... ✅ 25.0s 出图 1280x1680 (355982B)
|
||||
[27/40] ▶ qwer_straight_default896 (side=None)... ✅ 10.7s 出图 1288x1678 (281665B)
|
||||
[28/40] ▶ qwer_straight_origin0 (side=0)... ✅ 23.4s 出图 1280x1680 (322183B)
|
||||
[29/40] ▶ qwer_wave_default896 (side=None)... ✅ 11.1s 出图 1288x1678 (289322B)
|
||||
[30/40] ▶ qwer_wave_origin0 (side=0)... ✅ 25.8s 出图 1280x1680 (351719B)
|
||||
[31/40] ▶ asdf_ellipse_default896 (side=None)... ✅ 11.2s 出图 1254x1666 (285362B)
|
||||
[32/40] ▶ asdf_ellipse_origin0 (side=0)... ✅ 25.7s 出图 1248x1664 (286768B)
|
||||
[33/40] ▶ asdf_flower_default896 (side=None)... ✅ 10.7s 出图 1254x1666 (266946B)
|
||||
[34/40] ▶ asdf_flower_origin0 (side=0)... ✅ 22.9s 出图 1248x1664 (353668B)
|
||||
[35/40] ▶ asdf_heart_default896 (side=None)... ✅ 10.4s 出图 1254x1666 (271975B)
|
||||
[36/40] ▶ asdf_heart_origin0 (side=0)... ✅ 22.9s 出图 1248x1664 (291983B)
|
||||
[37/40] ▶ asdf_straight_default896 (side=None)... ✅ 9.8s 出图 1254x1666 (258665B)
|
||||
[38/40] ▶ asdf_straight_origin0 (side=0)... ✅ 24.0s 出图 1248x1664 (279883B)
|
||||
[39/40] ▶ asdf_wave_default896 (side=None)... ✅ 11.9s 出图 1254x1666 (286515B)
|
||||
[40/40] ▶ asdf_wave_origin0 (side=0)... ✅ 24.3s 出图 1248x1664 (295694B)
|
||||
|
||||
======================================================================
|
||||
汇总报告
|
||||
======================================================================
|
||||
|
||||
--- 速度对比(秒,✓=成功 ✗=失败)---
|
||||
图 发型 默认896 原图0 差值
|
||||
girl2 ellipse 10.8✓ 13.3✓ +2.5
|
||||
girl2 flower 11.0✓ 12.5✓ +1.5
|
||||
girl2 heart 11.9✓ 12.2✓ +0.3
|
||||
girl2 straight 12.1✓ 12.3✓ +0.2
|
||||
girl2 wave 12.7✓ 11.9✓ -0.8
|
||||
girl5 ellipse 9.6✓ 7.3✓ -2.3
|
||||
girl5 flower 8.0✓ 7.2✓ -0.8
|
||||
girl5 heart 8.1✓ 7.6✓ -0.5
|
||||
girl5 straight 8.4✓ 7.2✓ -1.2
|
||||
girl5 wave 8.4✓ 7.7✓ -0.7
|
||||
qwer ellipse 10.2✓ 30.0✓ +19.8
|
||||
qwer flower 10.2✓ 25.0✓ +14.8
|
||||
qwer heart 11.1✓ 25.0✓ +13.9
|
||||
qwer straight 10.7✓ 23.4✓ +12.7
|
||||
qwer wave 11.1✓ 25.8✓ +14.7
|
||||
asdf ellipse 11.2✓ 25.7✓ +14.5
|
||||
asdf flower 10.7✓ 22.9✓ +12.2
|
||||
asdf heart 10.4✓ 22.9✓ +12.5
|
||||
asdf straight 9.8✓ 24.0✓ +14.2
|
||||
asdf wave 11.9✓ 24.3✓ +12.4
|
||||
|
||||
结果图: /home/ubuntu/hair/image/compare_test/out/
|
||||
CSV报告: /home/ubuntu/hair/image/compare_test/out/report.csv
|
||||
JSON明细: /home/ubuntu/hair/image/compare_test/out/report.json
|
||||
@@ -0,0 +1,41 @@
|
||||
img,style,side,ok,elapsed_s,out_w,out_h,err
|
||||
girl2,ellipse,default896,True,10.8,1082,1081,
|
||||
girl2,ellipse,origin0,True,13.3,1088,1088,
|
||||
girl2,flower,default896,True,11.0,1082,1081,
|
||||
girl2,flower,origin0,True,12.5,1088,1088,
|
||||
girl2,heart,default896,True,11.9,1082,1081,
|
||||
girl2,heart,origin0,True,12.2,1088,1088,
|
||||
girl2,straight,default896,True,12.1,1082,1081,
|
||||
girl2,straight,origin0,True,12.3,1088,1088,
|
||||
girl2,wave,default896,True,12.7,1082,1081,
|
||||
girl2,wave,origin0,True,11.9,1088,1088,
|
||||
girl5,ellipse,default896,True,9.6,768,752,
|
||||
girl5,ellipse,origin0,True,7.3,768,752,
|
||||
girl5,flower,default896,True,8.0,768,752,
|
||||
girl5,flower,origin0,True,7.2,768,752,
|
||||
girl5,heart,default896,True,8.1,768,752,
|
||||
girl5,heart,origin0,True,7.6,768,752,
|
||||
girl5,straight,default896,True,8.4,768,752,
|
||||
girl5,straight,origin0,True,7.2,768,752,
|
||||
girl5,wave,default896,True,8.4,768,752,
|
||||
girl5,wave,origin0,True,7.7,768,752,
|
||||
qwer,ellipse,default896,True,10.2,1288,1678,
|
||||
qwer,ellipse,origin0,True,30.0,1280,1680,
|
||||
qwer,flower,default896,True,10.2,1288,1678,
|
||||
qwer,flower,origin0,True,25.0,1280,1680,
|
||||
qwer,heart,default896,True,11.1,1288,1678,
|
||||
qwer,heart,origin0,True,25.0,1280,1680,
|
||||
qwer,straight,default896,True,10.7,1288,1678,
|
||||
qwer,straight,origin0,True,23.4,1280,1680,
|
||||
qwer,wave,default896,True,11.1,1288,1678,
|
||||
qwer,wave,origin0,True,25.8,1280,1680,
|
||||
asdf,ellipse,default896,True,11.2,1254,1666,
|
||||
asdf,ellipse,origin0,True,25.7,1248,1664,
|
||||
asdf,flower,default896,True,10.7,1254,1666,
|
||||
asdf,flower,origin0,True,22.9,1248,1664,
|
||||
asdf,heart,default896,True,10.4,1254,1666,
|
||||
asdf,heart,origin0,True,22.9,1248,1664,
|
||||
asdf,straight,default896,True,9.8,1254,1666,
|
||||
asdf,straight,origin0,True,24.0,1248,1664,
|
||||
asdf,wave,default896,True,11.9,1254,1666,
|
||||
asdf,wave,origin0,True,24.3,1248,1664,
|
||||
|
File diff suppressed because one or more lines are too long
@@ -0,0 +1,562 @@
|
||||
[
|
||||
{
|
||||
"key": "girl2_ellipse_default896",
|
||||
"img": "girl2",
|
||||
"style": "ellipse",
|
||||
"style_idx": 1,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 10.8,
|
||||
"err": "",
|
||||
"out_w": 1082,
|
||||
"out_h": 1081,
|
||||
"bytes": 204589
|
||||
},
|
||||
{
|
||||
"key": "girl2_ellipse_origin0",
|
||||
"img": "girl2",
|
||||
"style": "ellipse",
|
||||
"style_idx": 1,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 13.3,
|
||||
"err": "",
|
||||
"out_w": 1088,
|
||||
"out_h": 1088,
|
||||
"bytes": 232252
|
||||
},
|
||||
{
|
||||
"key": "girl2_flower_default896",
|
||||
"img": "girl2",
|
||||
"style": "flower",
|
||||
"style_idx": 2,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 11.0,
|
||||
"err": "",
|
||||
"out_w": 1082,
|
||||
"out_h": 1081,
|
||||
"bytes": 210875
|
||||
},
|
||||
{
|
||||
"key": "girl2_flower_origin0",
|
||||
"img": "girl2",
|
||||
"style": "flower",
|
||||
"style_idx": 2,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 12.5,
|
||||
"err": "",
|
||||
"out_w": 1088,
|
||||
"out_h": 1088,
|
||||
"bytes": 236073
|
||||
},
|
||||
{
|
||||
"key": "girl2_heart_default896",
|
||||
"img": "girl2",
|
||||
"style": "heart",
|
||||
"style_idx": 3,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 11.9,
|
||||
"err": "",
|
||||
"out_w": 1082,
|
||||
"out_h": 1081,
|
||||
"bytes": 198911
|
||||
},
|
||||
{
|
||||
"key": "girl2_heart_origin0",
|
||||
"img": "girl2",
|
||||
"style": "heart",
|
||||
"style_idx": 3,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 12.2,
|
||||
"err": "",
|
||||
"out_w": 1088,
|
||||
"out_h": 1088,
|
||||
"bytes": 249893
|
||||
},
|
||||
{
|
||||
"key": "girl2_straight_default896",
|
||||
"img": "girl2",
|
||||
"style": "straight",
|
||||
"style_idx": 4,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 12.1,
|
||||
"err": "",
|
||||
"out_w": 1082,
|
||||
"out_h": 1081,
|
||||
"bytes": 211803
|
||||
},
|
||||
{
|
||||
"key": "girl2_straight_origin0",
|
||||
"img": "girl2",
|
||||
"style": "straight",
|
||||
"style_idx": 4,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 12.3,
|
||||
"err": "",
|
||||
"out_w": 1088,
|
||||
"out_h": 1088,
|
||||
"bytes": 235377
|
||||
},
|
||||
{
|
||||
"key": "girl2_wave_default896",
|
||||
"img": "girl2",
|
||||
"style": "wave",
|
||||
"style_idx": 5,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 12.7,
|
||||
"err": "",
|
||||
"out_w": 1082,
|
||||
"out_h": 1081,
|
||||
"bytes": 207736
|
||||
},
|
||||
{
|
||||
"key": "girl2_wave_origin0",
|
||||
"img": "girl2",
|
||||
"style": "wave",
|
||||
"style_idx": 5,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 11.9,
|
||||
"err": "",
|
||||
"out_w": 1088,
|
||||
"out_h": 1088,
|
||||
"bytes": 238081
|
||||
},
|
||||
{
|
||||
"key": "girl5_ellipse_default896",
|
||||
"img": "girl5",
|
||||
"style": "ellipse",
|
||||
"style_idx": 1,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 9.6,
|
||||
"err": "",
|
||||
"out_w": 768,
|
||||
"out_h": 752,
|
||||
"bytes": 105867
|
||||
},
|
||||
{
|
||||
"key": "girl5_ellipse_origin0",
|
||||
"img": "girl5",
|
||||
"style": "ellipse",
|
||||
"style_idx": 1,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 7.3,
|
||||
"err": "",
|
||||
"out_w": 768,
|
||||
"out_h": 752,
|
||||
"bytes": 112963
|
||||
},
|
||||
{
|
||||
"key": "girl5_flower_default896",
|
||||
"img": "girl5",
|
||||
"style": "flower",
|
||||
"style_idx": 2,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 8.0,
|
||||
"err": "",
|
||||
"out_w": 768,
|
||||
"out_h": 752,
|
||||
"bytes": 114378
|
||||
},
|
||||
{
|
||||
"key": "girl5_flower_origin0",
|
||||
"img": "girl5",
|
||||
"style": "flower",
|
||||
"style_idx": 2,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 7.2,
|
||||
"err": "",
|
||||
"out_w": 768,
|
||||
"out_h": 752,
|
||||
"bytes": 108495
|
||||
},
|
||||
{
|
||||
"key": "girl5_heart_default896",
|
||||
"img": "girl5",
|
||||
"style": "heart",
|
||||
"style_idx": 3,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 8.1,
|
||||
"err": "",
|
||||
"out_w": 768,
|
||||
"out_h": 752,
|
||||
"bytes": 113874
|
||||
},
|
||||
{
|
||||
"key": "girl5_heart_origin0",
|
||||
"img": "girl5",
|
||||
"style": "heart",
|
||||
"style_idx": 3,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 7.6,
|
||||
"err": "",
|
||||
"out_w": 768,
|
||||
"out_h": 752,
|
||||
"bytes": 103160
|
||||
},
|
||||
{
|
||||
"key": "girl5_straight_default896",
|
||||
"img": "girl5",
|
||||
"style": "straight",
|
||||
"style_idx": 4,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 8.4,
|
||||
"err": "",
|
||||
"out_w": 768,
|
||||
"out_h": 752,
|
||||
"bytes": 104492
|
||||
},
|
||||
{
|
||||
"key": "girl5_straight_origin0",
|
||||
"img": "girl5",
|
||||
"style": "straight",
|
||||
"style_idx": 4,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 7.2,
|
||||
"err": "",
|
||||
"out_w": 768,
|
||||
"out_h": 752,
|
||||
"bytes": 100941
|
||||
},
|
||||
{
|
||||
"key": "girl5_wave_default896",
|
||||
"img": "girl5",
|
||||
"style": "wave",
|
||||
"style_idx": 5,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 8.4,
|
||||
"err": "",
|
||||
"out_w": 768,
|
||||
"out_h": 752,
|
||||
"bytes": 120122
|
||||
},
|
||||
{
|
||||
"key": "girl5_wave_origin0",
|
||||
"img": "girl5",
|
||||
"style": "wave",
|
||||
"style_idx": 5,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 7.7,
|
||||
"err": "",
|
||||
"out_w": 768,
|
||||
"out_h": 752,
|
||||
"bytes": 113282
|
||||
},
|
||||
{
|
||||
"key": "qwer_ellipse_default896",
|
||||
"img": "qwer",
|
||||
"style": "ellipse",
|
||||
"style_idx": 1,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 10.2,
|
||||
"err": "",
|
||||
"out_w": 1288,
|
||||
"out_h": 1678,
|
||||
"bytes": 261812
|
||||
},
|
||||
{
|
||||
"key": "qwer_ellipse_origin0",
|
||||
"img": "qwer",
|
||||
"style": "ellipse",
|
||||
"style_idx": 1,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 30.0,
|
||||
"err": "",
|
||||
"out_w": 1280,
|
||||
"out_h": 1680,
|
||||
"bytes": 331013
|
||||
},
|
||||
{
|
||||
"key": "qwer_flower_default896",
|
||||
"img": "qwer",
|
||||
"style": "flower",
|
||||
"style_idx": 2,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 10.2,
|
||||
"err": "",
|
||||
"out_w": 1288,
|
||||
"out_h": 1678,
|
||||
"bytes": 312834
|
||||
},
|
||||
{
|
||||
"key": "qwer_flower_origin0",
|
||||
"img": "qwer",
|
||||
"style": "flower",
|
||||
"style_idx": 2,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 25.0,
|
||||
"err": "",
|
||||
"out_w": 1280,
|
||||
"out_h": 1680,
|
||||
"bytes": 328605
|
||||
},
|
||||
{
|
||||
"key": "qwer_heart_default896",
|
||||
"img": "qwer",
|
||||
"style": "heart",
|
||||
"style_idx": 3,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 11.1,
|
||||
"err": "",
|
||||
"out_w": 1288,
|
||||
"out_h": 1678,
|
||||
"bytes": 331337
|
||||
},
|
||||
{
|
||||
"key": "qwer_heart_origin0",
|
||||
"img": "qwer",
|
||||
"style": "heart",
|
||||
"style_idx": 3,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 25.0,
|
||||
"err": "",
|
||||
"out_w": 1280,
|
||||
"out_h": 1680,
|
||||
"bytes": 355982
|
||||
},
|
||||
{
|
||||
"key": "qwer_straight_default896",
|
||||
"img": "qwer",
|
||||
"style": "straight",
|
||||
"style_idx": 4,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 10.7,
|
||||
"err": "",
|
||||
"out_w": 1288,
|
||||
"out_h": 1678,
|
||||
"bytes": 281665
|
||||
},
|
||||
{
|
||||
"key": "qwer_straight_origin0",
|
||||
"img": "qwer",
|
||||
"style": "straight",
|
||||
"style_idx": 4,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 23.4,
|
||||
"err": "",
|
||||
"out_w": 1280,
|
||||
"out_h": 1680,
|
||||
"bytes": 322183
|
||||
},
|
||||
{
|
||||
"key": "qwer_wave_default896",
|
||||
"img": "qwer",
|
||||
"style": "wave",
|
||||
"style_idx": 5,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 11.1,
|
||||
"err": "",
|
||||
"out_w": 1288,
|
||||
"out_h": 1678,
|
||||
"bytes": 289322
|
||||
},
|
||||
{
|
||||
"key": "qwer_wave_origin0",
|
||||
"img": "qwer",
|
||||
"style": "wave",
|
||||
"style_idx": 5,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 25.8,
|
||||
"err": "",
|
||||
"out_w": 1280,
|
||||
"out_h": 1680,
|
||||
"bytes": 351719
|
||||
},
|
||||
{
|
||||
"key": "asdf_ellipse_default896",
|
||||
"img": "asdf",
|
||||
"style": "ellipse",
|
||||
"style_idx": 1,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 11.2,
|
||||
"err": "",
|
||||
"out_w": 1254,
|
||||
"out_h": 1666,
|
||||
"bytes": 285362
|
||||
},
|
||||
{
|
||||
"key": "asdf_ellipse_origin0",
|
||||
"img": "asdf",
|
||||
"style": "ellipse",
|
||||
"style_idx": 1,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 25.7,
|
||||
"err": "",
|
||||
"out_w": 1248,
|
||||
"out_h": 1664,
|
||||
"bytes": 286768
|
||||
},
|
||||
{
|
||||
"key": "asdf_flower_default896",
|
||||
"img": "asdf",
|
||||
"style": "flower",
|
||||
"style_idx": 2,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 10.7,
|
||||
"err": "",
|
||||
"out_w": 1254,
|
||||
"out_h": 1666,
|
||||
"bytes": 266946
|
||||
},
|
||||
{
|
||||
"key": "asdf_flower_origin0",
|
||||
"img": "asdf",
|
||||
"style": "flower",
|
||||
"style_idx": 2,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 22.9,
|
||||
"err": "",
|
||||
"out_w": 1248,
|
||||
"out_h": 1664,
|
||||
"bytes": 353668
|
||||
},
|
||||
{
|
||||
"key": "asdf_heart_default896",
|
||||
"img": "asdf",
|
||||
"style": "heart",
|
||||
"style_idx": 3,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 10.4,
|
||||
"err": "",
|
||||
"out_w": 1254,
|
||||
"out_h": 1666,
|
||||
"bytes": 271975
|
||||
},
|
||||
{
|
||||
"key": "asdf_heart_origin0",
|
||||
"img": "asdf",
|
||||
"style": "heart",
|
||||
"style_idx": 3,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 22.9,
|
||||
"err": "",
|
||||
"out_w": 1248,
|
||||
"out_h": 1664,
|
||||
"bytes": 291983
|
||||
},
|
||||
{
|
||||
"key": "asdf_straight_default896",
|
||||
"img": "asdf",
|
||||
"style": "straight",
|
||||
"style_idx": 4,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 9.8,
|
||||
"err": "",
|
||||
"out_w": 1254,
|
||||
"out_h": 1666,
|
||||
"bytes": 258665
|
||||
},
|
||||
{
|
||||
"key": "asdf_straight_origin0",
|
||||
"img": "asdf",
|
||||
"style": "straight",
|
||||
"style_idx": 4,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 24.0,
|
||||
"err": "",
|
||||
"out_w": 1248,
|
||||
"out_h": 1664,
|
||||
"bytes": 279883
|
||||
},
|
||||
{
|
||||
"key": "asdf_wave_default896",
|
||||
"img": "asdf",
|
||||
"style": "wave",
|
||||
"style_idx": 5,
|
||||
"side": "default896",
|
||||
"side_val": null,
|
||||
"ok": true,
|
||||
"elapsed": 11.9,
|
||||
"err": "",
|
||||
"out_w": 1254,
|
||||
"out_h": 1666,
|
||||
"bytes": 286515
|
||||
},
|
||||
{
|
||||
"key": "asdf_wave_origin0",
|
||||
"img": "asdf",
|
||||
"style": "wave",
|
||||
"style_idx": 5,
|
||||
"side": "origin0",
|
||||
"side_val": 0,
|
||||
"ok": true,
|
||||
"elapsed": 24.3,
|
||||
"err": "",
|
||||
"out_w": 1248,
|
||||
"out_h": 1664,
|
||||
"bytes": 295694
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""接口2 female 批量对比测试:4图 × 5发型 × 3分辨率档位 = 60次请求。
|
||||
串行执行,记录耗时与成败,结果图按 图_发型_档位 命名保存。
|
||||
用法: python3 batch_test.py
|
||||
支持断点续跑(progress.json);新增档位时只会补跑未完成项。
|
||||
"""
|
||||
import base64, csv, json, os, sys, time, traceback
|
||||
import requests
|
||||
|
||||
API = "http://127.0.0.1:8187/api/v1/hair/grow"
|
||||
TOKEN = "dev-shared-secret-2026"
|
||||
TIMEOUT = 600
|
||||
OUT = "/home/ubuntu/hair/image/compare_test/out"
|
||||
PROGRESS = "/home/ubuntu/hair/image/compare_test/progress.json"
|
||||
|
||||
IMG_DIR = "/home/ubuntu/hair/image"
|
||||
IMAGES = [
|
||||
("girl2", f"{IMG_DIR}/girl_img/girl2.jpg"),
|
||||
("girl5", f"{IMG_DIR}/girl_img/girl5.jpg"),
|
||||
("qwer", f"{IMG_DIR}/qwer.jpg"),
|
||||
("asdf", f"{IMG_DIR}/asdf.jpg"),
|
||||
]
|
||||
# female: 1=ellipse 2=flower 3=heart 4=straight 5=wave
|
||||
STYLES = [
|
||||
(1, "ellipse"), (2, "flower"), (3, "heart"), (4, "straight"), (5, "wave"),
|
||||
]
|
||||
# 三档: 默认896 / 显式1024 / 原图直送0
|
||||
SIDES = [
|
||||
("default896", None), # 不传 → 后端默认896
|
||||
("side1024", 1024), # 长边压到 1024
|
||||
("origin0", 0), # 原图直送
|
||||
]
|
||||
SIDE_LABEL = {
|
||||
"default896": "默认896",
|
||||
"side1024": "1024",
|
||||
"origin0": "原图0",
|
||||
}
|
||||
|
||||
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, style_idx, style_key, side_name, side_val):
|
||||
"""跑单次请求,返回 dict 结果。"""
|
||||
key = f"{img_name}_{style_key}_{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),
|
||||
}
|
||||
if side_val is not None:
|
||||
data["redraw_max_side"] = str(side_val)
|
||||
t0 = time.time()
|
||||
rec = {"key": key, "img": img_name, "style": style_key, "style_idx": style_idx,
|
||||
"side": side_name, "side_val": side_val, "ok": False,
|
||||
"elapsed": 0.0, "err": "", "out_w": 0, "out_h": 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"] = "无生发图(grown_png=None, 重绘失败)"
|
||||
return rec
|
||||
raw = base64.b64decode(grown)
|
||||
from PIL import Image
|
||||
import io as _io
|
||||
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():
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
prog = load_progress()
|
||||
done_keys = set(prog["done"])
|
||||
total = len(IMAGES) * len(STYLES) * len(SIDES)
|
||||
print(f"=== 接口2 female 批量对比测试 ===")
|
||||
print(f"矩阵: {len(IMAGES)}图 × {len(STYLES)}发型 × {len(SIDES)}档 = {total} 次")
|
||||
print(f"已跳过 {len(done_keys)} 个已完成项\n")
|
||||
|
||||
idx = 0
|
||||
for img_name, img_path in IMAGES:
|
||||
for style_idx, style_key in STYLES:
|
||||
for side_name, side_val in SIDES:
|
||||
idx += 1
|
||||
key = f"{img_name}_{style_key}_{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, style_idx, style_key, 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")
|
||||
print(f"JSON明细: {OUT}/report.json")
|
||||
|
||||
def write_report(results):
|
||||
# CSV
|
||||
csv_path = f"{OUT}/report.csv"
|
||||
with open(csv_path, "w", newline="") as f:
|
||||
w = csv.writer(f)
|
||||
w.writerow(["img","style","side","ok","elapsed_s","out_w","out_h","err"])
|
||||
for r in results:
|
||||
w.writerow([r["img"],r["style"],r["side"],r["ok"],r["elapsed"],
|
||||
r["out_w"],r["out_h"],r["err"]])
|
||||
# JSON
|
||||
json.dump(results, open(f"{OUT}/report.json","w"), ensure_ascii=False, indent=1)
|
||||
# 控制台速度对比表
|
||||
print("\n--- 速度对比(秒,✓=成功 ✗=失败)---")
|
||||
headers = ["图", "发型"] + [SIDE_LABEL[s] for s, _ in SIDES]
|
||||
print(f"{headers[0]:<8}{headers[1]:<10}" + "".join(f"{h:>12}" for h in headers[2:]))
|
||||
for img_name, _ in IMAGES:
|
||||
for _, style_key in STYLES:
|
||||
cells = []
|
||||
for side_name, _ in SIDES:
|
||||
r = next((x for x in results if x["img"]==img_name and x["style"]==style_key and x["side"]==side_name), None)
|
||||
cells.append(f"{r['elapsed']}{'✓' if r['ok'] else '✗'}" if r else "-")
|
||||
print(f"{img_name:<8}{style_key:<10}" + "".join(f"{c:>12}" for c in cells))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,244 @@
|
||||
#!/usr/bin/env python3
|
||||
"""把 report.json + 结果图生成为自包含 HTML 报告(支持 896 / 1024 / 原图0 三档)。"""
|
||||
import json, os, html
|
||||
|
||||
OUT = "/home/ubuntu/hair/image/compare_test/out"
|
||||
REPORT_HTML = os.path.join(OUT, "report.html")
|
||||
results = json.load(open(os.path.join(OUT, "report.json")))
|
||||
|
||||
IMAGES = ["girl2", "girl5", "qwer", "asdf"]
|
||||
STYLES = ["ellipse", "flower", "heart", "straight", "wave"]
|
||||
SIDES = [
|
||||
("default896", "默认 896", "#2563eb", "d"),
|
||||
("side1024", "1024", "#10b981", "m"),
|
||||
("origin0", "原图直送 0", "#f59e0b", "o"),
|
||||
]
|
||||
IMG_LONGSIDE = {"girl2": 1082, "girl5": 767, "qwer": 1678, "asdf": 1666}
|
||||
STYLE_CN = {"ellipse": "椭圆", "flower": "花瓣", "heart": "心形", "straight": "直线", "wave": "波浪"}
|
||||
|
||||
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
|
||||
|
||||
# 总览统计(>60s 视为冷启动/异常,不进均值)
|
||||
total = len(results)
|
||||
ok = sum(1 for r in results if r["ok"])
|
||||
COLD_S = 60.0
|
||||
|
||||
def avg(seq):
|
||||
seq = [x for x in seq if x is not None]
|
||||
return sum(seq) / len(seq) if seq else 0
|
||||
|
||||
def times(side, img=None):
|
||||
out = []
|
||||
for r in results:
|
||||
if not r["ok"] or r["side"] != side:
|
||||
continue
|
||||
if img is not None and r["img"] != img:
|
||||
continue
|
||||
if r["elapsed"] >= COLD_S:
|
||||
continue
|
||||
out.append(r["elapsed"])
|
||||
return out
|
||||
|
||||
cold_n = sum(1 for r in results if r["ok"] and r["elapsed"] >= COLD_S)
|
||||
side_avgs = {sid: avg(times(sid)) for sid, *_ in SIDES}
|
||||
all_times = [t for sid, *_ in SIDES for t in times(sid)]
|
||||
max_bar = max(all_times + [1])
|
||||
|
||||
# 构造速度对比图数据:每图 × 三档
|
||||
chart_rows = []
|
||||
for img in IMAGES:
|
||||
chart_rows.append((img, IMG_LONGSIDE[img], [avg(times(sid, img)) for sid, *_ in SIDES]))
|
||||
|
||||
def bar_svg():
|
||||
bar_h = 22
|
||||
gap = 12
|
||||
label_w = 78
|
||||
chart_w = 760
|
||||
n_bars = len(SIDES)
|
||||
rows = len(chart_rows)
|
||||
h = rows * (bar_h * n_bars + gap) + 40
|
||||
parts = [f'<svg viewBox="0 0 {label_w + chart_w + 80} {h}" class="chart">']
|
||||
y = 10
|
||||
for img, longside, avgs in chart_rows:
|
||||
base = avgs[0] if avgs and avgs[0] else 1
|
||||
for i, ((sid, label, color, _), a) in enumerate(zip(SIDES, avgs)):
|
||||
yi = y + i * bar_h
|
||||
w = int(a / max_bar * chart_w) if a else 0
|
||||
warn = "⚠" if (i > 0 and a > base * 1.8) else ""
|
||||
parts.append(f'<rect x="{label_w}" y="{yi}" width="{w}" height="{bar_h-4}" rx="3" fill="{color}"/>')
|
||||
parts.append(f'<text x="{label_w + w + 6}" y="{yi + bar_h - 10}" class="barlabel">{a:.1f}s {warn}</text>')
|
||||
parts.append(f'<text x="{label_w-8}" y="{yi + bar_h - 10}" class="rowlabel" text-anchor="end">{html.escape(label)}</text>')
|
||||
parts.append(f'<text x="0" y="{y + bar_h - 2}" class="imglabel">{img}<tspan class="imgside">长边{longside}</tspan></text>')
|
||||
y += bar_h * n_bars + gap
|
||||
parts.append("</svg>")
|
||||
return "".join(parts)
|
||||
|
||||
def img_cell(r, cls):
|
||||
if not r:
|
||||
return f'<td class="{cls} fail">缺失</td>'
|
||||
fname = r["key"] + ".jpg"
|
||||
status = "✅" if r["ok"] else "❌"
|
||||
t = f'{r["elapsed"]}s'
|
||||
dim = f'{r["out_w"]}×{r["out_h"]}'
|
||||
err = f'<div class="err">{html.escape(r["err"])}</div>' if r["err"] else ""
|
||||
img_tag = (f'<img loading="lazy" src="{fname}" onclick="openImg(this.src)" alt="{html.escape(r["key"])}">'
|
||||
if r["ok"] else '<div class="noimg">无图</div>')
|
||||
return (f'<td class="{cls}"><div class="thumb">{img_tag}</div>'
|
||||
f'<div class="meta">{status} {t} · {dim}</div>{err}</td>')
|
||||
|
||||
def compare_cards():
|
||||
out = []
|
||||
for img in IMAGES:
|
||||
out.append(f'<div class="card"><div class="card-h">📷 {html.escape(img)} <span class="tag">原图长边 {IMG_LONGSIDE[img]}px</span></div><div class="card-b">')
|
||||
heads = "".join(f'<th class="{cls}">{html.escape(label)}</th>' for _, label, _, cls in SIDES)
|
||||
out.append(f'<table class="cmp"><thead><tr><th>发型</th>{heads}</tr></thead><tbody>')
|
||||
for style in STYLES:
|
||||
sc = STYLE_CN[style]
|
||||
cells = "".join(img_cell(get(img, style, sid), cls) for sid, _, _, cls in SIDES)
|
||||
out.append(f'<tr><td class="sname">{html.escape(style)}<span>{sc}</span></td>{cells}</tr>')
|
||||
out.append("</tbody></table></div></div>")
|
||||
return "".join(out)
|
||||
|
||||
# 结论
|
||||
small = [img for img in IMAGES if IMG_LONGSIDE[img] <= 1100]
|
||||
large = [img for img in IMAGES if IMG_LONGSIDE[img] > 1100]
|
||||
def ratio_range(num_side, den_side="default896"):
|
||||
ratios = []
|
||||
for img in large:
|
||||
den = avg(times(den_side, img))
|
||||
num = avg(times(num_side, img))
|
||||
if den:
|
||||
ratios.append(num / den)
|
||||
if not ratios:
|
||||
return 0, 0
|
||||
return min(ratios), max(ratios)
|
||||
|
||||
r1024_lo, r1024_hi = ratio_range("side1024")
|
||||
r0_lo, r0_hi = ratio_range("origin0")
|
||||
cold_note = f"冷启动 {cold_n} 次(≥{COLD_S:.0f}s)已从均值剔除。" if cold_n else ""
|
||||
conclusion = (
|
||||
f"原图长边 ≤ 1100({'/'.join(small)})时三档耗时接近;"
|
||||
f"长边 > 1600({'/'.join(large)})时相对默认896:"
|
||||
f"<b>1024 约 {r1024_lo:.1f}~{r1024_hi:.1f}×</b>,"
|
||||
f"<b>原图直送约 {r0_lo:.1f}~{r0_hi:.1f}×</b>。"
|
||||
f"{cold_note}画质对比见下方三列并排,点击可放大。"
|
||||
)
|
||||
|
||||
avg896 = side_avgs.get("default896", 0)
|
||||
avg1024 = side_avgs.get("side1024", 0)
|
||||
avg0 = side_avgs.get("origin0", 0)
|
||||
ratio1024 = avg1024 / avg896 if avg896 else 0
|
||||
ratio0 = avg0 / avg896 if avg896 else 0
|
||||
|
||||
legend = "".join(
|
||||
f'<span><i style="background:{color}"></i>{html.escape(label)}</span>'
|
||||
for _, label, color, _ in SIDES
|
||||
)
|
||||
|
||||
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 分辨率对比测试报告</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: 20px; }}
|
||||
.wrap {{ max-width: 1400px; margin: 0 auto; }}
|
||||
h1 {{ font-size: 24px; margin-bottom: 4px; }}
|
||||
.sub {{ color: #6b7280; font-size: 13px; margin-bottom: 20px; }}
|
||||
.summary {{ display: grid; grid-template-columns: repeat(5, 1fr); gap: 12px; margin-bottom: 24px; }}
|
||||
.stat {{ background: #fff; border-radius: 10px; padding: 16px; box-shadow: 0 1px 3px rgba(0,0,0,.06); }}
|
||||
.stat .num {{ font-size: 26px; font-weight: 700; }}
|
||||
.stat .lbl {{ font-size: 12px; color: #6b7280; margin-top: 2px; }}
|
||||
.stat.d .num {{ color: #2563eb; }}
|
||||
.stat.m .num {{ color: #10b981; }}
|
||||
.stat.o .num {{ color: #f59e0b; }}
|
||||
.card {{ background: #fff; border-radius: 12px; box-shadow: 0 1px 4px rgba(0,0,0,.07); margin-bottom: 20px; overflow: hidden; }}
|
||||
.card-h {{ padding: 12px 18px; background: #fafafa; border-bottom: 1px solid #f0f0f0; font-weight: 700; font-size: 15px; display: flex; align-items: center; gap: 8px; }}
|
||||
.card-h .tag {{ font-size: 11px; font-weight: 500; color: #6b7280; background: #f3f4f6; padding: 2px 8px; border-radius: 10px; }}
|
||||
.card-b {{ padding: 16px; }}
|
||||
.chart {{ width: 100%; height: auto; max-width: 920px; }}
|
||||
.barlabel {{ font-size: 12px; fill: #374151; font-weight: 600; }}
|
||||
.rowlabel {{ font-size: 11px; fill: #6b7280; }}
|
||||
.imglabel {{ font-size: 14px; fill: #111; font-weight: 700; }}
|
||||
.imgside {{ font-size: 10px; fill: #9ca3af; font-weight: 400; }}
|
||||
table.cmp {{ width: 100%; border-collapse: collapse; table-layout: fixed; }}
|
||||
table.cmp th {{ font-size: 12px; color: #6b7280; font-weight: 600; padding: 8px; text-align: center; border-bottom: 2px solid #f0f0f0; }}
|
||||
table.cmp td {{ padding: 8px; border-bottom: 1px solid #f6f6f6; vertical-align: top; text-align: center; }}
|
||||
table.cmp th.d, table.cmp td.d {{ background: #eff6ff; }}
|
||||
table.cmp th.m, table.cmp td.m {{ background: #ecfdf5; }}
|
||||
table.cmp th.o, table.cmp td.o {{ background: #fffbeb; }}
|
||||
td.sname {{ font-weight: 600; text-align: left; width: 90px; }}
|
||||
td.sname span {{ display: block; font-size: 11px; color: #9ca3af; font-weight: 400; }}
|
||||
.thumb {{ background: #222; border-radius: 6px; overflow: hidden; margin-bottom: 4px; cursor: zoom-in; }}
|
||||
.thumb img {{ width: 100%; height: 200px; object-fit: contain; display: block; }}
|
||||
.noimg {{ color: #d1d5db; font-size: 12px; padding: 40px 0; }}
|
||||
.meta {{ font-size: 11px; color: #6b7280; }}
|
||||
.err {{ font-size: 10px; color: #dc2626; margin-top: 2px; }}
|
||||
.note {{ background: #fef3c7; border-left: 3px solid #f59e0b; padding: 12px 16px; border-radius: 6px; font-size: 13px; margin-bottom: 20px; }}
|
||||
.legend {{ display: flex; gap: 20px; font-size: 12px; color: #6b7280; margin-bottom: 12px; flex-wrap: wrap; }}
|
||||
.legend span {{ display: inline-flex; align-items: center; gap: 5px; }}
|
||||
.legend i {{ width: 12px; height: 12px; border-radius: 2px; display: inline-block; }}
|
||||
.overlay {{ display: none; position: fixed; inset: 0; background: rgba(0,0,0,.9); z-index: 999; justify-content: center; align-items: center; cursor: zoom-out; padding: 30px; }}
|
||||
.overlay.active {{ display: flex; }}
|
||||
.overlay img {{ max-width: 95%; max-height: 95%; object-fit: contain; border-radius: 4px; }}
|
||||
@media (max-width: 900px) {{
|
||||
.summary {{ grid-template-columns: repeat(2, 1fr); }}
|
||||
.thumb img {{ height: 160px; }}
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<h1>接口2 生发 · 分辨率对比测试报告</h1>
|
||||
<p class="sub">POST /api/v1/hair/grow · female · 4 张图 × 5 发型 × 3 档分辨率 = {total} 次 · 串行</p>
|
||||
|
||||
<div class="summary">
|
||||
<div class="stat"><div class="num">{ok}/{total}</div><div class="lbl">成功 / 总数</div></div>
|
||||
<div class="stat d"><div class="num">{avg896:.1f}s</div><div class="lbl">默认896 平均</div></div>
|
||||
<div class="stat m"><div class="num">{avg1024:.1f}s</div><div class="lbl">1024 平均 · ×{ratio1024:.2f}</div></div>
|
||||
<div class="stat o"><div class="num">{avg0:.1f}s</div><div class="lbl">原图直送 平均 · ×{ratio0:.2f}</div></div>
|
||||
<div class="stat"><div class="num" style="color:{'#dc2626' if ratio0>1.3 else '#16a34a'}">×{ratio0:.2f}</div><div class="lbl">原图/默认 倍率</div></div>
|
||||
</div>
|
||||
|
||||
<div class="note">
|
||||
<b>结论:</b>{conclusion}
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-h">平均耗时对比(按图分组,单位:秒)</div>
|
||||
<div class="card-b">
|
||||
<div class="legend">{legend}</div>
|
||||
{bar_svg()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 style="font-size:18px;margin:8px 0 14px">画质对比(左→右:默认896 · 1024 · 原图直送)</h2>
|
||||
{compare_cards()}
|
||||
|
||||
</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_HTML, "w") as f:
|
||||
f.write(html_doc)
|
||||
# 同步首页
|
||||
with open(os.path.join(OUT, "index.html"), "w") as f:
|
||||
f.write(html_doc)
|
||||
print(f"已生成: {REPORT_HTML}")
|
||||
print(f"图片目录: {OUT}")
|
||||
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=接口2 分辨率对比测试报告 HTTP 服务 (8848)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=ubuntu
|
||||
WorkingDirectory=/home/ubuntu/hair/image/compare_test/out
|
||||
ExecStart=/home/ubuntu/miniconda3/envs/my_hair/bin/python -m http.server 8848 --bind 0.0.0.0
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""接口2 female 分辨率对比测试 v2: 5图 × 3发型 × 5档 = 75次。
|
||||
串行执行,记录耗时与成败,结果图按 图_发型_档位 命名保存。
|
||||
支持断点续跑(progress.json)。
|
||||
"""
|
||||
import base64, csv, json, os, sys, 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/res_test/out"
|
||||
PROGRESS = "/home/ubuntu/hair/image/res_test/progress.json"
|
||||
|
||||
IMG_DIR = "/home/ubuntu/hair/image"
|
||||
IMAGES = [
|
||||
("girl2", f"{IMG_DIR}/girl_img/girl2.jpg"),
|
||||
("girl5", f"{IMG_DIR}/girl_img/girl5.jpg"),
|
||||
("qwer", f"{IMG_DIR}/qwer.jpg"),
|
||||
("asdf", f"{IMG_DIR}/asdf.jpg"),
|
||||
("girl7", f"{IMG_DIR}/girl_img/girl7.jpg"),
|
||||
]
|
||||
# female: 2=flower(花瓣) 3=heart(心形) 5=wave(波浪)
|
||||
STYLES = [(2, "flower"), (3, "heart"), (5, "wave")]
|
||||
# 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, style_idx, style_key, side_name, side_val):
|
||||
key = f"{img_name}_{style_key}_{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), # 含原图档=0
|
||||
}
|
||||
t0 = time.time()
|
||||
rec = {"key": key, "img": img_name, "style": style_key, "style_idx": style_idx,
|
||||
"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(STYLES) * len(SIDES)
|
||||
print(f"=== 接口2 female 分辨率对比测试 v2 ===")
|
||||
print(f"矩阵: {len(IMAGES)}图 × {len(STYLES)}发型 × {len(SIDES)}档 = {total} 次")
|
||||
print(f"已跳过 {len(done_keys)} 个已完成项\n")
|
||||
|
||||
idx = 0
|
||||
for img_name, img_path in IMAGES:
|
||||
for style_idx, style_key in STYLES:
|
||||
for side_name, side_val in SIDES:
|
||||
idx += 1
|
||||
key = f"{img_name}_{style_key}_{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, style_idx, style_key, 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", "style", "side", "side_val", "ok", "elapsed_s", "out_w", "out_h", "bytes", "err"])
|
||||
for r in results:
|
||||
w.writerow([r["img"], r["style"], 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)
|
||||
|
||||
# 控制台速度表:按 图×发型 分行,5档列
|
||||
print("\n--- 速度对比(秒)---")
|
||||
print(f"{'图·发型':<18}{'原图0':>8}{'1024':>8}{'896':>8}{'768':>8}{'640':>8}")
|
||||
for img_name, _ in IMAGES:
|
||||
for _, style_key in STYLES:
|
||||
cells = []
|
||||
for side_name, _ in SIDES:
|
||||
r = next((x for x in results if x["img"] == img_name and x["style"] == style_key and x["side"] == side_name), None)
|
||||
cells.append(f"{r['elapsed']}✓" if r and r["ok"] else (f"{r['elapsed']}✗" if r else "-"))
|
||||
print(f"{img_name+'·'+style_key:<18}{cells[0]:>8}{cells[1]:>8}{cells[2]:>8}{cells[3]:>8}{cells[4]:>8}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/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}")
|
||||
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=接口2 5档分辨率对比报告 v2 (8849)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=ubuntu
|
||||
WorkingDirectory=/home/ubuntu/hair/image/res_test/out
|
||||
ExecStart=/home/ubuntu/miniconda3/envs/my_hair/bin/python -m http.server 8849 --bind 0.0.0.0
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -101,6 +101,16 @@
|
||||
<option value="false">⬜ 关闭 — 空遮罩,不烧模板线</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="redrawSideGroup">
|
||||
<label>重绘分辨率</label>
|
||||
<select id="redrawMaxSide" title="仅 Female 生效:ComfyUI 重绘前把送图长边压到该像素。值越小越快越省显存,细节越少;0=原图直送">
|
||||
<option value="" selected>默认 896</option>
|
||||
<option value="1024">1024(高清,慢)</option>
|
||||
<option value="768">768(快)</option>
|
||||
<option value="640">640(最快,糊)</option>
|
||||
<option value="0">0 — 不缩图(原图直送)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" id="submitBtn" onclick="submitTest()">🚀 提交</button>
|
||||
<button class="btn btn-outline btn-sm" onclick="clearResults()">清除</button>
|
||||
</div>
|
||||
@@ -155,6 +165,11 @@ async function submitTest() {
|
||||
fd.append('hair_style', checked.join(','));
|
||||
fd.append('use_mask', $('useMask').value);
|
||||
fd.append('prompt', $('promptInput').value);
|
||||
// 重绘分辨率:仅 female 传给后端(male 路径不接受此参数)
|
||||
if ($('gender').value === 'female') {
|
||||
const side = $('redrawMaxSide').value;
|
||||
if (side !== '') fd.append('redraw_max_side', side); // 空串=不传→后端默认896
|
||||
}
|
||||
const _reqStart = performance.now();
|
||||
try {
|
||||
const r = await fetch(API_BASE + '/api/v1/hair/grow', { method:'POST', headers:{ 'X-Internal-Token': 'dev-shared-secret-2026' }, body:fd });
|
||||
@@ -258,6 +273,8 @@ function onGenderChange() {
|
||||
lb.appendChild(document.createTextNode(' ' + s.label));
|
||||
div.appendChild(lb);
|
||||
});
|
||||
// 重绘分辨率选项仅 female 生效:male 时隐藏
|
||||
$('redrawSideGroup').style.display = (g === 'female') ? '' : 'none';
|
||||
}
|
||||
|
||||
function selectAllHair(select) {
|
||||
|
||||
Reference in New Issue
Block a user