From 5101bb5f6b24e6c82922f1bd453a10ee6646be37 Mon Sep 17 00:00:00 2001 From: xsl Date: Sun, 26 Jul 2026 22:06:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=8E=A5=E5=8F=A32):=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E9=A1=B5=E6=9A=B4=E9=9C=B2=E9=87=8D=E7=BB=98=E5=88=86=E8=BE=A8?= =?UTF-8?q?=E7=8E=87=E9=80=89=E9=A1=B9=20+=20=E5=88=86=E8=BE=A8=E7=8E=87?= =?UTF-8?q?=E5=AF=B9=E6=AF=94=E6=B5=8B=E8=AF=95=E5=A5=97=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- .gitignore | 10 + .../archive_20260725/batch_test.log | 75 +++ .../compare_test/archive_20260725/report.csv | 41 ++ .../compare_test/archive_20260725/report.html | 90 +++ .../compare_test/archive_20260725/report.json | 562 ++++++++++++++++++ image/compare_test/batch_test.py | 162 +++++ image/compare_test/gen_report.py | 244 ++++++++ image/compare_test/report-server.service | 15 + image/res_test/batch_test.py | 148 +++++ image/res_test/gen_report.py | 204 +++++++ image/res_test/report-server-v2.service | 15 + static/test_interface2.html | 17 + 12 files changed, 1583 insertions(+) create mode 100644 image/compare_test/archive_20260725/batch_test.log create mode 100644 image/compare_test/archive_20260725/report.csv create mode 100644 image/compare_test/archive_20260725/report.html create mode 100644 image/compare_test/archive_20260725/report.json create mode 100644 image/compare_test/batch_test.py create mode 100644 image/compare_test/gen_report.py create mode 100644 image/compare_test/report-server.service create mode 100644 image/res_test/batch_test.py create mode 100644 image/res_test/gen_report.py create mode 100644 image/res_test/report-server-v2.service diff --git a/.gitignore b/.gitignore index 4a32e7f..6acd943 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/image/compare_test/archive_20260725/batch_test.log b/image/compare_test/archive_20260725/batch_test.log new file mode 100644 index 0000000..345aa46 --- /dev/null +++ b/image/compare_test/archive_20260725/batch_test.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 diff --git a/image/compare_test/archive_20260725/report.csv b/image/compare_test/archive_20260725/report.csv new file mode 100644 index 0000000..d1be2ac --- /dev/null +++ b/image/compare_test/archive_20260725/report.csv @@ -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, diff --git a/image/compare_test/archive_20260725/report.html b/image/compare_test/archive_20260725/report.html new file mode 100644 index 0000000..002b1cd --- /dev/null +++ b/image/compare_test/archive_20260725/report.html @@ -0,0 +1,90 @@ + + + + + +接口2 分辨率对比测试报告 + + + +
+

💄 接口2 生发 · 分辨率对比测试报告

+

POST /api/v1/hair/grow · female · 4 张图 × 5 发型 × 2 档分辨率 = 40 次 · 串行 · 4090 (24G)

+ +
+
40/40
成功 / 总数
+
10.4s
默认896 平均耗时
+
17.4s
原图直送 平均耗时
+
×1.67
原图/默认 倍率
+
+ +
+ 📊 结论:原图长边 ≤ 1082(girl2/girl5)时两档耗时接近;原图长边 > 1600(qwer/asdf)时,原图直送比默认896慢约 2.3~2.5 倍。 + 4090 24G 显存扛得住长边 1678 的原图直送(无 OOM)。画质对比见下方左右并排图,点击可放大。 +
+ +
+
📈 平均耗时对比(按图分组,单位:秒)
+
+
默认 896原图直送 0
+ 11.7s默认89612.4s 原图0girl2长边10828.5s默认8967.4s 原图0girl5长边76710.7s默认89625.8s ⚠原图0qwer长边167810.8s默认89624.0s ⚠原图0asdf长边1666 +
+
+ +

🔍 画质对比(左:默认896 · 右:原图直送)

+
📷 girl2 原图长边 1082px
发型默认 896原图直送 0耗时差
ellipse椭圆
girl2_ellipse_default896
✅ 10.8s · 1082×1081
girl2_ellipse_origin0
✅ 13.3s · 1088×1088
+2.5s
flower花瓣
girl2_flower_default896
✅ 11.0s · 1082×1081
girl2_flower_origin0
✅ 12.5s · 1088×1088
+1.5s
heart心形
girl2_heart_default896
✅ 11.9s · 1082×1081
girl2_heart_origin0
✅ 12.2s · 1088×1088
+0.3s
straight直线
girl2_straight_default896
✅ 12.1s · 1082×1081
girl2_straight_origin0
✅ 12.3s · 1088×1088
+0.2s
wave波浪
girl2_wave_default896
✅ 12.7s · 1082×1081
girl2_wave_origin0
✅ 11.9s · 1088×1088
-0.8s
📷 girl5 原图长边 767px
发型默认 896原图直送 0耗时差
ellipse椭圆
girl5_ellipse_default896
✅ 9.6s · 768×752
girl5_ellipse_origin0
✅ 7.3s · 768×752
-2.3s
flower花瓣
girl5_flower_default896
✅ 8.0s · 768×752
girl5_flower_origin0
✅ 7.2s · 768×752
-0.8s
heart心形
girl5_heart_default896
✅ 8.1s · 768×752
girl5_heart_origin0
✅ 7.6s · 768×752
-0.5s
straight直线
girl5_straight_default896
✅ 8.4s · 768×752
girl5_straight_origin0
✅ 7.2s · 768×752
-1.2s
wave波浪
girl5_wave_default896
✅ 8.4s · 768×752
girl5_wave_origin0
✅ 7.7s · 768×752
-0.7s
📷 qwer 原图长边 1678px
发型默认 896原图直送 0耗时差
ellipse椭圆
qwer_ellipse_default896
✅ 10.2s · 1288×1678
qwer_ellipse_origin0
✅ 30.0s · 1280×1680
+19.8s
flower花瓣
qwer_flower_default896
✅ 10.2s · 1288×1678
qwer_flower_origin0
✅ 25.0s · 1280×1680
+14.8s
heart心形
qwer_heart_default896
✅ 11.1s · 1288×1678
qwer_heart_origin0
✅ 25.0s · 1280×1680
+13.9s
straight直线
qwer_straight_default896
✅ 10.7s · 1288×1678
qwer_straight_origin0
✅ 23.4s · 1280×1680
+12.7s
wave波浪
qwer_wave_default896
✅ 11.1s · 1288×1678
qwer_wave_origin0
✅ 25.8s · 1280×1680
+14.7s
📷 asdf 原图长边 1666px
发型默认 896原图直送 0耗时差
ellipse椭圆
asdf_ellipse_default896
✅ 11.2s · 1254×1666
asdf_ellipse_origin0
✅ 25.7s · 1248×1664
+14.5s
flower花瓣
asdf_flower_default896
✅ 10.7s · 1254×1666
asdf_flower_origin0
✅ 22.9s · 1248×1664
+12.2s
heart心形
asdf_heart_default896
✅ 10.4s · 1254×1666
asdf_heart_origin0
✅ 22.9s · 1248×1664
+12.5s
straight直线
asdf_straight_default896
✅ 9.8s · 1254×1666
asdf_straight_origin0
✅ 24.0s · 1248×1664
+14.2s
wave波浪
asdf_wave_default896
✅ 11.9s · 1254×1666
asdf_wave_origin0
✅ 24.3s · 1248×1664
+12.4s
+ +
+
+ +
+ + + diff --git a/image/compare_test/archive_20260725/report.json b/image/compare_test/archive_20260725/report.json new file mode 100644 index 0000000..ab48e76 --- /dev/null +++ b/image/compare_test/archive_20260725/report.json @@ -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 + } +] \ No newline at end of file diff --git a/image/compare_test/batch_test.py b/image/compare_test/batch_test.py new file mode 100644 index 0000000..848e95c --- /dev/null +++ b/image/compare_test/batch_test.py @@ -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() diff --git a/image/compare_test/gen_report.py b/image/compare_test/gen_report.py new file mode 100644 index 0000000..9d39dba --- /dev/null +++ b/image/compare_test/gen_report.py @@ -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''] + 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'') + parts.append(f'{a:.1f}s {warn}') + parts.append(f'{html.escape(label)}') + parts.append(f'{img}长边{longside}') + y += bar_h * n_bars + gap + parts.append("") + return "".join(parts) + +def img_cell(r, cls): + if not r: + return f'缺失' + fname = r["key"] + ".jpg" + status = "✅" if r["ok"] else "❌" + t = f'{r["elapsed"]}s' + dim = f'{r["out_w"]}×{r["out_h"]}' + err = f'
{html.escape(r["err"])}
' if r["err"] else "" + img_tag = (f'{html.escape(r[' + if r["ok"] else '
无图
') + return (f'
{img_tag}
' + f'
{status} {t} · {dim}
{err}') + +def compare_cards(): + out = [] + for img in IMAGES: + out.append(f'
📷 {html.escape(img)} 原图长边 {IMG_LONGSIDE[img]}px
') + heads = "".join(f'{html.escape(label)}' for _, label, _, cls in SIDES) + out.append(f'{heads}') + 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'{cells}') + out.append("
发型
{html.escape(style)}{sc}
") + 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"1024 约 {r1024_lo:.1f}~{r1024_hi:.1f}×," + f"原图直送约 {r0_lo:.1f}~{r0_hi:.1f}×。" + 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'{html.escape(label)}' + for _, label, color, _ in SIDES +) + +html_doc = f""" + + + + +接口2 分辨率对比测试报告 + + + +
+

接口2 生发 · 分辨率对比测试报告

+

POST /api/v1/hair/grow · female · 4 张图 × 5 发型 × 3 档分辨率 = {total} 次 · 串行

+ +
+
{ok}/{total}
成功 / 总数
+
{avg896:.1f}s
默认896 平均
+
{avg1024:.1f}s
1024 平均 · ×{ratio1024:.2f}
+
{avg0:.1f}s
原图直送 平均 · ×{ratio0:.2f}
+
×{ratio0:.2f}
原图/默认 倍率
+
+ +
+ 结论:{conclusion} +
+ +
+
平均耗时对比(按图分组,单位:秒)
+
+
{legend}
+ {bar_svg()} +
+
+ +

画质对比(左→右:默认896 · 1024 · 原图直送)

+ {compare_cards()} + +
+
+ +
+ + + +""" + +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}") diff --git a/image/compare_test/report-server.service b/image/compare_test/report-server.service new file mode 100644 index 0000000..a8e0006 --- /dev/null +++ b/image/compare_test/report-server.service @@ -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 diff --git a/image/res_test/batch_test.py b/image/res_test/batch_test.py new file mode 100644 index 0000000..46af7bf --- /dev/null +++ b/image/res_test/batch_test.py @@ -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() diff --git a/image/res_test/gen_report.py b/image/res_test/gen_report.py new file mode 100644 index 0000000..f4d18fb --- /dev/null +++ b/image/res_test/gen_report.py @@ -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'' + f'
原图
' + f'
📷 原图
') + # 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'' + f'
{html.escape(r[
' + f'
{r["elapsed"]}s · ' + f'{r["out_w"]}×{r["out_h"]}
') + elif r: + cell = (f'
' + f'
{html.escape(r["err"][:30])}
') + else: + cell = '
' + side_cells.append(cell) + row_label = (f'' + f'
{html.escape(img)}
' + f'
{html.escape(style_cn)}
' + f'
长边 {LONGSIDE[img]}
') + rows_html.append("" + row_label + orig_cell + "".join(side_cells) + "") + +# 顶部平均耗时卡 +def stat_card(lbl, val, col, sub=""): + return (f'
' + f'
{val:.1f}s
' + f'
{lbl}{sub}
') + +stat_cards = "".join([ + stat_card("原图直送", avg_origin, "#dc2626", "
排除冷启动"), + 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""" + + + + +接口2 分辨率对比测试 v2 + + + +
+

💄 接口2 生发 · 5档分辨率对比报告

+

POST /api/v1/hair/grow · female · 5 图 × 3 发型(花瓣/心形/波浪) × 5 档分辨率 = 75 次 · 串行 · 4090 (24G)

+ +
+
{ok}/{total}
成功 / 总数
+ {stat_cards} +
+ +
+ 📊 结论:耗时随送图分辨率单调下降。大图(qwer/asdf 长边~1670) 原图直送需 ~27-30s,是 896 档(10s) 的近 3 倍; + 中小图(girl2/girl5/girl7 长边 767-1082)各档差异较小(6-15s)。 + 4090 24G 全程无 OOM,75/75 成功。画质对比见下表(横向滑动),点击任意图可放大。 + + 快(<{tmin+ (tmax-tmin)*0.33:.0f}s) + 中等 + 慢(>{tmin+ (tmax-tmin)*0.66:.0f}s) + +
+ +
+ + + + + + + + + + + + + + {"".join(rows_html)} + +
图 · 发型📷 原图原图直送 (0)
不缩放
1024896
默认
768640
+
+
+
+ + + +""" +with open(REPORT, "w") as f: + f.write(html_doc) +print(f"已生成: {REPORT}") diff --git a/image/res_test/report-server-v2.service b/image/res_test/report-server-v2.service new file mode 100644 index 0000000..74b69f2 --- /dev/null +++ b/image/res_test/report-server-v2.service @@ -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 diff --git a/static/test_interface2.html b/static/test_interface2.html index ed23551..31ff52f 100644 --- a/static/test_interface2.html +++ b/static/test_interface2.html @@ -101,6 +101,16 @@ +
+ + +
@@ -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) {