113 Commits
Author SHA1 Message Date
xsl f68049f001 save 2026-08-04 22:39:27 +08:00
xsl 7725e461eb save code 2026-08-04 22:38:42 +08:00
xsl 7d8dfd93b8 save 2026-08-04 22:38:42 +08:00
Ubuntu 9379fbb8f8 save 2026-08-02 01:27:04 +08:00
xsl c20606c003 save 2026-07-31 23:29:07 +08:00
xslandCursor 1ca033f25a feat: 接口1/6 标注层字号上调一档 + 眉心改用 9 号点定位
- annotation: 自适应字号系数 0.017→0.020(下限 8→9),标注文字更大更清晰
- measure: _brow_center 只取 FaceMesh 9 号点(眉间上点),不再与 151 取中点

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 23:02:19 +08:00
UbuntuandCursor 87ff2c15d0 chore: 精简生发提示词,去掉磨皮/美颜要求
统一改为「填充遮罩区域的头发」,涉及后端默认值、ComfyUI 工作流 JSON、
测试页、benchmark 脚本、local_test。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 15:57:57 +08:00
UbuntuandCursor c536e4ccb1 feat: 屏蔽接口3(B端生发)
网关层直接拦截返回 1007,不再转发到 worker 池;worker 侧路由同步标记
deprecated 并短路返回,保留原参数签名避免老客户端裸 404。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 15:57:46 +08:00
xslandCursor 9fb5b486c0 fix(接口4): worker 移除脸型 Mock + face_shape 改本机 MediaPipe 计算
- worker /api/v1/face/features 不再返回假成功数据,直接告知仅网关实现,
  避免本机误打 :8187 被 Mock 结果误导。
- 网关 ark_api_key 加载优先级改为 gateway/config.json 优先(原先误读
  worker_config.json 里的失效 key)。
- 接口4 face_shape 不再采信豆包结果,改用本机 face/face_shape_classifier.py
  (MediaPipe 7 类)计算覆盖;其余 5 项特征仍走豆包。
- 修复 face_shape_classifier 共享 FaceMesh 实例的线程安全问题(加锁),
  避免网关侧接口4 并发请求时崩溃/结果错乱。
- 新增 /api/v1/debug/face-shape 调试接口 + static/test_face_shape.html
  单图调试页(worker 侧)。
- 更新文档:网关机现在也需要 mediapipe/opencv-python/numpy<2。

⚠️ 部署前提醒:网关机需先安装 mediapipe==0.10.14 / opencv-python==4.10.0.84 /
numpy==1.26.4,否则接口4 会返回 1007「分析服务异常」。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 14:41:49 +08:00
xsl a748dfd1e5 Merge branch 'main' of http://git.xiangsilian.com:3000/xsl/hair 2026-07-29 13:26:44 +08:00
xslandCursor 587876f8a0 feat: 脸型分类器(7分类)+批量报告生成
基于 MediaPipe 468 关键点提取几何特征,转 z 分数后与各脸型原型加权匹配。
参考分布与原型靶心取自 1093 张测试集的实测画像,不再靠人工设定绝对阈值。

方形脸占比从 29.6% 降到 13.8%,两处原因:一是参考统计量原先只由 50 张样本
估得,相对全量人群有系统性偏移,且三项偏移都在给方形脸加分;二是原型把
aspect_ratio 当作方形脸的主特征,但实测方脸组该值中位仅 +0.16,真正"宽"的
是圆脸(+1.01),等于在拿脸宽找方脸。

原型参数在「6 张基准标注图判定不变、且领先第二名 >=3 分」的约束下搜索得到。
余量约束是必要的:早前一版余量仅 0.008 分,权重写码时四舍五入就会翻转结论。

测试素材(人像照片)与报告输出体积大,一并加入 .gitignore。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-29 13:16:43 +08:00
xsl 81c7a0b55c feat: 接口1/5/6 发际线弃用逻辑(顶庭<0.7cm)
发际线离头顶<0.7cm时判定分割不可靠,弃用发际线:
顶/上庭字段置null、face_total只算中下庭、标注图保留头顶线去掉发际线、
只标中/下庭。eye1/7竖向范围改用眉心。
2026-07-27 23:21:01 +08:00
xsl 218818bdd0 docs: 同步 integration.html / 接口文档.md / test_interface5.html
- 接口5: 补 generate_grow_image 参数说明(接口文档/integration/test_interface5 加控件)
- 接口1/5/6: 补 left_position/right_position 字段(MediaPipe 21/251号点)
- 接口4: features 字段纠正为固定6个英文字段(原误写~42项含中文, 与代码不符)
- 接口7: 完全移除(代码已 deprecated=True 固定返回错误, 文档却当正常接口详述)
- 错误码: 删错误的'1004已废弃'(1004仍用于接口2/5 gender校验), 补 1004 正确描述 + 1009(X-Internal-Token鉴权)
- test_interface5.html: 加 generate_grow_image 复选框
2026-07-24 00:52:14 +08:00
xsl ae47472a63 feat(接口1/5/6): 返回数据新增 left_position/right_position(MediaPipe 21/251号点)
- face_mesh_landmarks.py: 加常量 LEFT_POSITION=21 / RIGHT_POSITION=251
- measure.py: MeasureResult 收 landmarks/宽高, to_response 顶层输出两点(原图像素 {x,y}, 与 landmarks 同格式)
- measure_face 透传 landmarks(签名不变, 6处调用零改动); __init__ 用 None 默认值守卫向后兼容
- 三接口自动生效: 接口1/6 在 data 顶层, 接口5 在 face_measure 对象里(复用同一 to_response)
- 实测坐标左右镜像合理, 44 个现有测试全过无回归
2026-07-24 00:52:14 +08:00
xsl cd8985e93d feat(接口5): 新增 generate_grow_image 参数控制是否生成生发效果图
- app.py: 接口5 路由加表单参数 generate_grow_image(bool, 默认 True)并透传
- hairline/service.py: generate_hairline_pngs 加同名参数, False 时跳过 ComfyUI 生发、grown_png 恒 None
- 默认行为不变(向后兼容); false 时仅返回三档发际线叠图与中心点, 大幅降低耗时
- 网关字节级透传 multipart, 新参数自动到达 worker, 无需改网关
2026-07-24 00:52:04 +08:00
UbuntuandClaude bb9f55e93c feat(gateway): 全局串行化(并发=1) + 记录入参/出参/耗时日志
并发模型从「每worker并发1 + 多worker并行」改为全局串行:同一时间
只处理1个请求,其余排队;多 worker 仅作热备(主 worker 坏了才用备机)。
接口4(face/features) 走豆包、不占 GPU,不纳入串行。

- pool: asyncio.Semaphore(max_global_concurrency=1) + acquire/release_global_slot
  (依赖单进程 uvicorn 部署,已在注释中标注)
- forward: proxy_request 最外层 acquire 全局槽、try/finally 全路径释放;
  入参 multipart 解析挂 request.state;原重试/故障转移逻辑抽到 _dispatch_with_retries
- reqlog(新): 标量入参保留;图片(file/base64)存盘转URL,绝不内嵌base64;
  出参递归摘要截断(landmarks/长串/大数组)
- logging_middleware: RequestLogEntry 加 request_params/response_data,
  jsonl 全量记录;_load_from_logfile 同步映射防重启丢字段;get_stats recent 暴露
- /gateway-health 暴露 global_max/global_busy/global_waiting
- config: dispatch 新增 max_global_concurrency / max_queue_wait_seconds
- tests: test_reqlog + test_gateway_serialization(9 用例)

顺带提交此前未提交的网关统计(daily stats 接口)与耗时看板(api_timing_dashboard.html)。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-23 22:56:48 +08:00
xslandCursor 98b9108837 perf(接口2): 稳定混跑耗时至12s内 —— ComfyUI插队 + CLIP挪CPU + 提示词全局统一
问题:接口2 与接口3/5 乱序调用时耗时抖动(最差 15~22s)。两个根因:
1. GPU 24G 常驻 21.4G,Flux-2(3.9G) 无法完全驻留显存,每次采样动态换页,
   速度随空闲显存波动(2s~8s);
2. ComfyUI 单队列 FIFO,接口2 排在接口3/5 批量任务后面。

改动:
- hairline/comfyui.py: run() 新增 front 参数,/prompt 带 "front": true 插队到队列最前;
  redraw.py 透传;service.py 接口2 三处调用(女重绘 + 男有/无遮罩)传 front=True,
  接口3/5 仍走普通队列。
- add_hair.json / 0716add-hair-api.json: 节点61 CLIPLoader device default→cpu。
  qwen CLIP(4G) 不再占显存(文本条件缓存常年命中),ComfyUI 显存 8.8G→4.5G,
  Flux-2 完全驻留,采样稳定 ~3-5s。代价:换 prompt 后首次请求 CPU 编码 ~11s(一次性)。
- 提示词全局统一为「填充遮罩区域的头发,皮肤加一点磨皮,再加一点美颜」:
  app.py 4处默认值、service.py _REDRAW_PROMPT、redraw.py _DEFAULT_PROMPT、
  4个工作流节点60内置文案、测试页(test_interface2/3/7/12/12_final)、local_test。
  任何两个不同 prompt 交替提交都会打爆 CLIP 编码缓存(--cache-classic 只存最近一次),
  之前测试页旧文案与服务端不一致导致交替测试每次 +11s。
- app.py: 接口7 /api/v1/hair/grow-v2 下线(业务弃用;add_hair2.json 的 Klein-9b
  会把常驻 Klein-4b 挤出显存)。保留 stub 返回 1007 明确报错,避免裸 404。

实测(1024 档):接口2女 8.5~10s、接口2男 ~5s、接口3 ~7-10s,交替混跑无尖刺。

Co-authored-by: Cursor <cursoragent@cursor.com>

(cherry-picked from ubuntu3090 e7b62f2;已适配 main 分支代码结构:main 无 _REDRAW_PROMPT/_REDRAW_MAX_SIDE 缩图逻辑,front=True 直接加在 _call_local_redraw / generate_grow_results 的调用点;另把 main 独有的 benchmark_*.py 里的 prompt 一并统一)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 01:35:42 +08:00
xslandCursor d4aae9722c fix(pose): 修正正面照被误判为1003(solvePnP翻转解)
ITERATIVE 偶发收敛到相机后方(tz<0),roll≈±180° 超阈值,
把正面照误判为非正面。检测到负深度时回退 SQPNP 重解正深度解。
补充回归测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-21 16:46:05 +08:00
xsl f870c20f7f fix(接口3测试页): base64 图片补 data:image/jpeg;base64, 前缀
后端返回的 hair_growth_image_base64 是裸 base64(无 data: 前缀),
原代码直接赋给 <img>.src,浏览器把 base64 当相对路径请求,
拼成 http://host:8187/9j/4AAQ... 发 GET 导致 400 Bad Request。

参照接口2 测试页的写法,手动拼接 data:image/jpeg;base64, 前缀。
后端代码无需改动(接口2 一直正常)。
2026-07-19 15:14:41 +08:00
xsl 6d9e92f710 chore: torch 升级为 2.11.0+cu128 支持 RTX 5090 (sm_120)
原 torch 2.2.2+cu121 只编到 sm_90,在 RTX 5090 上 GPU 算子报
'no kernel image',接口2 因 SegFormer/BiSeNet 跑不动 GPU 而失败。
venv 实际已升级到 torch 2.11.0+cu128 / torchvision 0.26.0+cu128,
此处同步 requirements.txt 让文档与实际环境一致。

numpy 保持 1.26.4(<2,mediapipe/scikit-image 依赖),cu128 兼容。
2026-07-18 16:30:18 +08:00
xsl b58cd4c441 优化为4b 模型 2026-07-18 15:30:31 +08:00
xslandCursor 659c037270 feat: ComfyUI 改走 10.60.74.221,测试页上传图超阈值自动降采样
将 worker 默认 ComfyUI 地址改为远端 10.60.74.221:8188;前端测试页在像素超过 1536000 时等比缩小到 786432 以内。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 02:23:35 +08:00
xsl c1bb9614c7 save code 2026-07-17 01:54:04 +08:00
xsl 2b1f528ddd perf: 接口2/12 跳过 viz 叠图生成,省 ~80ms
compute_mask 新增 render_viz 开关(默认 True 保持接口11 行为不变)。
接口2/12 路径(generate_hairline_redraw)传 render_viz=False,跳过 6+ 张
overlay JPG 的 _jpg_b64 编码(baseline/upper/hair_seg/mask/hairline/pushed),
只保留必需的数据字段(_inner_pts/_outer_pts/_upper_mask/mask_pixels)。
2026-07-17 01:37:53 +08:00
xsl d9e96aca87 chore: 重绘服务改走内网 10.60.74.221:8899
gpu_worker 与重绘服务在同一内网,走内网地址(3ms 延迟,比公网更稳定)。
三处调用地址(service.py / test_interface12 / test_interface12_final)
由公网 117.50.183.232 改为内网 10.60.74.221。仍可用 HAIR_LOCAL_REDRAW_URL 覆盖。
2026-07-17 00:57:58 +08:00
xsl 8aed389d79 chore: 重绘服务拆到远程机器 117.50.183.232:8899
接口2 female 后端(generate_grow_results_swap)、test_interface12.html、
test_interface12_final.html 三处重绘调用地址由本机 127.0.0.1:8899 改为远程
117.50.183.232:8899,本机不再跑重绘服务(释放本机显存)。

地址仍可用 HAIR_LOCAL_REDRAW_URL 环境变量覆盖。
2026-07-17 00:44:20 +08:00
xsl c72e3ceda9 asdf 2026-07-17 00:32:13 +08:00
xsl 99ce21334a asdf 2026-07-17 00:26:47 +08:00
xsl 5fbc03a6df feat: 接口12/接口2 发际线重绘改走 local_test 外部 ComfyUI 服务
后端 generate_hairline_redraw 跳过内置 Flux-2 重绘,改为产出 final(接缝融合基底)
+ 纯红遮罩 PNG(redraw_band_mask_base64,遮罩区=(255,0,0,255)、其余全透明)。

接口2 female(generate_grow_results_swap) 取 final+遮罩后在后端调 local_test
(0716add-hair.json 工作流) 完成重绘,结果作为生发图返回;male 分支不变。

测试页 test_interface12.html / test_interface12_final.html 改为两阶段:
先生成 final+纯红遮罩,再前端调 local_test 重绘并展示;color_match 默认不勾选。

local_test/app.py 加 CORS 头(OPTIONS 预检),支持浏览器跨域直连。
2026-07-17 00:00:15 +08:00
xsl 0bbb15d668 添加服务 2026-07-16 22:57:12 +08:00
xsl 632e75317b 接口6 增加字段 2026-07-16 12:38:10 +08:00
xsl 1cd4115b26 Merge branch 'main' of http://git.xiangsilian.com:3000/xsl/hair 2026-07-16 09:38:53 +08:00
xslandCursor e8a2c5a8a1 fix: 日志目录去掉硬编码 /home/xsl,改为基于仓库根解析(支持 HAIR_LOG_DIR 覆盖)
worker 部署到 /home/ubuntu 等其他路径时,原硬编码 /home/xsl/hair/log
会导致 Permission denied。改为相对仓库根,兼容多机部署。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-16 08:07:03 +08:00
xsl 0f8a7e27c0 部署线上5090时候的改动git commit -m 部署线上5090时候的改动。 2026-07-16 07:54:07 +08:00
xsl 12f34c44f2 修改发型为最终发型 2026-07-16 00:22:54 +08:00
xslandCursor a208fe88ec feat: 拆分接口11/12,新增接口12 final / final v2 精简重绘端点
- 接口11 移除重绘,仅生成 final;接口12 (grow_v2) 负责发际线带 Flux-2 重绘
- 接口12 重绘带改为发际线外推 band_lo_mult~band_hi_mult 倍 push(默认 0.5~1.5),页面可调
- 接口12 同时产出 A 整帧重绘 与 B 局部加发+全脸美颜(beauty_alpha 可调)
- 新增 grow_v2_final(整帧重绘)/ grow_v2_final_v2(B 局部+美颜)端点:仅需图片+发型 ID,其余用固化默认值(color_match 关闭)
- 配套精简测试页 test_interface12_final.html / test_interface12_final_v2.html / test_interface12.html
- 恢复接口11 调试页多频段与换发型可调参数、color_match 默认不勾选
- 删除旧脚本 batch_grow_v2.py / gen_report_hairline_v2.py / test_simple.html

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-16 00:04:36 +08:00
xsl 4001df2c34 save code 2026-07-15 01:16:32 +08:00
xsl 9386f84c88 docs: 同步默认提示词「加一点美颜」到文档/接入页/测试页
- docs/接口文档.md:接口2/3/5/7 参数表统一更新默认提示词,接口2/3/7
  补全此前缺失的 prompt 参数行;顺带修正接口7 输出表 image_url 描述
  为透明 PNG(上一轮透明 PNG 改动的遗漏)
- static/integration.html:接口2 描述句 + 接口7 字段表同步透明 PNG 描述
- static/test_interface2/3/7.html:prompt 输入框默认值同步更新
2026-07-13 23:46:40 +08:00
xsl d5397a6d6f chore: 接口2/3/5/7 生发 ComfyUI 默认提示词加「加一点美颜」
将接口2(/api/v1/hair/grow)、接口3(/api/v1/hair/grow-b)、
接口5(/api/v1/hairline/generate)、接口7(/api/v1/hair/grow-v2)的
prompt 参数默认值从「补充遮罩区域的头发」改为「补充遮罩区域的头发,加一点美颜」。
2026-07-13 23:41:42 +08:00
xsl 95c6a2d929 feat: 接口2/5 发际线叠图改为透明 PNG(仅曲线),生发图不变
接口2(/api/v1/hair/grow)的 image_url 和接口5(/api/v1/hairline/generate)
的 image_middle/high/low_url 从「原图+白线合成 JPG」改为「透明底 PNG(仅含
发际线曲线)」,前端需叠加原图显示。grown_image_url 生发图保持不变(ComfyUI
完整人像照片)。

实现:
- app.py 新增 _rgba_png_b64() 编码 RGBA 透明层为 PNG base64(保留 alpha)
- hairline/service.py 接口2/5 改用 build_overlay_layer(返回 RGBA 透明层)
  替代 render_hairline_overlay(合成到原图)
- 网关零改动:rewrite_base64_to_url 已按 \x89PNG 魔数嗅探落盘为 .png

测试页:test_interface2/5.html 改为「原图打底 + 透明PNG 绝对定位叠加」显示
(复用 .img-stack 结构,固定叠加无开关)。

文档:接口文档.md / integration.html 更新接口2/5 图片字段说明。
测试:43 passed,接口2 image_base64 改断言为 PNG 魔数,接口5 三档叠图同。
2026-07-13 23:38:55 +08:00
xsl 28255ef7c2 feat: 接口5 新增 face_measure(复用接口1测量数值)+ 接口1 七眼 eye1~eye7
接口5(/api/v1/hairline/generate)在发际线结果基础上新增 data.face_measure,
复用接口1的四庭七眼测量数值(四庭/七眼 eye1~eye7/landmarks/姿态),
不含标注图;独立流程容错,测量失败时为 null 不影响发际线主结果。

重构:提取 _run_face_measure_data() 共用函数,接口1/6 改调它再补标注图,
行为不变(43 测试全过,错误码 1001/1003/1008 回归正常)。

接口1/5 七眼新增 eye1~eye7 从左到右 7 段宽度(cm),eye1/eye7 耳朵不可见
时为 null(保留键)。

测试页 test_interface5.html:移除点击切换卡片网格改为所有发型平铺,
新增四庭七眼测量卡片。前端接入页 integration.html / 接口文档同步更新。
2026-07-13 22:40:55 +08:00
xsl b163a3f34a 调整 baseline 关键点 + 新增极简测试页
- head_mask.py: BASELINE_IDX 改为 [162,71,68,104,69,108,151,337,299,333,298,301,389],
  左端点 21→162、右端点 251→389,新增 71/301 两点;同步更新注释
- static/test_simple.html: 极简测试页,仅需选图片+发型,原图与结果左右并排对比
- .gitignore: 忽略 report_hairline_v2.zip
2026-07-12 22:27:02 +08:00
Ubuntu 5d9d91bc82 save code 2026-07-12 18:50:05 +08:00
xsl 3fe5f6cf0d fix: batch_grow_v2.py 的 hr_options 从 HR_OPTIONS 动态生成,不再硬编码两档
旧代码硬编码 hr_options=[hr,nohr],导致仅非高清批量跑完后 meta 仍含 hr 档,
报告脚本取 hr_opts[0]=hr 与实际结果的 nohr 不匹配,结果图全部渲染不出来。
2026-07-12 00:08:01 +08:00
xsl e17677158a 批量测试:仅非高清(20脸×5发型=100张)+ 报告原图大图并排对比
- batch_grow_v2.py: HR_OPTIONS 仅保留非高清档,去掉高清
- gen_report_hairline_v2.py: 每张脸 grid 改为「原图大图 + 5发型」6列并排,
  原图不再是缩略图;去掉高清/非高清行标签和失败表高清列
2026-07-12 00:02:42 +08:00
xsl 1e6c2e54de 接口11/12:固定 pushed 遮罩 + multiband 融合,移除其他算法选项
遮罩算法只保留 pushed(发际线外推),融合算法只保留 multiband(多频段金字塔),
eroded/closed/feather/alpha_gradient/seamless 等旧选项从接口参数层移除。

- generate_hairline_grow: 删除 mask_type/blend_method/feather_px/color_match 参数,
  内部固定 mask_type=pushed、blend_method=multiband
- app.py 接口11/12: 删除 mask_type/blend_method/feather_px/color_match Form 参数,
  调用处改关键字传参;grow_v2 只传 image+hairline_id 即默认走最新算法
- 前端两个测试页: 删除遮罩/融合下拉选项及相关联动,固定展示 pushed 步骤
- 文档: 更新为"固定 pushed + multiband,移除其他选项"
2026-07-11 23:31:19 +08:00
xsl 1b9f3fdb6f 终于修改对了,还是opus 2026-07-11 23:14:07 +08:00
xsl 41bb164a52 接口11/12:新增发际线外推遮罩(pushed)模式 + multiband融合修复 + 调试日志 + 对比报告脚本
发际线生发遮罩算法(mask_type=pushed):
- _extract_hairline:提取头发/皮肤交界线(逐列头发下沿),用 baseline 水平 y 线截断(无竖线)
- _pushed_mask:以眉心(151点)为圆心逐点径向外推 push_cm,与 baseline 组闭合区域
- 径向归并锯齿用插值填补,避免遮罩碎裂
- pushed 模式过程可视化(①-f 交界线 / ①-g 外推+遮罩),eroded/closed 不展示无关步骤

multiband 金字塔融合修复(hairline_grow.py):
- mb_levels 按层数膨胀外缘 keep 区,让过渡带随层数变宽(旧硬二值钳回导致 mb_levels 形同虚设)

接口12 grow_v2(固定参数精简版):
- 固定 multiband/mb_levels=5/erode_cm=0.6,仅返回 final_base64
- 支持 mask_type=pushed + hairline_push_cm/hairline_edge

调试支持:
- 调试页 test_interface11_debug.html(前后端日志面板 + 下载日志按钮)
- hairline_grow.log 全链路日志(按 rid 关联),/api/v1/debug/hairline_log 下载接口
- 遮罩计算过程可视化(baseline/upper/头发分割/交界线/外推/最终遮罩)

文档与脚本:
- docs/发际线生发遮罩算法_pushed模式.md 算法说明
- scripts/batch_grow_v2.py 批量调用、gen_report_hairline_v2.py 对比报告生成
2026-07-11 22:23:57 +08:00
Ubuntu de2cecf0d5 save code 2026-07-10 00:38:39 +08:00
xslandClaude Opus 4.8 f8e30ad32e 接口5:改为多选发型 + 每发型返回 middle/high/low 三档叠图(去掉 hairline_level 入参)
- 去掉 hairline_level 入参;middle/high/low 三档都返回
- 入参改为同接口2:gender + hair_style(逗号分隔多选,必填),缺失/越界返回 1007
- 每个选中发型返回 image_{middle,high,low}_base64 三档叠图 + grown_image_base64 生发图,
  按发型分组;order = 发型序号,含 hairline_type
- 生发黑模板仍固定 middle(hairline_texture_black/),每发型 1 张生发图
- best_hairline_center_point 取首个选中发型的 middle 档
- 同步更新测试页(三档并排展示)、integration.html、stub_worker、接口文档/实现说明、test_api

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 00:19:08 +08:00
xslandClaude 864d7f969a fix: torch.load 添加 weights_only=False 以兼容新版 PyTorch
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-09 23:44:48 +08:00
xslandClaude Opus 4.8 9675e147a0 接口5:新增 hairline_level 档位(middle/high/low)贴图 + 集成接口2生发能力
- hairline_level: 可选 middle(默认)/high/low,选用不同高度档位发际线贴图;
  新增 hairline_texture_high/low 两套贴图,get_texture_map 改为按档位缓存
- hair_style: 可选逗号分隔序号,对选中发际线类型同步生发(ComfyUI),
  结果合并进 hairline_images[].grown_image_base64(未选中为 null);
  生发黑模板固定取 middle(hairline_texture_black/),与档位无关
- 新增 use_mask/prompt 生发控制参数(同接口2)
- 测试页/接口文档同步更新

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 23:30:21 +08:00
xslandClaude Opus 4.8 0ddfa83743 接口11:发际线生发(接口9遮罩 + change_hair换发型/区域生发 + 按遮罩羽化贴回)+ 分步可视化测试页
- 端点 POST /api/v1/hairline/grow(app.py,纯新增,不影响接口1-5)
- 编排模块 face_analysis/hairline_grow.py:复用接口9 遮罩 → HTTP 调 change_hair(8801) → 按遮罩贴回
- 双生成后端 gen_backend:swaphair(换发型LoRA) / hairgrow(区域生发inpaint)
- swap_mode:ext_mask(接口9遮罩作换发型遮罩) / as_is;融合 feather/alpha_gradient/seamless
- 参数全在测试页可调;可视化按算法文档4步:最终遮罩→生成全帧→严格贴回→接缝融合
- 附启停脚本 scripts/restart_if11_backends.sh、算法文档、测试图

注:change_hair 侧 swapHair 的 ext_mask/denoising_strength 改造在 change_hair 仓库,向后兼容。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 22:06:17 +08:00
xslandClaude Opus 4.8 3eb60bddc5 接口10:头部外缘膨胀带遮罩 + 分步可视化
新增 POST /api/v1/head/band(worker + 网关代理)与测试页 test_interface10.html:
- 先和接口9 一样得到内缩后的基准遮罩(含额头的闭合区域外缘朝151内缩 erode_cm、
  底线不动,默认1.2cm),在此基础上取外轮廓线,去掉贴着底部分割线的那一段。
- 把外轮廓线膨胀成带子(总宽 dilate_cm,默认2cm;半径=总宽/2,虹膜标定换算)。
- 裁到分割线以上(不越过底线)。BiSeNet/SegFormer 两套并排对比。
- 两个可调参数 erode_cm + dilate_cm(页面数字框+滑块+localStorage)。
- 复用 head_mask 的构件,无新依赖;纯新增,不改动既有接口。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:32:45 +08:00
xslandClaude Opus 4.8 9774997035 接口9:头发遮罩生成 + 分步可视化
新增 POST /api/v1/head/mask(worker + 网关代理)与测试页 test_interface9.html:
- MediaPipe 关键点连成额头分割线(21,68,104,69,108,151,337,299,333,298,251,
  左端21/右端251 水平延伸到图片边缘),分割线以上为上半区。
- 头发分割 BiSeNet 与 SegFormer 两套并排对比;每列从最顶端头发向下填充到分割线,
  得到含额头的闭合区域(不从发际线割断)。
- 外缘朝中心点151内缩 erode_cm(默认1.2cm,页面可调,虹膜标定换算像素)、底线不动。
- 复用现有 detector/hair_segmenter/SegFormer 单例(只读推理),无新依赖;纯新增,
  不改动既有接口。

顺带修复接口2 遗留测试 test_grow_female_returns_5:hair_style 自 cb1989c 起必填,
补上 hair_style=1,2,3,4,5。全套 42 passed。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:09:29 +08:00
Ubuntu aa981229c0 添加统计请求的功能 2026-07-02 23:28:49 +08:00
Ubuntu 5d94edfc1a save code 2026-06-25 20:25:49 +08:00
xslandClaude Opus 4.8 a177dc2583 去除上传图片的分辨率与文件大小限制
- app.py: 删除全部 MAX_FILE_BYTES(≤1MB→1006) 与 MIN_SHORT_SIDE/MIN_LONG_SIDE
  (分辨率→1002) 校验及对应常量; 同步清理 File 描述、图片要求说明、
  错误码表(移除1002/1006)与过时示例
- gateway/app.py: 删除注释掉的 1006 大小校验块与描述里的 ≤1MB
- run_worker.sh / hair-worker.service: 删除临时放开限制的环境变量
- tests: 移除已过时的 test_oversize_1006 / test_lowres_1002 及 oversize_file fixture

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 22:21:39 +08:00
xslandClaude Opus 4.8 a9439e4975 接口1: 标注图人头最左/最右竖线改用耳朵分割外缘
- 最左/最右竖线由「头发轮廓」改为同一 BiSeNet 的耳朵类(7/8)外缘,
  看不到耳朵(被头发/侧脸遮挡→掩膜空)则该侧不画线
- 外耳轮廓常被误标成头发: 从耳朵外缘沿紧邻前景(耳∪发)按脸宽自适应
  外扩回收(上限 face_w*0.045, 遇背景间隙即停)
- 先按人脸包围盒裁剪再分割: BiSeNet 在紧裁人脸上训练, 整张全身/街拍图
  脸偏小会严重欠分割、丢耳朵; 裁剪后映射回原图, 耳朵稳定可分
- segment_hair_and_ears() 单次推理同出 hair_mask + ear_mask

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 21:36:23 +08:00
Ubuntu 5fd7d05d26 save code 2026-06-24 20:49:34 +08:00
Ubuntu e58ece0ada save code 2026-06-23 23:30:24 +08:00
xsl 57d1a876cc save code 2026-06-23 23:26:40 +08:00
xsl 6e8113b3ff save code 2026-06-23 22:46:24 +08:00
xsl 85f1ca521d 修改画线 2026-06-23 22:29:41 +08:00
xslandClaude 8cd44848d6 feat(接口6): 复刻接口1,新增 /api/v1/face/measure-v2
- 抽取 _face_measure_impl() 共用实现,接口1/6 零逻辑差异
- 接口6 路径 POST /api/v1/face/measure-v2
- 入参/出参与接口1 完全一致
- 文档同步更新(接口文档、实现说明、网关待改动)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-23 20:27:51 +08:00
UbuntuandClaude 76bae98073 feat(接口2/7): hair_style改为多选复选框,新增接口7测试页,网关注册v2路由
- 接口2/7测试页:发型从单选下拉框改为多选复选框+全选/全不选,提交时逗号分隔
- 新建static/test_interface7.html(基于接口2测试页,紫色主题,调用/api/v1/hair/grow-v2)
- gateway/app.py:注册/api/v1/hair/grow-v2代理路由 + 首页索引
- integration.html:新增接口7完整文档卡片(入参/出参/测试页链接)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-22 23:43:39 +08:00
xslandClaude 9678b54267 feat(接口2/7): hair_style 支持逗号分隔多选,如 1,2,3
- 参数类型从 int 改为 string(逗号分隔),自动解析去重排序
- 越界/非法值返回 1007
- _parse_hair_styles() 辅助函数:解析 + 去重 + 范围校验
- service 层 hair_style 参数改为 hair_styles: list[int]
- 接口2 和 接口7 同步更新
- 服务已重启,smoke test 通过

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-22 23:36:13 +08:00
xsl 1d23414d21 save code 2026-06-22 23:23:46 +08:00
xslandClaude cb1989c042 feat(接口2/7): 接口2加hair_style参数选单张发型;新增接口7用add_hair2工作流
接口2 变更:
- 新增必填 hair_style(int) 参数,按序号只生成一张(不再全量)
- female:1-5 male:1-4,越界返回1007

接口7 新增:
- POST /api/v1/hair/grow-v2,功能与接口2一致
- 使用 add_hair2.json 工作流(Flux-2 Klein 9b)
- SaveImage输出节点自动检测(75)

comfyui.py 重构:
- run() 支持 workflow_path 参数,多工作流按路径缓存
- SaveImage 输出节点自动检测,不再硬编码
- 输入/种子/提示词节点ID两个工作流相同(26/6/60)

文档:
- 接口文档、实现说明、网关待改动 三份同步更新
- 网关只需加一行路由,base64→URL改写无需改动

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-22 23:22:25 +08:00
xslandClaude Sonnet 4.6 28a6062fea feat(接口4): 精简输出为固定6个英文字段,缩短提示词
- face_features.py:_PROMPT 只问6项特征(+有无人脸),analyze_features
  只返回6个英文字段;无人脸返回 None(不再依赖 has_face 在外部判定)
- gateway/app.py:无脸判定改为 feats is None,去掉 has_face import
- app.py / docs / test_interface4.html:Swagger/文档/示例/测试页同步

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 22:19:00 +08:00
xsl be81567261 save code 2026-06-17 23:36:01 +08:00
Ubuntu 24da4eae6f save code 2026-06-17 23:27:34 +08:00
Ubuntu c15a3c2e15 save code 2026-06-17 23:04:49 +08:00
xslandClaude Opus 4.8 5311c8d7c7 feat(接口2/3): 加 use_mask 开关用于遮罩对比;网关回退纯透传
- 接口3 generate_grow_b 增加 use_mask(默认 true):false 跳过检测、
  直接送划线图(空遮罩)
- 接口2 generate_grow_results 增加 use_mask:false 用干净原图+空遮罩,
  只跑一次 ComfyUI、N 项复用
- app.py 接口2/3 增加 use_mask form 参数并透传
- gateway/app.py 回退为纯透传(移除 OpenAPI schema 注入,请求体原样转发)
- docs 补 use_mask 说明

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 23:01:12 +08:00
xsl 52f1bb4c0b 修改接口3 加入是否用遮罩接口 2026-06-17 22:27:06 +08:00
xsl 01397c414e save code 2026-06-16 21:59:13 +08:00
xsl 8d68ee323a save code 2026-06-16 21:16:44 +08:00
Ubuntu d2642b66f8 save code 2026-06-16 21:15:19 +08:00
xslandClaude Opus 4.8 a86125246e docs: 网关侧改动清单(JPG落盘扩展名嗅探等)
汇总 worker 近期变更里与网关有关的点供网关侧应用:
- [功能必需] base64→url 落盘按内容嗅探扩展名(PNG/JPG),因接口2/3/5 改 JPG(已在 forward.py 改)
- [建议] 生发接口超时≥120s
- [确认] 递归改写覆盖数组内字段/可空 null
- [可选] OpenAPI 表单 gender/grow-b 声明
- 已完成项:接口4 网关实现/200状态/接口3去original

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:44:41 +08:00
xslandClaude Opus 4.8 4c7681b338 perf(图片): 接口2/3/5 返回 JPG(体积~9×↓),接口1 标注图仍 PNG(透明)
- app.py: 接口2(预览+生发)/3(生发)/5(发际线叠图) 编码改 JPG(质量90,env JPG_QUALITY);
  接口1 annotated_image 含透明仍 PNG。_png_to_jpg_b64 把 ComfyUI 的 PNG 重编码为 JPG(无法解码则透传)
- gateway/forward.py: 落盘按内容嗅探扩展名(PNG头→.png 否则.jpg),原先硬编码 .png
- 测试/文档同步;实测接口5 一张 59KB(JPG) vs 548KB(PNG)。pytest 44 全绿

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:35:24 +08:00
xslandClaude Opus 4.8 e3c67fc8cf feat(comfyui): ComfyUI 改 8188 + HTTP Basic Auth
本机 ComfyUI 开了 Basic Auth(user admin),端口 8182→8188:
- hairline/comfyui.py: 默认 URL 8188;所有 httpx 请求带 auth=(user,password)
  密码来源 环境变量 COMFYUI_PASSWORD → worker_config.json.comfyui_password → password.txt
- password.txt 入 .gitignore(含密码不入 git);worker_config.example 加 comfyui_password
- 实现说明文档同步(8188 + Basic Auth)
实测:ping + 实跑一张生发图均通过(带鉴权)。pytest 44 全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 23:10:09 +08:00
Ubuntu a3f4c822cb feat: 补接口4测试页,更新索引和接入说明
- static/test_interface4.html: 上传照片 → 英文优先字段 + 全部42项特征表格 + 原始JSON
- gateway/app.py: 首页索引增加 if4_features 测试页
- static/integration.html: 接入说明补充接口4测试页链接
2026-06-15 21:36:40 +08:00
Ubuntu bee4bdae10 feat: 前端接入说明页 + 网关首页索引
- static/integration.html: 5接口入参/出参表、fetch示例、错误码、在线测试页链接
- gateway/app.py: 首页 / 增加 test_pages 和 integration_guide 索引
2026-06-15 21:26:56 +08:00
xslandClaude Opus 4.8 76f7c06905 review+docs: 接口4 回收到网关、对齐错误码、合并文档为一份实现说明
代码 review 后的清理:
- 接口4 由网关本机实现,worker app.py 的 /face/features 回退 Mock(保持 worker 无外网依赖);
  worker requirements 标注 volcengine 改为网关侧;移除 worker 的接口4 测试(随实现挪到网关)
- 网关接口4 业务错误 HTTP 状态统一改 200(与其余接口/worker 约定一致,原为400/503)
- 接口文档:gender 非法码 1004(原误写1008);修正指向已删文档的链接

文档合并:把各接口技术方案/开发任务书/系统架构/网关任务书 合并成 docs/实现说明.md(简要总览),
删除原 7 份分散文档,README 收敛为索引(实现说明/接口文档/需求/OFFLINE_ASSETS)。

pytest 44 全绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:58:33 +08:00
Ubuntu 68fe1c1406 feat(网关): 接口4 改由网关本机直接调豆包视觉模型,不再代理到 worker
- gateway/app.py: 接口4 handler 改为直接调用 face_features 模块
- face_features.py: API Key 读取支持 gateway/config.json
- gateway/config.example.json: 增加 ark_api_key 配置项
2026-06-15 20:46:12 +08:00
xslandClaude Opus 4.8 043a4c0603 docs: 接口4 网关侧实现方案(供网关开发照做)
接口4 不碰本地GPU/模型,仅调外网豆包→放网关本地实现更合理。文档含:
路由改本地处理(不转发)、httpx调方舟(OpenAI兼容,无需SDK)、prompt原文、
base64 data URI喂图、字段映射(6英文+全部中文)、无人脸→1001、配置(ark密钥入网关config)、
错误码、worker侧回收步骤、自测。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 20:33:50 +08:00
xslandClaude Opus 4.8 3008552331 docs(网关): 接口4 已实现说明(无图片字段透传+外网豆包调用耗时)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:51:02 +08:00
xslandClaude Opus 4.8 0e71830bb6 feat(接口4): 用户特征-火山方舟豆包视觉模型(替换Mock)
接口4 改用远程视觉大模型(算法来源 /home/xsl/fuyan),一次返回几十项面部特征。

- face_features.py: 火山方舟 Ark 客户端(单例)+doubao-seed-1-6-vision prompt(移植fuyan)
  +base64 data URI喂图(实测doubao接受)+解析JSON+映射6英文优先字段(face_shape等)
  并保留doubao全部中文字段;has_face 据"图片是否有人脸"判定
- app.py: /face/features 真实实现,三选一图(image_url直传doubao,file/base64转dataURI),
  features 为 JSON 字符串;无人脸→1001;重活线程池
- 配置: API Key 走 worker_config.json.ark_api_key / 环境变量 ARK_API_KEY(不入git);
  worker_config.example 加占位; requirements 加 volcengine-python-sdk[ark]
- 文档: 接口文档/OpenAPI 更新为豆包实现+几十项字段+1001
- 测试: mock doubao 的成功/无人脸/多参,47全绿

⚠️ 唯一调外网云模型的接口,worker 需可达 ark.cn-beijing.volces.com(已实测可达)。
实测 frontal: 42字段, 鹅蛋脸/平眉/18-25岁/静态型/女/少年型。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:50:17 +08:00
Ubuntu 795de68985 save code 2026-06-15 10:33:15 +08:00
xslandClaude Opus 4.8 bf76923591 refactor(接口3): 简化为只需划线图一张,去掉 original + best_hairline
按需求方意见——B端只需上传一张已画好发际线的图,用不着原图:
- 入参去掉 original_image_*,只保留 marked_image_*(三选一)
- 输出去掉 best_hairline_image_url,只返回 hair_growth_image_url + hairline_type
- ComfyUI 输入图改用 marked 划线图原样(add_hair.json 本就是"画了线的照片",
  提示词清除黑线);检测路径只用于建遮罩,不再重画干净线/不需对齐原图
- service.generate_grow_b 签名改 (marked_bgr) 单参
- 同步文档:接口文档/接口3技术方案/网关映射表(去掉接口3 best_hairline 行)
- 测试更新:grow-b 只传 marked,断言无 best_hairline 字段,44全绿

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 10:28:13 +08:00
Ubuntu cd4ca278d4 完成接口1 和接口2,测试页面也完成 2026-06-15 10:10:52 +08:00
xslandClaude Opus 4.8 d4e6a794d8 docs(接口2): 标注生发图已实现,清理"只做预览"过期文案
接口2 的生发后图片(§10 ComfyUI/Flux)已实现,更新早期"本期只做预览/不做生发"的描述:
- 技术方案:标题/§0/§9.6 改为"预览+生发两步均已实现",§9.6 列出后续优化方向
- 接口文档:接口2 说明改为"每方案返回 image_url(预览)+grown_image_url(生发图)"

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 09:47:42 +08:00
xslandClaude Opus 4.8 147fef6ca6 docs(网关): 补全 base64→url 映射表(接口1/2/3/5) + 嵌套数组/超时提醒
接口 1/2/3/5 已真实实现,补齐网关需要的字段映射,供网关开发参考:
- 网关任务书 §6:完整映射表(annotated_image / results[].image / results[].grown_image /
  best_hairline_image / hair_growth_image / hairline_images[].image);推荐"凡 *_base64 递归改写"
  通用实现;可空字段(生发图)保留 null;gender 等入参网关透传无需改造
- 网关任务书 §9:接口4 仍 mock;生发接口(2/3) ComfyUI 同步出图慢,request_timeout 调大≥120s
- 架构 §9:标注两个易漏点(数组内字段需递归、生发图可空)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:29:33 +08:00
xslandClaude Opus 4.8 38161d1b50 feat(接口5): 发际线PNG生成(真实实现,替换Mock)
复用接口2 预览管线:gender 必填 → 该性别全部发际线叠加图(同接口2预览) +
最佳(order=1)发际线曲线的面部中间点坐标。无生发(不调 ComfyUI)。

- hairline/service.py: generate_hairline_pngs——N张发际线叠图 +
  best_center(面部中轴眉心x × order1曲线在该处的y)
- app.py: /hairline/generate 真实实现,新增 gender 必填(非法→1004),
  返回 hairline_images[].image_base64 + best_hairline_center_point;
  无人脸→1001;重活线程池
- 接口文档/OpenAPI: 接口5 新增 gender 入参 + 输出改 base64(网关改url)
- 测试: test_api 接口5(gender必填/N张/center点),44全绿

实测(5090): female 5张叠图 + center{x,y},~2.5s(无Flux)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:20:56 +08:00
xslandClaude Opus 4.8 ce95a508c1 feat(接口3): B端生发-马克笔发际线检测+生发(替换Mock)
医生在额头用马克笔画规划发际线 → 检测该线 → 生发。检测算法源自 /home/xsl/headmark。

- hairline/marker_detect.py: 黑帽响应图(MORPH_BLACKHAT)+鬓角锚点(MediaPipe 21/251吸附)
  +skimage route_through_array 最小路径检测画线;路径平均响应阈值拒识无画线
  (headmark 调研:全局灰度阈值不可用,黑帽+Dijkstra 实测误差≤0.5px)
- hairline/mask.py: 抽出 mask_from_curve(曲线+ROI闭合),接口2/3共用
- hairline/service.py: generate_grow_b——检测→遮罩→原图重画干净线→ComfyUI生发
- app.py: /hair/grow-b 真实实现,marked+original各三选一+校验;输出
  best_hairline_image_base64(=原图)/hair_growth_image_base64/hairline_type="custom";
  无人脸或未检测到画线→1001;重活进线程池
- requirements: scikit-image==0.24.0 (⚠️锁0.24,0.25+强依赖numpy>=2会顶掉mediapipe的numpy<2)
- 文档: docs/接口3-B端生发-技术实现方案.md
- 测试: test_marker.py(检测/拒识/辅助) + test_api grow-b(mock ComfyUI),42全绿

实测(5090): grow-b ~6.4s,生发图把额头发际线补到医生画线、清除划线、人物保持。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:08:05 +08:00
xslandClaude Opus 4.8 94ad95850e feat(接口2): 新增生发后图片(ComfyUI/Flux inpaint)
在发际线预览基础上,每种发际线再出一张「植发3个月」生发图:

- hairline/mask.py: headmark 5步法遮罩(额头上部区域∩SegFormer头部=ROI,
  取发际线曲线以上闭合区域);用 hairline_texture_black 渲染黑线替代手绘检测;
  compose_comfy_rgba 合成 RGBA(alpha=255-mask, 透明=重绘区, 对齐 ComfyUI mask=1-alpha)
- hairline/comfyui.py: ComfyUI 客户端(默认8182),/upload/image+/prompt(改节点26+随机seed)
  +轮询/history+/view 取回生发图
- hairline/render.py: 抽出 build_overlay_layer 供遮罩取曲线像素
- hairline/service.py: extract_context 一次出 landmarks/parse_map/502点;
  generate_grow_results 每种=预览+生发图(同步串行N张,单张ComfyUI失败则grown置空不拖垮整请求)
- app.py: /hair/grow 返回 results[].grown_image_base64;重活放线程池避免卡事件循环
- add_hair.json 工作流 + hairline_texture_black/ 黑贴图入库
- 测试: test_mask.py(遮罩几何) + test_api mock ComfyUI 验 grown 字段,35 全绿

实测(5090): female 5张生发图同步约18s;预览/生发图人物五官服饰背景保持、黑线已清除。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 23:42:30 +08:00
xslandClaude Opus 4.8 4c69bb4623 docs(接口2): 新增生发图设计(§10 ComfyUI+Flux) + headmark遮罩算法
- 技术方案 §10:生发图生成管线(add_hair.json 工作流解读、遮罩算法、
  ComfyUI 客户端、契约变更、M5~M8 步骤、风险)
- 遮罩算法参考 /home/xsl/headmark 5步法,用 hairline_texture_black 渲染黑线
  替代手绘检测:额头上部区域 ∩ 头部分割 = ROI,取发际线曲线以上闭合区域
- 接口文档:results[] 新增 grown_image_url(生发后图片) + 同步/超时说明

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 23:32:21 +08:00
xslandClaude Opus 4.8 554b64a916 feat(接口2): C端生发发际线预览(真实实现,替换Mock)
第一步:按性别把发际线类型贴图渲染到照片,输出 N 张发际线叠加预览图。

- hairline/render.py: 解析 face_ext.obj(502 UV + 64 ribbon扩展面) + OpenCV 逐三角
  仿射 warp 渲染器;关键修复——face_ext.obj 是 OBJ序,用 INDEX_MAP_468 把 MP序
  502点重排成 OBJ序后再投影,否则 ribbon 会错贴到中脸
- hairline/service.py: FaceLandmarker+SegFormer 单例 + 性别贴图映射(扫描去空格)
  + generate_previews 管线(female5/male4)
- 集成点修复: face_landmarks DEFAULT_MODEL_PATH 改 hairline/models/;
  constants HF_FACE_PARSER_MODEL 改本地路径(离线)
- app.py: /api/v1/hair/grow 接真实实现,gender 必填(非法→1004),返回
  results[].image_base64(不落盘),校验/鉴权同接口1;lifespan 预热接口2单例;
  补 logging.basicConfig
- 依赖: transformers==4.45.2;SegFormer 权重走 hf-mirror 下载(见 OFFLINE_ASSETS)
- 测试: tests/test_hairline.py(mesh/重排/贴图映射) + test_api 接口2用例,31 全绿

注:SegFormer 受 5090/torch 限制走 CPU(~2.5s/张),换 cu128 可 SEG_DEVICE=cuda。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:31:41 +08:00
xslandClaude Opus 4.8 891bc0da8b feat(annotation): 线名移到右侧 + 线条只覆盖人脸范围
- 5 条横线的线名(头顶/发际线/眉心/鼻翼下缘/下巴尖)从左侧移到右侧
- 四庭 cm(顶庭/上庭/中庭/下庭)留在左侧,右对齐贴脸盒左缘
- 横线只覆盖脸宽(左右脸颊)、竖线只覆盖脸高(头顶→下巴),渐变消失
- 七眼段宽标注移到脸盒上端/下端(配合新线长),相邻段上下错行防重叠

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:58:12 +08:00
xslandClaude Opus 4.8 10488171eb feat(annotation): 标注图改版——横线标名+四庭cm,竖线标七眼段宽(顶/底)
按需求重排标注图:
- 去掉右侧全脸总高标尺,去掉单眼宽度/两眼间距/脸宽 三条横向标注
- 横向 5 条线标注线名(头顶/发际线/眉心/鼻翼下缘/下巴尖) + 保留左侧四庭 cm
- 新增纵向 6 条渐变竖线(左脸颊/左右眼内外角/右脸颊),切脸宽为 5 段
- 每段宽度在顶部和底部各标一次(只标 X.XXcm),相邻段上下错行防重叠
- 新增 draw_gradient_vertical_line 竖向渐变线
- test_api 用模块级 ACCEPT_PASSWORDS 固定测试密码,不依赖 worker_config.json

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 18:37:09 +08:00
xsl 9391408088 Merge branch 'main' of http://git.xiangsilian.com:3000/xsl/hair 2026-06-14 18:23:25 +08:00
Ubuntu eaafbc97ea save code 2026-06-14 18:23:08 +08:00
xslandClaude Opus 4.8 023bb2e6fd feat(worker): start.sh 改为后台服务开关(start/stop/restart/status)
- ./start.sh 现支持 start/stop/restart/status 子命令,无参默认 start
- 后台运行 + PID 文件(worker.pid) + 日志(worker.log),手动控制开关
- 前台热重载开发仍用 run_worker.sh
- gitignore 补 worker.pid / worker.log

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:47:49 +08:00
xslandClaude Opus 4.8 0eaaf05ac3 fix(annotation): 标注图重排,8 个量全标注且不重叠
- 修复七眼三个横向量(单眼宽度/两眼间距/脸宽)叠在同一条眼睛线上的重叠问题,
  改为上下错开三个高度,各自虚线带箭头 + 紧贴标签
- 新增「全脸总高」标注:右侧竖向标尺(虚线双箭头,头顶→下巴)+ 标签
- 四庭(顶/上/中/下庭)仍在左侧各段中点
- 标签用全名:单眼宽度/两眼间距/脸宽/全脸总高
- 新增 tests/test_annotation.py 防回归

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 17:33:58 +08:00
xsl 78226ae0b9 chore: SegFormer 模型不入库,改为手动下载
- .gitignore 排除 hairline/models/face-parsing/model.safetensors(~323MB)
- OFFLINE_ASSETS.md 更新:区分已入库文件和需手动下载文件,补充 SegFormer sha256
2026-06-14 17:19:24 +08:00
xsl e827b23c1d Merge branch 'main' of http://git.xiangsilian.com:3000/xsl/hair 2026-06-14 17:08:16 +08:00
xsl db32fa12e5 feat(接口2): 移植head3d发际线管线 + 接口2实现方案文档
- 从head3d复制发际线检测管线到 hairline/ 包:MediaPipe Tasks + SegFormer分割
  + 17锚点射线检测 + 502点mesh(face_ext.obj)+UV
- 复制模型:face_landmarker.task(3.7MB)、SegFormer config/preprocessor
  (model.safetensors 340MB 单独下载中)
- 新增 docs/接口2-C端生发-技术实现方案.md:第一步=发际线曲线叠加预览图,
  新增gender必填参数,按性别贴图数量输出(female5/male4),hairline_type英文key,
  服务端cv2逐三角形warp渲染器(head3d只有浏览器端Three.js渲染)
- 接口文档.md 接口2章节同步:gender参数、输出语义、错误码说明
- hairline_texture/ 9张发际线贴图入库
2026-06-14 16:59:39 +08:00
xsl ad1b95df4e Merge branch 'main' of http://git.xiangsilian.com:3000/xsl/hair 2026-06-14 16:49:39 +08:00
xslandClaude Opus 4.8 8d3b145111 feat(worker): 接口1 四庭七眼测量真实实现(替换 Mock)
worker 侧从 Mock 替换为真实算法:
- face_analysis 包:detector(MediaPipe 478点) / pose(solvePnP 姿态) /
  calibration(虹膜直径法) / hair_segmenter+bisenet_model(方案B 头发分割) /
  measure(方案A兜底+B/A决策+七眼+换算) / annotation(numpy渐变线+中文标注)
- app.py:/api/v1/face/measure 接真实实现,返回 annotated_image_base64
  (不落盘不拼URL,落盘由网关做);加 X-Internal-Token 鉴权、/health 就绪态、
  可配置分辨率门槛、异常兜底
- 部署:start.sh/run_worker.sh/hair-worker.service 监听 8187;worker_config 示例
- 测试 tests/:Tier1合成真值<1e-6 + Tier2缩放不变 + Tier3叠加 + 错误码集成 +
  数值回归,pytest 24 项全绿
- 文档补实测基线表 + RTX5090/torch 说明

注:worker 为 RTX 5090(sm_120),pinned torch 2.2.2(cu121) 只到 sm_90,
BiSeNet 已自动回退 CPU(方案B 正常);要用 GPU 需换 torch cu128(≥2.7)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 16:07:28 +08:00
xsl 5e513c7006 添加发型图片 2026-06-14 15:31:29 +08:00
Ubuntu 3b706ec0ef 网关开发完成 2026-06-14 15:04:34 +08:00
xsl bf68a32d9f docs: 拆分网关与worker文档,按机器分别开发
- 新增 网关-开发任务书.md: 独立自包含的网关侧任务书(骨架/健康检查派发/
  鉴权转发/base64改URL/静态托管/部署/DoD),在外网机开发
- worker任务书 v1.5: 移除§16网关章节,顶部标注本机职责,DoD只留worker侧,
  指向独立网关任务书
- 新增 docs/README.md: 文档索引,标清worker机/网关机/共享分别读哪些
2026-06-14 14:29:48 +08:00
xsl 2e789d4efa docs: 架构拆分为外网网关+高性能worker(GPU)
- 新增 系统架构-网关与高性能后端.md: 网关薄代理(健康检查/空闲派发/鉴权/
  base64落盘改URL/无后端→1007) + worker(GPU跑完整app)职责划分、配置文件、
  鉴权(共享密码可轮换)、标注图base64流程、部署、安全注意
- 关键约束: 接口文档不变; 每worker并发=1, 总并发=健康worker数; 多worker可配置
- 技术方案: 加运行位置横幅; torch改GPU(CUDA); handler返回annotated_image_base64
- 任务书v1.4: 标注差异说明; 阶段八返base64+鉴权中间件; 阶段十worker(GPU)部署;
  新增§16网关工作流; DoD拆分worker侧/网关侧
- OFFLINE_ASSETS: 区分worker(GPU torch)与网关(轻量)依赖
2026-06-14 13:55:45 +08:00
489 changed files with 37721 additions and 1527 deletions
+74
View File
@@ -4,7 +4,81 @@ __pycache__/
.env
# 网关配置(含密码,不入 git
gateway/config.json
# worker 配置(含鉴权密码,不入 git)
worker_config.json
# ComfyUI Basic Auth 密码(不入 git
password.txt
# worker 运行期文件(PID / 日志)
worker.pid
worker.log
# 请求日志(运行时生成,不入 git)
gateway/request_log.jsonl
gateway/request_log.jsonl.1
# SegFormer 模型权重(~323MB,体积过大,不入 git,见 OFFLINE_ASSETS.md
hairline/models/face-parsing/model.safetensors
# 生成的标注图、测试输出
static/annotations/*
!static/annotations/.gitkeep
tests/output/
# 本地临时遮罩测试页(不入 git)
test_local.py
# 运行期日志 / uvicorn 日志(不入 git
log/
uvicorn.log
uvicorn*.log
# ZCode 工具目录(不入 git
.zcode/
# 临时响应文件(不入 git
_grow*_resp.json
# 测试素材图(体积大,不入 git)
image/test/
# 批量报告输出(生成图+原图,体积大,不入 git)
static/report_hairline_v2/
static/report_hairline_v2.zip
# local_test 运行期日志 / pid(不入 git)
local_test/hair_service.log
local_test/hair_service.pid
# benchmark 原始输出(含结果图+原图,体积大,不入 git)
benchmark_out/
# benchmark 部署的 HTML 报告(图片 base64 内嵌,体积大,不入 git)
static/hairstyle_thumbs/
# 网关运行期日志(不入 git
gateway.log
# 工作流备份文件(不入 git
*.json.bak.*
# 脸型测试素材(人像照片,体积大,不入 git;仅保留 6 张基准标注图)
face/test_img/脸型测试集合/
face/test_img/girl/
face/test_img/man/
# 脸型特征缓存(由 face/dump_features.py 生成,可随时重跑)
face/cache/
# 脸型报告输出(标注图体积大,不入 git)
static/face_shape_report/
static/face_shape_report.html
static/facetest_report/
static/facetest_report.html
static/facetest_all_report/
static/facetest_all_report.html
+327
View File
@@ -0,0 +1,327 @@
{
"16": {
"class_type": "UNETLoader",
"inputs": {
"unet_name": "flux-2-klein-4b-fp8.safetensors",
"weight_dtype": "fp8_e4m3fn_fast"
}
},
"3": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "flux2-vae.safetensors"
}
},
"61": {
"class_type": "CLIPLoader",
"inputs": {
"clip_name": "qwen_3_4b.safetensors",
"type": "flux2",
"device": "cpu"
}
},
"26": {
"class_type": "LoadImage",
"inputs": {
"image": "placeholder.png"
}
},
"60": {
"class_type": "JjkText",
"inputs": {
"text": "填充遮罩区域的头发"
}
},
"22": {
"class_type": "CLIPTextEncode",
"inputs": {
"clip": [
"61",
0
],
"text": [
"60",
0
]
}
},
"31": {
"class_type": "easy imageSize",
"inputs": {
"image": [
"26",
0
]
}
},
"33": {
"class_type": "Mask Fill Holes",
"inputs": {
"masks": [
"26",
1
]
}
},
"36": {
"class_type": "Convert Masks to Images",
"inputs": {
"masks": [
"33",
0
]
}
},
"39": {
"class_type": "ImageScale",
"inputs": {
"image": [
"36",
0
],
"upscale_method": "nearest-exact",
"width": [
"31",
0
],
"height": [
"31",
1
],
"crop": "disabled"
}
},
"37": {
"class_type": "Image To Mask",
"inputs": {
"image": [
"39",
0
],
"method": "intensity"
}
},
"32": {
"class_type": "LayerUtility: ImageScaleByAspectRatio V2",
"inputs": {
"image": [
"26",
0
],
"mask": [
"37",
0
],
"aspect_ratio": "custom",
"proportional_width": [
"31",
0
],
"proportional_height": [
"31",
1
],
"fit": "letterbox",
"method": "lanczos",
"round_to_multiple": "8",
"scale_to_side": "None",
"scale_to_length": 1024,
"background_color": "#000000"
}
},
"44": {
"class_type": "ImageAndMaskPreview",
"inputs": {
"image": [
"32",
0
],
"mask": [
"32",
1
],
"mask_opacity": 1,
"mask_color": "FFFF00",
"pass_through": true
}
},
"14": {
"class_type": "GetImageSize+",
"inputs": {
"image": [
"44",
0
]
}
},
"13": {
"class_type": "VAEEncode",
"inputs": {
"pixels": [
"44",
0
],
"vae": [
"3",
0
]
}
},
"2": {
"class_type": "ModelSamplingFlux",
"inputs": {
"model": [
"16",
0
],
"max_shift": 1.15,
"base_shift": 0.5,
"width": [
"14",
0
],
"height": [
"14",
1
]
}
},
"19": {
"class_type": "FluxGuidance",
"inputs": {
"conditioning": [
"22",
0
],
"guidance": 1
}
},
"5": {
"class_type": "ReferenceLatent",
"inputs": {
"conditioning": [
"19",
0
],
"latent": [
"13",
0
]
}
},
"7": {
"class_type": "EmptySD3LatentImage",
"inputs": {
"width": [
"14",
0
],
"height": [
"14",
1
],
"batch_size": 1
}
},
"1": {
"class_type": "BasicScheduler",
"inputs": {
"model": [
"2",
0
],
"scheduler": "simple",
"steps": 4,
"denoise": 1
}
},
"20": {
"class_type": "BasicGuider",
"inputs": {
"model": [
"2",
0
],
"conditioning": [
"5",
0
]
}
},
"6": {
"class_type": "RandomNoise",
"inputs": {
"noise_seed": 0
}
},
"8": {
"class_type": "KSamplerSelect",
"inputs": {
"sampler_name": "euler"
}
},
"9": {
"class_type": "SamplerCustomAdvanced",
"inputs": {
"noise": [
"6",
0
],
"guider": [
"20",
0
],
"sampler": [
"8",
0
],
"sigmas": [
"1",
0
],
"latent_image": [
"7",
0
]
}
},
"10": {
"class_type": "VAEDecode",
"inputs": {
"samples": [
"9",
0
],
"vae": [
"3",
0
]
}
},
"62": {
"class_type": "ColorMatch",
"inputs": {
"image_ref": [
"26",
0
],
"image_target": [
"10",
0
],
"method": "mkl",
"strength": 1,
"multithread": true
}
},
"17": {
"class_type": "SaveImage",
"inputs": {
"images": [
"62",
0
],
"filename_prefix": "hair_inpaint"
}
}
}
+117
View File
@@ -0,0 +1,117 @@
{
"16": {"class_type": "UNETLoader", "inputs": {
"unet_name": "flux2.0/flux-2-klein-9b-fp8.safetensors",
"weight_dtype": "fp8_e4m3fn_fast"}},
"3": {"class_type": "VAELoader", "inputs": {
"vae_name": "flux2-vae.safetensors"}},
"61": {"class_type": "CLIPLoader", "inputs": {
"clip_name": "qwen_3_8b_fp8mixed.safetensors",
"type": "flux2",
"device": "default"}},
"26": {"class_type": "LoadImage", "inputs": {
"image": "placeholder.png"}},
"60": {"class_type": "JjkText", "inputs": {
"text": "填充遮罩区域的头发,皮肤加一点磨皮"}},
"22": {"class_type": "CLIPTextEncode", "inputs": {
"clip": ["61", 0],
"text": ["60", 0]}},
"31": {"class_type": "easy imageSize", "inputs": {
"image": ["26", 0]}},
"33": {"class_type": "Mask Fill Holes", "inputs": {
"masks": ["26", 1]}},
"36": {"class_type": "Convert Masks to Images", "inputs": {
"masks": ["33", 0]}},
"39": {"class_type": "ImageScale", "inputs": {
"image": ["36", 0],
"upscale_method": "nearest-exact",
"width": ["31", 0],
"height": ["31", 1],
"crop": "disabled"}},
"37": {"class_type": "Image To Mask", "inputs": {
"image": ["39", 0],
"method": "intensity"}},
"32": {"class_type": "LayerUtility: ImageScaleByAspectRatio V2", "inputs": {
"image": ["26", 0],
"mask": ["37", 0],
"aspect_ratio": "custom",
"proportional_width": ["31", 0],
"proportional_height": ["31", 1],
"fit": "letterbox",
"method": "lanczos",
"round_to_multiple": "8",
"scale_to_side": "None",
"scale_to_length": 1024,
"background_color": "#000000"}},
"44": {"class_type": "ImageAndMaskPreview", "inputs": {
"image": ["32", 0],
"mask": ["32", 1],
"mask_opacity": 1,
"mask_color": "FFFF00",
"pass_through": true}},
"14": {"class_type": "GetImageSize+", "inputs": {
"image": ["44", 0]}},
"13": {"class_type": "VAEEncode", "inputs": {
"pixels": ["44", 0],
"vae": ["3", 0]}},
"2": {"class_type": "ModelSamplingFlux", "inputs": {
"model": ["16", 0],
"max_shift": 1.15,
"base_shift": 0.5,
"width": ["14", 0],
"height": ["14", 1]}},
"19": {"class_type": "FluxGuidance", "inputs": {
"conditioning": ["22", 0],
"guidance": 1}},
"5": {"class_type": "ReferenceLatent", "inputs": {
"conditioning": ["19", 0],
"latent": ["13", 0]}},
"7": {"class_type": "EmptySD3LatentImage", "inputs": {
"width": ["14", 0],
"height": ["14", 1],
"batch_size": 1}},
"1": {"class_type": "BasicScheduler", "inputs": {
"model": ["2", 0],
"scheduler": "simple",
"steps": 4,
"denoise": 1}},
"20": {"class_type": "BasicGuider", "inputs": {
"model": ["2", 0],
"conditioning": ["5", 0]}},
"6": {"class_type": "RandomNoise", "inputs": {
"noise_seed": 0}},
"8": {"class_type": "KSamplerSelect", "inputs": {
"sampler_name": "euler"}},
"9": {"class_type": "SamplerCustomAdvanced", "inputs": {
"noise": ["6", 0],
"guider": ["20", 0],
"sampler": ["8", 0],
"sigmas": ["1", 0],
"latent_image": ["7", 0]}},
"10": {"class_type": "VAEDecode", "inputs": {
"samples": ["9", 0],
"vae": ["3", 0]}},
"62": {"class_type": "ColorMatch", "inputs": {
"image_ref": ["26", 0],
"image_target": ["10", 0],
"method": "mkl",
"strength": 1,
"multithread": true}},
"17": {"class_type": "SaveImage", "inputs": {
"images": ["62", 0],
"filename_prefix": "hair_inpaint"}}
}
File diff suppressed because one or more lines are too long
+42 -16
View File
@@ -1,19 +1,44 @@
# 离线资产清单(内网部署用)
开发/部署机在内网无法联网,以下模型权重与字体**已预先下载**放在仓库对应目录。
这些文件**不进 git**(见 `.gitignore`),通过文件拷贝(U盘/内网共享)随项目一起带到内网机即可。
## 已入库文件(git clone 后自动到位)
> 拷贝到内网机后,用本文件末尾的 sha256 校验完整性,确认未损坏。
## 文件清单
以下文件体积适中,已直接提交进 git,clone 仓库即可:
| 文件 | 路径 | 大小 | 用途 |
|------|------|------|------|
| BiSeNet 主权重 | `face_analysis/weights/79999_iter.pth` | 53,289,463 B (~53MB) | 人脸解析分割(方案 B,取真实发际线/头顶) |
| resnet18 骨干 | `face_analysis/weights/resnet18-5c106cde.pth` | 46,827,520 B (~45MB) | BiSeNet 骨干网络**内网必需**(见下) |
| 中文字体 | `face_analysis/fonts/NotoSansCJKsc-Regular.otf` | 16,437,364 B (~16MB) | 标注图中文渲染= 思源黑体,同一套字体) |
| BiSeNet 主权重 | `face_analysis/weights/79999_iter.pth` | ~53MB | 人脸解析分割(接口1方案B,取真实发际线/头顶) |
| resnet18 骨干 | `face_analysis/weights/resnet18-5c106cde.pth` | ~45MB | BiSeNet 骨干网络 |
| 中文字体 | `face_analysis/fonts/NotoSansCJKsc-Regular.otf` | ~16MB | 标注图中文渲染 |
| MediaPipe 模型 | `hairline/models/face_landmarker.task` | ~3.7MB | 468点人脸关键点检测(接口2) |
## sha256 校验
## 需手动下载文件(体积过大,不入 git)
### SegFormer 人脸分割模型(接口2必需)
| 文件 | 路径 | 大小 |
|------|------|------|
| model.safetensors | `hairline/models/face-parsing/model.safetensors` | 338,580,732 B (~323MB) |
下载命令(国内用 hf-mirror 镜像,快很多):
```bash
# 国内镜像(推荐)
curl -L -o hairline/models/face-parsing/model.safetensors \
"https://hf-mirror.com/jonathandinu/face-parsing/resolve/main/model.safetensors"
# 官方源
# curl -L -o hairline/models/face-parsing/model.safetensors \
# "https://huggingface.co/jonathandinu/face-parsing/resolve/main/model.safetensors"
```
sha256 校验:
```
c2bec795a8c243db71bd95be538fd62559003566466c71237e45c99b920f4b62 hairline/models/face-parsing/model.safetensors
```
```bash
sha256sum hairline/models/face-parsing/model.safetensors
```
## sha256 校验(已入库文件)
```
468e13ca13a9b43cc0881a9f99083a430e9c0a38abd935431d1c28ee94b26567 face_analysis/weights/79999_iter.pth
@@ -21,19 +46,13 @@
2c76254f6fc379fddfce0a7e84fb5385bb135d3e399294f6eeb6680d0365b74b face_analysis/fonts/NotoSansCJKsc-Regular.otf
```
校验命令:
```bash
# Linux/macOS
sha256sum -c <<'EOF'
468e13ca13a9b43cc0881a9f99083a430e9c0a38abd935431d1c28ee94b26567 face_analysis/weights/79999_iter.pth
5c106cde386e87d4033832f2996f5493238eda96ccf559d1d62760c4de0613f8 face_analysis/weights/resnet18-5c106cde.pth
2c76254f6fc379fddfce0a7e84fb5385bb135d3e399294f6eeb6680d0365b74b face_analysis/fonts/NotoSansCJKsc-Regular.otf
EOF
```
```powershell
# Windows PowerShell
Get-FileHash face_analysis\weights\79999_iter.pth -Algorithm SHA256
```
> resnet18 文件名内嵌的 `5c106cde` 即其官方 sha256 前 8 位(torchvision 命名惯例),与上表一致 = 官方权重无误。
@@ -55,11 +74,18 @@ BiSeNet 初始化时会调用 `torch.utils.model_zoo` / `torchvision` **联网
79999_iter.pth https://huggingface.co/ManyOtherFunctions/face-parse-bisent/resolve/main/79999_iter.pth
resnet18-5c106cde.pth https://download.pytorch.org/models/resnet18-5c106cde.pth
NotoSansCJKsc-Regular.otf https://github.com/notofonts/noto-cjk/raw/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf
model.safetensors https://huggingface.co/jonathandinu/face-parsing/resolve/main/model.safetensors
```
## 还差什么(pip 依赖)
模型已就位,但**内网机还需要 Python 依赖的离线 wheel 包**`mediapipe`/`opencv-python`/`torch` CPU 版等),否则 `pip install` 在内网无法联网安装。这部分**与目标机的操作系统Python 版本强相关**,需确认后单独打包:见仓库提交说明或联系下载方补充 `wheels/` 目录。
模型已就位,但**内网机还需要 Python 依赖的离线 wheel 包**,否则 `pip install` 在内网无法联网安装。这部分**与目标机的操作系统Python 版本、CUDA 版本强相关**,需确认后单独打包:
- **workerGPU 机)**`mediapipe` / `opencv-python` / `numpy<2` / `Pillow` / **`torch`+`torchvision` 的 CUDA 版**(按 GPU 的 CUDA 版本选 cu118/cu121 等)/ `transformers`(接口2 SegFormer+ FastAPI/uvicorn 全家桶。
- **网关机**FastAPI/uvicorn/httpx 等代理依赖 + **接口4 现需 `mediapipe`/`opencv-python`/`numpy<2`**(脸型本机计算),
仍**不需要 torch**(无 GPU 推理需求)。
> 架构已拆分(见 `docs/实现说明.md`):算法依赖只装在 worker,网关保持轻量。
---
+450
View File
@@ -0,0 +1,450 @@
{
"1": {
"inputs": {
"scheduler": "simple",
"steps": 4,
"denoise": 1,
"model": [
"2",
0
]
},
"class_type": "BasicScheduler",
"_meta": {
"title": "基本调度器"
}
},
"2": {
"inputs": {
"max_shift": 1.15,
"base_shift": 0.5,
"width": [
"14",
0
],
"height": [
"14",
1
],
"model": [
"16",
0
]
},
"class_type": "ModelSamplingFlux",
"_meta": {
"title": "采样算法(Flux"
}
},
"3": {
"inputs": {
"vae_name": "flux2-vae.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "加载VAE"
}
},
"5": {
"inputs": {
"conditioning": [
"19",
0
],
"latent": [
"13",
0
]
},
"class_type": "ReferenceLatent",
"_meta": {
"title": "参考Latent"
}
},
"6": {
"inputs": {
"noise_seed": 217742615722421
},
"class_type": "RandomNoise",
"_meta": {
"title": "随机噪波"
}
},
"7": {
"inputs": {
"width": [
"14",
0
],
"height": [
"14",
1
],
"batch_size": 1
},
"class_type": "EmptySD3LatentImage",
"_meta": {
"title": "空Latent图像(SD3"
}
},
"8": {
"inputs": {
"sampler_name": "euler"
},
"class_type": "KSamplerSelect",
"_meta": {
"title": "K采样器选择"
}
},
"9": {
"inputs": {
"noise": [
"6",
0
],
"guider": [
"20",
0
],
"sampler": [
"8",
0
],
"sigmas": [
"1",
0
],
"latent_image": [
"7",
0
]
},
"class_type": "SamplerCustomAdvanced",
"_meta": {
"title": "自定义采样器(高级)"
}
},
"10": {
"inputs": {
"samples": [
"9",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"13": {
"inputs": {
"pixels": [
"44",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEEncode",
"_meta": {
"title": "VAE编码"
}
},
"14": {
"inputs": {
"image": [
"44",
0
]
},
"class_type": "GetImageSize+",
"_meta": {
"title": "🔧 Get Image Size"
}
},
"16": {
"inputs": {
"unet_name": "flux-2-klein-4b-fp8.safetensors",
"weight_dtype": "fp8_e4m3fn"
},
"class_type": "UNETLoader",
"_meta": {
"title": "UNet加载器"
}
},
"17": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"62",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "保存图像"
}
},
"19": {
"inputs": {
"guidance": 1,
"conditioning": [
"22",
0
]
},
"class_type": "FluxGuidance",
"_meta": {
"title": "Flux引导"
}
},
"20": {
"inputs": {
"model": [
"2",
0
],
"conditioning": [
"5",
0
]
},
"class_type": "BasicGuider",
"_meta": {
"title": "基本引导器"
}
},
"22": {
"inputs": {
"text": [
"60",
0
],
"clip": [
"61",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码"
}
},
"26": {
"inputs": {
"image": "clipspace/clipspace-painted-masked-1781655058499.png [input]"
},
"class_type": "LoadImage",
"_meta": {
"title": "加载图像"
}
},
"31": {
"inputs": {
"image": [
"26",
0
]
},
"class_type": "easy imageSize",
"_meta": {
"title": "图像尺寸"
}
},
"32": {
"inputs": {
"aspect_ratio": "custom",
"proportional_width": [
"31",
0
],
"proportional_height": [
"31",
1
],
"fit": "letterbox",
"method": "lanczos",
"round_to_multiple": "8",
"scale_to_side": "None",
"scale_to_length": 1024,
"background_color": "#000000",
"image": [
"26",
0
],
"mask": [
"37",
0
]
},
"class_type": "LayerUtility: ImageScaleByAspectRatio V2",
"_meta": {
"title": "LayerUtility: ImageScaleByAspectRatio V2"
}
},
"33": {
"inputs": {
"masks": [
"26",
1
]
},
"class_type": "Mask Fill Holes",
"_meta": {
"title": "Mask Fill Holes"
}
},
"36": {
"inputs": {
"masks": [
"33",
0
]
},
"class_type": "Convert Masks to Images",
"_meta": {
"title": "Convert Masks to Images"
}
},
"37": {
"inputs": {
"method": "intensity",
"image": [
"39",
0
]
},
"class_type": "Image To Mask",
"_meta": {
"title": "Image To Mask"
}
},
"39": {
"inputs": {
"upscale_method": "nearest-exact",
"width": [
"31",
0
],
"height": [
"31",
1
],
"crop": "disabled",
"image": [
"36",
0
]
},
"class_type": "ImageScale",
"_meta": {
"title": "缩放图像"
}
},
"44": {
"inputs": {
"mask_opacity": 1,
"mask_color": "FFFF00",
"pass_through": true,
"image": [
"32",
0
],
"mask": [
"32",
1
]
},
"class_type": "ImageAndMaskPreview",
"_meta": {
"title": "ImageAndMaskPreview"
}
},
"45": {
"inputs": {
"images": [
"44",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "预览图像"
}
},
"53": {
"inputs": {
"rgthree_comparer": {
"images": [
{
"name": "A",
"selected": true,
"url": "/api/view?filename=rgthree.compare._temp_zuixa_00119_.png&type=temp&subfolder=&rand=0.7926013627811991"
},
{
"name": "B",
"selected": true,
"url": "/api/view?filename=rgthree.compare._temp_zuixa_00120_.png&type=temp&subfolder=&rand=0.43692946883795813"
}
]
},
"image_a": [
"62",
0
],
"image_b": [
"26",
0
]
},
"class_type": "Image Comparer (rgthree)",
"_meta": {
"title": "Image Comparer (rgthree)"
}
},
"60": {
"inputs": {
"text": "填充遮罩区域的头发"
},
"class_type": "JjkText",
"_meta": {
"title": "Text"
}
},
"61": {
"inputs": {
"clip_name": "qwen_3_4b.safetensors",
"type": "flux2",
"device": "cpu"
},
"class_type": "CLIPLoader",
"_meta": {
"title": "加载CLIP"
}
},
"62": {
"inputs": {
"method": "mkl",
"strength": 1,
"multithread": true,
"image_ref": [
"26",
0
],
"image_target": [
"10",
0
]
},
"class_type": "ColorMatch",
"_meta": {
"title": "Color Match"
}
}
}
+450
View File
@@ -0,0 +1,450 @@
{
"1": {
"inputs": {
"scheduler": "simple",
"steps": 6,
"denoise": 1,
"model": [
"2",
0
]
},
"class_type": "BasicScheduler",
"_meta": {
"title": "基本调度器"
}
},
"2": {
"inputs": {
"max_shift": 1.15,
"base_shift": 0.5,
"width": [
"14",
0
],
"height": [
"14",
1
],
"model": [
"16",
0
]
},
"class_type": "ModelSamplingFlux",
"_meta": {
"title": "采样算法(Flux"
}
},
"3": {
"inputs": {
"vae_name": "flux2-vae.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "加载VAE"
}
},
"5": {
"inputs": {
"conditioning": [
"19",
0
],
"latent": [
"13",
0
]
},
"class_type": "ReferenceLatent",
"_meta": {
"title": "参考Latent"
}
},
"6": {
"inputs": {
"noise_seed": 217742615722421
},
"class_type": "RandomNoise",
"_meta": {
"title": "随机噪波"
}
},
"7": {
"inputs": {
"width": [
"14",
0
],
"height": [
"14",
1
],
"batch_size": 1
},
"class_type": "EmptySD3LatentImage",
"_meta": {
"title": "空Latent图像(SD3"
}
},
"8": {
"inputs": {
"sampler_name": "euler"
},
"class_type": "KSamplerSelect",
"_meta": {
"title": "K采样器选择"
}
},
"9": {
"inputs": {
"noise": [
"6",
0
],
"guider": [
"20",
0
],
"sampler": [
"8",
0
],
"sigmas": [
"1",
0
],
"latent_image": [
"7",
0
]
},
"class_type": "SamplerCustomAdvanced",
"_meta": {
"title": "自定义采样器(高级)"
}
},
"10": {
"inputs": {
"samples": [
"9",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"13": {
"inputs": {
"pixels": [
"44",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEEncode",
"_meta": {
"title": "VAE编码"
}
},
"14": {
"inputs": {
"image": [
"44",
0
]
},
"class_type": "GetImageSize+",
"_meta": {
"title": "🔧 Get Image Size"
}
},
"16": {
"inputs": {
"unet_name": "flux2.0/flux-2-klein-9b-fp8.safetensors",
"weight_dtype": "fp8_e4m3fn"
},
"class_type": "UNETLoader",
"_meta": {
"title": "UNet加载器"
}
},
"17": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"62",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "保存图像"
}
},
"19": {
"inputs": {
"guidance": 1,
"conditioning": [
"22",
0
]
},
"class_type": "FluxGuidance",
"_meta": {
"title": "Flux引导"
}
},
"20": {
"inputs": {
"model": [
"2",
0
],
"conditioning": [
"5",
0
]
},
"class_type": "BasicGuider",
"_meta": {
"title": "基本引导器"
}
},
"22": {
"inputs": {
"text": [
"60",
0
],
"clip": [
"61",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码"
}
},
"26": {
"inputs": {
"image": "clipspace/clipspace-painted-masked-1781655058499.png [input]"
},
"class_type": "LoadImage",
"_meta": {
"title": "加载图像"
}
},
"31": {
"inputs": {
"image": [
"26",
0
]
},
"class_type": "easy imageSize",
"_meta": {
"title": "图像尺寸"
}
},
"32": {
"inputs": {
"aspect_ratio": "custom",
"proportional_width": [
"31",
0
],
"proportional_height": [
"31",
1
],
"fit": "letterbox",
"method": "lanczos",
"round_to_multiple": "8",
"scale_to_side": "None",
"scale_to_length": 1024,
"background_color": "#000000",
"image": [
"26",
0
],
"mask": [
"37",
0
]
},
"class_type": "LayerUtility: ImageScaleByAspectRatio V2",
"_meta": {
"title": "LayerUtility: ImageScaleByAspectRatio V2"
}
},
"33": {
"inputs": {
"masks": [
"26",
1
]
},
"class_type": "Mask Fill Holes",
"_meta": {
"title": "Mask Fill Holes"
}
},
"36": {
"inputs": {
"masks": [
"33",
0
]
},
"class_type": "Convert Masks to Images",
"_meta": {
"title": "Convert Masks to Images"
}
},
"37": {
"inputs": {
"method": "intensity",
"image": [
"39",
0
]
},
"class_type": "Image To Mask",
"_meta": {
"title": "Image To Mask"
}
},
"39": {
"inputs": {
"upscale_method": "nearest-exact",
"width": [
"31",
0
],
"height": [
"31",
1
],
"crop": "disabled",
"image": [
"36",
0
]
},
"class_type": "ImageScale",
"_meta": {
"title": "缩放图像"
}
},
"44": {
"inputs": {
"mask_opacity": 1,
"mask_color": "FFFF00",
"pass_through": true,
"image": [
"32",
0
],
"mask": [
"32",
1
]
},
"class_type": "ImageAndMaskPreview",
"_meta": {
"title": "ImageAndMaskPreview"
}
},
"45": {
"inputs": {
"images": [
"44",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "预览图像"
}
},
"53": {
"inputs": {
"rgthree_comparer": {
"images": [
{
"name": "A",
"selected": true,
"url": "/api/view?filename=rgthree.compare._temp_zuixa_00119_.png&type=temp&subfolder=&rand=0.7926013627811991"
},
{
"name": "B",
"selected": true,
"url": "/api/view?filename=rgthree.compare._temp_zuixa_00120_.png&type=temp&subfolder=&rand=0.43692946883795813"
}
]
},
"image_a": [
"62",
0
],
"image_b": [
"26",
0
]
},
"class_type": "Image Comparer (rgthree)",
"_meta": {
"title": "Image Comparer (rgthree)"
}
},
"60": {
"inputs": {
"text": "补充遮罩区补充遮罩区域内的头发,头发填满遮罩区域。发际线往下挡住额头"
},
"class_type": "JjkText",
"_meta": {
"title": "Text"
}
},
"61": {
"inputs": {
"clip_name": "qwen_3_8b_fp8mixed.safetensors",
"type": "flux2",
"device": "default"
},
"class_type": "CLIPLoader",
"_meta": {
"title": "加载CLIP"
}
},
"62": {
"inputs": {
"method": "mkl",
"strength": 1,
"multithread": true,
"image_ref": [
"26",
0
],
"image_target": [
"10",
0
]
},
"class_type": "ColorMatch",
"_meta": {
"title": "Color Match"
}
}
}
+714
View File
@@ -0,0 +1,714 @@
{
"1": {
"inputs": {
"scheduler": "simple",
"steps": 6,
"denoise": 0.9,
"model": [
"2",
0
]
},
"class_type": "BasicScheduler",
"_meta": {
"title": "基本调度器"
}
},
"2": {
"inputs": {
"max_shift": 1.15,
"base_shift": 0.5,
"width": [
"14",
0
],
"height": [
"14",
1
],
"model": [
"16",
0
]
},
"class_type": "ModelSamplingFlux",
"_meta": {
"title": "采样算法(Flux"
}
},
"3": {
"inputs": {
"vae_name": "flux2-vae.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "加载VAE"
}
},
"5": {
"inputs": {
"conditioning": [
"19",
0
],
"latent": [
"64",
0
]
},
"class_type": "ReferenceLatent",
"_meta": {
"title": "参考Latent"
}
},
"6": {
"inputs": {
"noise_seed": 176834207514884
},
"class_type": "RandomNoise",
"_meta": {
"title": "随机噪波"
}
},
"7": {
"inputs": {
"width": [
"14",
0
],
"height": [
"14",
1
],
"batch_size": 1
},
"class_type": "EmptySD3LatentImage",
"_meta": {
"title": "空Latent图像(SD3"
}
},
"8": {
"inputs": {
"sampler_name": "euler"
},
"class_type": "KSamplerSelect",
"_meta": {
"title": "K采样器选择"
}
},
"9": {
"inputs": {
"noise": [
"6",
0
],
"guider": [
"20",
0
],
"sampler": [
"8",
0
],
"sigmas": [
"1",
0
],
"latent_image": [
"64",
0
]
},
"class_type": "SamplerCustomAdvanced",
"_meta": {
"title": "自定义采样器(高级)"
}
},
"10": {
"inputs": {
"samples": [
"9",
0
],
"vae": [
"3",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"14": {
"inputs": {
"image": [
"44",
0
]
},
"class_type": "GetImageSize+",
"_meta": {
"title": "🔧 Get Image Size"
}
},
"16": {
"inputs": {
"unet_name": "flux-2-klein-4b-fp8.safetensors",
"weight_dtype": "fp8_e4m3fn"
},
"class_type": "UNETLoader",
"_meta": {
"title": "UNet加载器"
}
},
"19": {
"inputs": {
"guidance": 1.5,
"conditioning": [
"22",
0
]
},
"class_type": "FluxGuidance",
"_meta": {
"title": "Flux引导"
}
},
"20": {
"inputs": {
"model": [
"2",
0
],
"conditioning": [
"5",
0
]
},
"class_type": "BasicGuider",
"_meta": {
"title": "基本引导器"
}
},
"22": {
"inputs": {
"text": [
"60",
0
],
"clip": [
"61",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码"
}
},
"26": {
"inputs": {
"image": "clipspace/clipspace-painted-masked-1781933242779.png [input]"
},
"class_type": "LoadImage",
"_meta": {
"title": "加载图像"
}
},
"31": {
"inputs": {
"image": [
"26",
0
]
},
"class_type": "easy imageSize",
"_meta": {
"title": "图像尺寸"
}
},
"32": {
"inputs": {
"aspect_ratio": "custom",
"proportional_width": [
"31",
0
],
"proportional_height": [
"31",
1
],
"fit": "letterbox",
"method": "bicubic",
"round_to_multiple": "8",
"scale_to_side": "None",
"scale_to_length": 1024,
"background_color": "#000000",
"image": [
"26",
0
],
"mask": [
"89",
0
]
},
"class_type": "LayerUtility: ImageScaleByAspectRatio V2",
"_meta": {
"title": "图层工具:按宽高比缩放 V2"
}
},
"33": {
"inputs": {
"masks": [
"26",
1
]
},
"class_type": "Mask Fill Holes",
"_meta": {
"title": "Mask Fill Holes"
}
},
"36": {
"inputs": {
"masks": [
"33",
0
]
},
"class_type": "Convert Masks to Images",
"_meta": {
"title": "Convert Masks to Images"
}
},
"37": {
"inputs": {
"method": "intensity",
"image": [
"39",
0
]
},
"class_type": "Image To Mask",
"_meta": {
"title": "Image To Mask"
}
},
"39": {
"inputs": {
"upscale_method": "nearest-exact",
"width": [
"31",
0
],
"height": [
"31",
1
],
"crop": "disabled",
"image": [
"36",
0
]
},
"class_type": "ImageScale",
"_meta": {
"title": "缩放图像"
}
},
"44": {
"inputs": {
"mask_opacity": 1,
"mask_color": "FFFF00",
"pass_through": true,
"image": [
"32",
0
],
"mask": [
"32",
1
]
},
"class_type": "ImageAndMaskPreview",
"_meta": {
"title": "ImageAndMaskPreview"
}
},
"45": {
"inputs": {
"images": [
"44",
0
]
},
"class_type": "PreviewImage",
"_meta": {
"title": "预览图像"
}
},
"53": {
"inputs": {
"rgthree_comparer": {
"images": [
{
"name": "A",
"selected": true,
"url": "/api/view?filename=rgthree.compare._temp_plnpv_00023_.png&type=temp&subfolder=&rand=0.8949967564686747"
},
{
"name": "B",
"selected": true,
"url": "/api/view?filename=rgthree.compare._temp_plnpv_00024_.png&type=temp&subfolder=&rand=0.6013833933740399"
}
]
},
"image_a": [
"73",
0
],
"image_b": [
"26",
0
]
},
"class_type": "Image Comparer (rgthree)",
"_meta": {
"title": "Image Comparer (rgthree)"
}
},
"60": {
"inputs": {
"text": "填充遮罩区域的头发"
},
"class_type": "JjkText",
"_meta": {
"title": "Text"
}
},
"61": {
"inputs": {
"clip_name": "qwen_3_4b.safetensors",
"type": "flux2",
"device": "default"
},
"class_type": "CLIPLoader",
"_meta": {
"title": "加载CLIP"
}
},
"64": {
"inputs": {
"grow_mask_by": 3,
"vae": [
"3",
0
],
"images": [
"44",
0
],
"masks": [
"32",
1
]
},
"class_type": "VAEEncodeForInpaint_Frames",
"_meta": {
"title": "VAE Encode For Inpaint Frames ♾️Mixlab"
}
},
"68": {
"inputs": {
"sampler_name": "euler"
},
"class_type": "KSamplerSelect",
"_meta": {
"title": "K采样器选择"
}
},
"69": {
"inputs": {
"vae_name": "flux2-vae.safetensors"
},
"class_type": "VAELoader",
"_meta": {
"title": "加载VAE"
}
},
"70": {
"inputs": {
"cfg": 1,
"model": [
"86",
0
],
"positive": [
"85",
0
],
"negative": [
"80",
0
]
},
"class_type": "CFGGuider",
"_meta": {
"title": "CFG引导器"
}
},
"71": {
"inputs": {
"noise_seed": 1105558556688843
},
"class_type": "RandomNoise",
"_meta": {
"title": "随机噪波"
}
},
"72": {
"inputs": {
"noise": [
"71",
0
],
"guider": [
"70",
0
],
"sampler": [
"68",
0
],
"sigmas": [
"77",
0
],
"latent_image": [
"81",
0
]
},
"class_type": "SamplerCustomAdvanced",
"_meta": {
"title": "自定义采样器(高级)"
}
},
"73": {
"inputs": {
"samples": [
"72",
0
],
"vae": [
"69",
0
]
},
"class_type": "VAEDecode",
"_meta": {
"title": "VAE解码"
}
},
"75": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"73",
0
]
},
"class_type": "SaveImage",
"_meta": {
"title": "保存图像"
}
},
"76": {
"inputs": {
"rgthree_comparer": {
"images": [
{
"name": "A",
"selected": true,
"url": "/api/view?filename=rgthree.compare._temp_ssjll_00009_.png&type=temp&subfolder=&rand=0.8998219800410978"
},
{
"name": "B",
"selected": true,
"url": "/api/view?filename=rgthree.compare._temp_ssjll_00010_.png&type=temp&subfolder=&rand=0.2100153657131264"
}
]
},
"image_a": [
"73",
0
],
"image_b": [
"26",
0
]
},
"class_type": "Image Comparer (rgthree)",
"_meta": {
"title": "Image Comparer (rgthree)"
}
},
"77": {
"inputs": {
"steps": 6,
"width": [
"83",
1
],
"height": [
"83",
1
]
},
"class_type": "Flux2Scheduler",
"_meta": {
"title": "Flux2调度器"
}
},
"78": {
"inputs": {
"clip_name": "qwen_3_8b_fp8mixed.safetensors",
"type": "flux2",
"device": "default"
},
"class_type": "CLIPLoader",
"_meta": {
"title": "加载CLIP"
}
},
"79": {
"inputs": {
"conditioning": [
"87",
0
]
},
"class_type": "ConditioningZeroOut",
"_meta": {
"title": "条件零化"
}
},
"80": {
"inputs": {
"conditioning": [
"79",
0
],
"latent": [
"81",
0
]
},
"class_type": "ReferenceLatent",
"_meta": {
"title": "参考Latent"
}
},
"81": {
"inputs": {
"pixels": [
"10",
0
],
"vae": [
"69",
0
]
},
"class_type": "VAEEncode",
"_meta": {
"title": "VAE编码"
}
},
"82": {
"inputs": {
"width": [
"83",
0
],
"height": [
"83",
1
],
"batch_size": 1
},
"class_type": "EmptyFlux2LatentImage",
"_meta": {
"title": "空Latent图像(Flux2"
}
},
"83": {
"inputs": {
"image": [
"10",
0
]
},
"class_type": "GetImageSize",
"_meta": {
"title": "获取图像尺寸"
}
},
"85": {
"inputs": {
"conditioning": [
"87",
0
],
"latent": [
"81",
0
]
},
"class_type": "ReferenceLatent",
"_meta": {
"title": "参考Latent"
}
},
"86": {
"inputs": {
"unet_name": "flux2.0/flux-2-klein-9b-fp8.safetensors",
"weight_dtype": "default"
},
"class_type": "UNETLoader",
"_meta": {
"title": "UNet加载器"
}
},
"87": {
"inputs": {
"text": "填充遮罩区域的头发",
"clip": [
"78",
0
]
},
"class_type": "CLIPTextEncode",
"_meta": {
"title": "CLIP文本编码"
}
},
"89": {
"inputs": {
"left": 5,
"top": 15,
"right": 5,
"bottom": 15,
"mask": [
"37",
0
]
},
"class_type": "FeatherMask",
"_meta": {
"title": "羽化遮罩"
}
}
}
+1417 -124
View File
File diff suppressed because it is too large Load Diff
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
"""9B vs 4B 模型对比:3 张图 × 5 种发型 = 15 张对比图。
第一次运行:PLAN_TAG=A (9B 模型,需先切工作流到 .bak)
第二次运行:PLAN_TAG=Bplus4B 模型,需切回 4B 工作流)
"""
import json, os, time, base64, subprocess, urllib.request
from datetime import datetime
API_BASE = "http://127.0.0.1:8187"
TOKEN = "dev-shared-secret-2026"
IMAGES = [
"/home/ubuntu/hair/image/girl_img/girl1.jpg",
"/home/ubuntu/hair/image/girl_img/girl7.jpg",
"/home/ubuntu/hair/image/girl_img/girl13.jpg",
]
PLAN_TAG = os.getenv("PLAN_TAG", "A")
OUT_DIR = f"/home/ubuntu/hair/benchmark_out/9b_vs_4b_{PLAN_TAG}"
RESULT_JSON = os.path.join(OUT_DIR, "results.json")
REPORT_HTML = os.path.join(OUT_DIR, "report.html")
HAIR_STYLES = [(1, "椭圆"), (2, "花瓣"), (3, "心形"), (4, "直线"), (5, "波浪")]
WARMUP = os.getenv("WARMUP", "1") == "1" # 第一次调用做预热
os.makedirs(OUT_DIR, exist_ok=True)
def get_vram():
try:
out = subprocess.check_output(
["nvidia-smi", "--query-gpu=memory.used,memory.free",
"--format=csv,noheader,nounits"], text=True, timeout=5).strip()
used, free = out.split(",")
return int(used.strip()), int(free.strip())
except Exception:
return -1, -1
def _multipart(fields, files=None):
boundary = "----CmpTest" + str(int(time.time() * 1000))
parts = []
for k, v in fields.items():
parts.append(f"--{boundary}\r\n".encode())
parts.append(f'Content-Disposition: form-data; name="{k}"\r\n\r\n'.encode())
parts.append(str(v).encode())
parts.append(b"\r\n")
if files:
for field_name, (filename, data, mime) in files.items():
parts.append(f"--{boundary}\r\n".encode())
parts.append(f'Content-Disposition: form-data; name="{field_name}"; filename="{filename}"\r\n'.encode())
parts.append(f"Content-Type: {mime}\r\n\r\n".encode())
parts.append(data)
parts.append(b"\r\n")
parts.append(f"--{boundary}--\r\n".encode())
return b"".join(parts), boundary
def call_iface2(image_path, hair_style):
with open(image_path, "rb") as f:
img_data = f.read()
fields = {"gender": "female", "hair_style": str(hair_style),
"prompt": "填充遮罩区域的头发"}
body, boundary = _multipart(
fields, {"image_file": (os.path.basename(image_path), img_data, "image/jpeg")})
req = urllib.request.Request(f"{API_BASE}/api/v1/hair/grow", data=body, method="POST")
req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")
req.add_header("X-Internal-Token", TOKEN)
t0 = time.time()
try:
with urllib.request.urlopen(req, timeout=600) as resp:
data = json.loads(resp.read())
return data, time.time() - t0, None
except Exception as e:
return {}, time.time() - t0, str(e)
def extract_image(data, img_idx, hs):
"""从响应提取 grown_image_base64 并保存为 jpg。"""
if not data or not data.get("data"):
return None
items = data["data"].get("results") or []
if not items:
return None
b64 = items[0].get("grown_image_base64") or ""
if b64.startswith("data:"):
b64 = b64.split(",", 1)[1]
if not b64:
return None
fname = f"img{img_idx}_style{hs}.jpg"
path = os.path.join(OUT_DIR, fname)
with open(path, "wb") as f:
f.write(base64.b64decode(b64))
return path
def run_test():
results = []
print(f"9B vs 4B 对比测试 — Plan={PLAN_TAG}")
print(f"图片: {[os.path.basename(p) for p in IMAGES]}")
print(f"发型: {[(s,n) for s,n in HAIR_STYLES]}")
print(f"总调用: {len(IMAGES)*len(HAIR_STYLES)}\n")
# 预热请求(避免第一次冷启动计入统计)
if WARMUP:
print("[warmup] 预热请求 (girl13, style=1) ...")
t0 = time.time()
_, warmup_time, _ = call_iface2(IMAGES[2], 1)
print(f" warmup: {warmup_time:.1f}s\n")
for img_idx, img_path in enumerate(IMAGES, start=1):
img_name = os.path.basename(img_path)
for hs, hs_name in HAIR_STYLES:
print(f"[img{img_idx}/{len(IMAGES)}] {img_name} style={hs}({hs_name}) ...")
vram_before, _ = get_vram()
data, elapsed, error = call_iface2(img_path, hs)
vram_after, _ = get_vram()
code = data.get("code", -1) if data else -1
ok = (code == 0)
img_saved = extract_image(data, img_idx, hs) if ok else None
record = {
"plan": PLAN_TAG,
"image_idx": img_idx,
"image_name": img_name,
"image_path": img_path,
"hair_style": hs,
"hair_style_name": hs_name,
"timestamp": datetime.now().strftime("%H:%M:%S"),
"elapsed_s": round(elapsed, 2),
"success": ok,
"code": code,
"error": error,
"vram_before_mb": vram_before,
"vram_after_mb": vram_after,
"vram_delta_mb": vram_after - vram_before,
"saved_image_path": img_saved,
}
results.append(record)
status = "" if ok else ""
print(f"{status} code={code} time={elapsed:.1f}s "
f"vram={vram_before}{vram_after}MB (Δ{vram_after-vram_before:+d}) "
f"img={'saved' if img_saved else 'none'}")
with open(RESULT_JSON, "w") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
print(f"\n✅ 完成!{RESULT_JSON}")
print(f"📊 成功: {sum(1 for r in results if r['success'])}/{len(results)}")
if any(r['success'] for r in results):
avg = sum(r['elapsed_s'] for r in results if r['success']) / sum(1 for r in results if r['success'])
print(f"⏱ 平均: {avg:.2f}s")
if __name__ == "__main__":
run_test()
+450
View File
@@ -0,0 +1,450 @@
#!/usr/bin/env python3
"""基准测试:19张图片 × 5种发际线,记录每步耗时和显存变化。
用法: python3 benchmark_grow.py
输出: benchmark_results.json + benchmark_report.html
"""
import json, os, time, base64, subprocess, re, glob, shutil
from datetime import datetime
from pathlib import Path
import urllib.request, urllib.error
API_URL = "http://127.0.0.1:8187/api/v1/hair/grow"
TOKEN = "dev-shared-secret-2026"
IMG_DIR = "/home/ubuntu/hair/image/girl_img"
OUT_DIR = "/home/ubuntu/hair/benchmark_out"
RESULT_JSON = os.path.join(OUT_DIR, "benchmark_results.json")
REPORT_HTML = os.path.join(OUT_DIR, "benchmark_report.html")
HAIRLINES = [
("1", "ellipse", "椭圆形"),
("2", "flower", "花瓣形"),
("3", "heart", "心形"),
("4", "straight", "直线形"),
("5", "wave", "波浪形"),
]
os.makedirs(OUT_DIR, exist_ok=True)
# ── 工具函数 ──────────────────────────────────────────────
def get_vram():
"""返回 (used_MB, free_MB)"""
try:
out = subprocess.check_output(
["nvidia-smi", "--query-gpu=memory.used,memory.free",
"--format=csv,noheader,nounits"], text=True, timeout=5
).strip()
used, free = out.split(",")
return int(used.strip()), int(free.strip())
except Exception:
return -1, -1
def get_gpu_procs():
"""返回各进程显存占用 dict"""
try:
out = subprocess.check_output(
["nvidia-smi", "--query-compute-apps=pid,used_memory",
"--format=csv,noheader,nounits"], text=True, timeout=5
).strip()
procs = {}
for line in out.splitlines():
parts = line.split(",")
if len(parts) >= 2:
procs[parts[0].strip()] = int(parts[1].strip())
return procs
except Exception:
return {}
def read_worker_log_tail(n=50):
"""读取 worker.log 最后 n 行"""
log_path = "/home/ubuntu/hair/worker.log"
try:
result = subprocess.run(["tail", "-n", str(n), log_path],
capture_output=True, text=True, timeout=5)
return result.stdout
except Exception:
return ""
def call_api(image_path, hair_style_value):
"""调用接口2,返回 (json_dict, elapsed_sec, error_or_None)"""
import mimetypes
boundary = "----BenchmarkBoundary" + str(int(time.time()*1000))
filename = os.path.basename(image_path)
mime = mimetypes.guess_type(image_path)[0] or "image/jpeg"
with open(image_path, "rb") as f:
img_data = f.read()
body_parts = []
body_parts.append(f"--{boundary}\r\n".encode())
body_parts.append(f'Content-Disposition: form-data; name="image_file"; filename="{filename}"\r\n'.encode())
body_parts.append(f"Content-Type: {mime}\r\n\r\n".encode())
body_parts.append(img_data)
body_parts.append(f"\r\n--{boundary}\r\n".encode())
body_parts.append(b'Content-Disposition: form-data; name="gender"\r\n\r\n')
body_parts.append(b"female")
body_parts.append(f"\r\n--{boundary}\r\n".encode())
body_parts.append(b'Content-Disposition: form-data; name="hair_style"\r\n\r\n')
body_parts.append(hair_style_value.encode())
body_parts.append(f"\r\n--{boundary}\r\n".encode())
body_parts.append(b'Content-Disposition: form-data; name="use_mask"\r\n\r\n')
body_parts.append(b"0")
body_parts.append(f"\r\n--{boundary}\r\n".encode())
body_parts.append(b'Content-Disposition: form-data; name="prompt"\r\n\r\n')
body_parts.append(b"\xe5\xa1\xab\xe5\x85\x85\xe9\x81\xae\xe7\xbd\xa9\xe5\x8c\xba\xe5\x9f\x9f\xe7\x9a\x84\xe5\xa4\xb4\xe5\x8f\x91\xef\xbc\x8c\xe7\x9a\xae\xe8\x82\xa4\xe5\x8a\xa0\xe4\xb8\x80\xe7\x82\xb9\xe7\xa3\xa8\xe7\x9a\xae")
body_parts.append(f"\r\n--{boundary}--\r\n".encode())
body = b"".join(body_parts)
req = urllib.request.Request(API_URL, data=body, method="POST")
req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")
req.add_header("X-Internal-Token", TOKEN)
t0 = time.time()
try:
with urllib.request.urlopen(req, timeout=600) as resp:
raw = resp.read()
elapsed = time.time() - t0
data = json.loads(raw)
return data, elapsed, None
except urllib.error.HTTPError as e:
elapsed = time.time() - t0
try:
data = json.loads(e.read())
except Exception:
data = {"error": str(e)}
return data, elapsed, f"HTTP {e.code}"
except Exception as e:
elapsed = time.time() - t0
return {}, elapsed, str(e)
def parse_step_timing(log_text):
"""从 worker.log 文本中解析步骤耗时"""
timing = {}
for line in log_text.splitlines():
# 匹配 "步骤1 遮罩完成 耗时=949ms"
m = re.search(r"步骤(\d+)\s+\S+\s+耗时=(\d+)ms", line)
if m:
timing[f"step{m.group(1)}_ms"] = int(m.group(2))
# 匹配 "人脸检出 px_per_cm=30.669 图尺寸=813x967"
if "人脸检出" in line:
m2 = re.search(r"px_per_cm=([\d.]+)", line)
if m2:
timing["px_per_cm"] = float(m2.group(1))
# 匹配 "头发分割完成"
if "头发分割完成" in line:
timing["hair_seg"] = True
# 匹配 "换发型图失败"
if "换发型图失败" in line or "换发型服务不可达" in line:
timing["swap_error"] = line.strip()[-100:]
# 匹配 "重绘" 相关
if "重绘" in line and ("完成" in line or "失败" in line):
timing["redraw_status"] = "完成" if "完成" in line else "失败"
return timing
# ── 主流程 ────────────────────────────────────────────────
def run_benchmark():
images = sorted(glob.glob(os.path.join(IMG_DIR, "girl*.jpg")))
print(f"找到 {len(images)} 张图片")
# 加载已有结果(支持断点续跑)
results = []
if os.path.exists(RESULT_JSON):
with open(RESULT_JSON) as f:
results = json.load(f)
print(f"已有 {len(results)} 条记录,继续未完成的测试")
total_calls = len(images) * len(HAIRLINES)
done = len(results)
print(f"总计 {total_calls} 次调用,已完成 {done},剩余 {total_calls - done}")
log_offset = 0
try:
log_offset = subprocess.check_output(["wc", "-l", "/home/ubuntu/hair/worker.log"],
text=True, timeout=5).split()[0]
log_offset = int(log_offset)
except Exception:
pass
for img_idx, img_path in enumerate(images):
img_name = os.path.basename(img_path)
# 跳过已完成的图片
img_results = [r for r in results if r["image"] == img_name]
if len(img_results) >= len(HAIRLINES):
print(f"[{img_idx+1}/{len(images)}] {img_name} 已完成,跳过")
continue
for hl_id, hl_key, hl_label in HAIRLINES:
# 跳过已完成的
existing = [r for r in results if r["image"] == img_name and r["hairline_id"] == hl_id]
if existing:
continue
print(f"\n[{img_idx+1}/{len(images)}] {img_name}{hl_label}({hl_id}) ...")
# 记录 worker.log 行数
try:
log_before = int(subprocess.check_output(
["wc", "-l", "/home/ubuntu/hair/worker.log"], text=True, timeout=5).split()[0])
except Exception:
log_before = 0
# VRAM before
vram_before, vram_free_before = get_vram()
procs_before = get_gpu_procs()
t_start = time.time()
# 调用API
api_result, elapsed, error = call_api(img_path, hl_id)
t_end = time.time()
# VRAM after
vram_after, vram_free_after = get_vram()
procs_after = get_gpu_procs()
# 读取新日志
try:
log_diff = subprocess.check_output(
["tail", "-n", "+{}".format(log_before + 1), "/home/ubuntu/hair/worker.log"],
text=True, timeout=5)
except Exception:
log_diff = ""
step_timing = parse_step_timing(log_diff)
# 提取结果图片
preview_b64 = ""
grown_b64 = ""
api_code = api_result.get("code", -1)
api_msg = api_result.get("message", "")
api_results = api_result.get("data", {}).get("results", [])
if api_results:
r0 = api_results[0]
preview_b64 = r0.get("image_base64", "")
grown_b64 = r0.get("grown_image_base64", "")
# 保存缩略图
thumb_dir = os.path.join(OUT_DIR, "thumbs")
os.makedirs(thumb_dir, exist_ok=True)
if grown_b64:
grown_bytes = base64.b64decode(grown_b64)
thumb_path = os.path.join(thumb_dir, f"{img_name}_hl{hl_id}_grown.jpg")
with open(thumb_path, "wb") as f:
f.write(grown_bytes)
if preview_b64:
preview_bytes = base64.b64decode(preview_b64)
thumb_path = os.path.join(thumb_dir, f"{img_name}_hl{hl_id}_preview.png")
with open(thumb_path, "wb") as f:
f.write(preview_bytes)
record = {
"image": img_name,
"image_idx": img_idx + 1,
"hairline_id": hl_id,
"hairline_key": hl_key,
"hairline_label": hl_label,
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"total_time_s": round(elapsed, 2),
"api_code": api_code,
"api_msg": api_msg,
"error": error,
"has_preview": bool(preview_b64),
"has_grown": bool(grown_b64),
"preview_size": len(preview_b64),
"grown_size": len(grown_b64),
"vram_before_mb": vram_before,
"vram_after_mb": vram_after,
"vram_free_before_mb": vram_free_before,
"vram_free_after_mb": vram_free_after,
"vram_delta_mb": vram_after - vram_before,
"procs_before": procs_before,
"procs_after": procs_after,
"step_timing": step_timing,
}
results.append(record)
print(f" → HTTP code={api_code} time={elapsed:.1f}s vram={vram_before}{vram_after}MB "
f"preview={'' if preview_b64 else ''} grown={'' if grown_b64 else ''}")
# 保存中间结果
with open(RESULT_JSON, "w") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
# 生成中间报告
generate_html(results)
# 最终报告
generate_html(results)
print(f"\n✅ 完成!结果: {RESULT_JSON}")
print(f"📄 报告: {REPORT_HTML}")
print(f"📊 总调用: {len(results)}/{total_calls}")
def generate_html(results):
"""生成HTML报告"""
# 统计
total = len(results)
success = sum(1 for r in results if r["has_grown"])
failed = total - success
times = [r["total_time_s"] for r in results if r["has_grown"]]
avg_time = sum(times) / len(times) if times else 0
max_time = max(times) if times else 0
min_time = min(times) if times else 0
# 按发际线类型分组统计
hl_stats = {}
for r in results:
if r["has_grown"]:
hl = r["hairline_label"]
if hl not in hl_stats:
hl_stats[hl] = {"count": 0, "times": []}
hl_stats[hl]["count"] += 1
hl_stats[hl]["times"].append(r["total_time_s"])
# 按图片分组
img_groups = {}
for r in results:
img = r["image"]
if img not in img_groups:
img_groups[img] = []
img_groups[img].append(r)
# 生成VRAM变化数据
vram_data = [(i, r["vram_after_mb"]) for i, r in enumerate(results)]
html = f"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>接口2 基准测试报告</title>
<style>
* {{ margin:0; padding:0; box-sizing:border-box; }}
body {{ font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif; background:#1a1a2e; color:#e0e0e0; padding:20px; }}
h1 {{ text-align:center; margin-bottom:20px; color:#00d4ff; }}
.subtitle {{ text-align:center; color:#888; margin-bottom:30px; font-size:14px; }}
.summary {{ display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:15px; margin-bottom:30px; }}
.card {{ background:#16213e; border-radius:12px; padding:20px; text-align:center; border:1px solid #333; }}
.card .num {{ font-size:32px; font-weight:700; }}
.card .label {{ font-size:12px; color:#888; margin-top:5px; }}
.card.ok .num {{ color:#0f0; }}
.card.err .num {{ color:#f44; }}
.card.time .num {{ color:#00d4ff; }}
table {{ width:100%; border-collapse:collapse; margin-bottom:30px; background:#16213e; border-radius:12px; overflow:hidden; }}
th {{ background:#0f3460; padding:12px 8px; text-align:center; font-size:13px; color:#fff; }}
td {{ padding:8px; text-align:center; border-bottom:1px solid #222; font-size:13px; }}
tr:hover {{ background:#1a1a3e; }}
.img-cell {{ text-align:left; }}
.time-bar {{ display:inline-block; height:20px; background:linear-gradient(90deg,#0f3460,#00d4ff); border-radius:4px; vertical-align:middle; min-width:2px; }}
.hl-badge {{ display:inline-block; padding:2px 8px; border-radius:10px; font-size:11px; font-weight:600; }}
.hl-ellipse {{ background:#1b4332; color:#52b788; }}
.hl-flower {{ background:#3a0ca3; color:#c77dff; }}
.hl-heart {{ background:#6a040f; color:#ff6b6b; }}
.hl-straight {{ background:#0077b6; color:#90e0ef; }}
.hl-wave {{ background:#9d4edd; color:#e0aaff; }}
.ok {{ color:#0f0; }} .fail {{ color:#f44; }}
.vram-chart {{ margin:20px 0; }}
.vram-bars {{ display:flex; align-items:flex-end; height:120px; gap:2px; padding:10px; background:#0d1117; border-radius:8px; }}
.vram-bar {{ flex:1; background:linear-gradient(180deg,#00d4ff,#0f3460); border-radius:2px 2px 0 0; min-height:2px; position:relative; }}
.vram-bar:hover::after {{ content:attr(data-val) 'MB'; position:absolute; bottom:100%; left:50%; transform:translateX(-50%); background:#333; padding:2px 6px; border-radius:4px; font-size:10px; white-space:nowrap; }}
.section-title {{ font-size:18px; font-weight:600; margin:30px 0 15px; color:#00d4ff; border-bottom:1px solid #333; padding-bottom:10px; }}
.hl-stats {{ display:grid; grid-template-columns:repeat(5,1fr); gap:15px; margin-bottom:20px; }}
.hl-card {{ background:#16213e; border-radius:12px; padding:15px; text-align:center; }}
.hl-card .avg {{ font-size:24px; font-weight:700; color:#00d4ff; }}
.hl-card .minmax {{ font-size:11px; color:#888; margin-top:5px; }}
.thumb {{ max-width:100px; max-height:100px; border-radius:4px; cursor:pointer; }}
.thumb:hover {{ transform:scale(2); transition:transform 0.3s; }}
</style>
</head>
<body>
<h1>📊 接口2 基准测试报告</h1>
<div class="subtitle">生成时间:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} {total} 次调用</div>
<div class="summary">
<div class="card ok"><div class="num">{success}</div><div class="label">成功</div></div>
<div class="card err"><div class="num">{failed}</div><div class="label">失败</div></div>
<div class="card time"><div class="num">{avg_time:.1f}s</div><div class="label">平均耗时</div></div>
<div class="card time"><div class="num">{min_time:.1f}s</div><div class="label">最快</div></div>
<div class="card time"><div class="num">{max_time:.1f}s</div><div class="label">最慢</div></div>
</div>
"""
# 按发际线类型统计
if hl_stats:
html += '<div class="section-title">按发际线类型统计</div><div class="hl-stats">'
for hl_label in ["椭圆形","花瓣形","心形","直线形","波浪形"]:
if hl_label in hl_stats:
s = hl_stats[hl_label]
times = s["times"]
avg = sum(times) / len(times)
mn, mx = min(times), max(times)
cls = hl_label
html += f'<div class="hl-card"><div class="hl-badge hl-{{cls}}">{hl_label}</div><div class="avg">{avg:.1f}s</div><div class="minmax">{mn:.1f}~{mx:.1f}s ({s["count"]}次)</div></div>'
else:
html += f'<div class="hl-card"><div class="hl-badge">{hl_label}</div><div class="avg">-</div><div class="minmax">未完成</div></div>'
html += '</div>'
# VRAM 变化图
if vram_data:
max_vram = max(v for _, v in vram_data if v > 0) or 1
html += '<div class="section-title">显存变化</div><div class="vram-chart"><div class="vram-bars">'
for i, (_, vram) in enumerate(vram_data):
if vram > 0:
h = int(vram / max_vram * 100)
html += f'<div class="vram-bar" style="height:{h}%" data-val="{vram}" title="{i+1}"></div>'
else:
html += f'<div class="vram-bar" style="height:0%" data-val="0"></div>'
html += '</div></div>'
# 详细结果表格
html += '<div class="section-title">详细结果</div><table><thead><tr>'
html += '<th>#</th><th>图片</th><th>发际线</th><th>总耗时</th><th>遮罩步骤</th>'
html += '<th>显存前</th><th>显存后</th><th>显存变化</th>'
html += '<th>预览图</th><th>生发图</th><th>状态</th>'
html += '</tr></thead><tbody>'
for i, r in enumerate(results):
hl_cls = r["hairline_key"]
status = '<span class="ok">✓ 成功</span>' if r["has_grown"] else f'<span class="fail">✗ {r.get("error","")}</span>'
step_ms = r.get("step_timing", {}).get("step1_ms", "")
step_str = f"{step_ms}ms" if step_ms else "-"
vram_d = r["vram_delta_mb"]
vram_d_str = f'<span style="color:{"#f44" if vram_d>0 else "#0f0"}">{"+" if vram_d>=0 else ""}{vram_d}</span>'
# 缩略图
thumb_grown = ""
if r["has_grown"]:
thumb_path = f"thumbs/{r['image']}_hl{r['hairline_id']}_grown.jpg"
if os.path.exists(os.path.join(OUT_DIR, thumb_path)):
thumb_grown = f'<img class="thumb" src="{thumb_path}">'
html += f"""<tr>
<td>{i+1}</td>
<td class="img-cell">{r['image']}</td>
<td><span class="hl-badge hl-{hl_cls}">{r['hairline_label']}</span></td>
<td><div class="time-bar" style="width:{min(r['total_time_s'],300)}px"></div> {r['total_time_s']:.1f}s</td>
<td>{step_str}</td>
<td>{r['vram_before_mb']}MB</td>
<td>{r['vram_after_mb']}MB</td>
<td>{vram_d_str}</td>
<td>{'' if r['has_preview'] else ''}</td>
<td>{thumb_grown if thumb_grown else ('' if r['has_grown'] else '')}</td>
<td>{status}</td>
</tr>"""
html += '</tbody></table>'
html += '</body></html>'
with open(REPORT_HTML, "w", encoding="utf-8") as f:
f.write(html)
if __name__ == "__main__":
run_benchmark()
+201
View File
@@ -0,0 +1,201 @@
#!/usr/bin/env python3
"""接口2女性5种发型对比测试:Plan B vs Plan B+。
测试 hair_style=1..5(椭圆/花瓣/心形/直线/波浪),每种发型一次,记录耗时+显存+生成图。
"""
import json, os, time, base64, subprocess, urllib.request, urllib.error
from datetime import datetime
API_BASE = "http://127.0.0.1:8187"
TOKEN = "dev-shared-secret-2026"
GIRL_IMG = "/home/ubuntu/hair/image/girl_img/girl13.jpg"
PLAN_TAG = os.getenv("PLAN_TAG", "Bplus") # Bplus / B
OUT_DIR = f"/home/ubuntu/hair/benchmark_out/iface2_female_{PLAN_TAG}"
RESULT_JSON = os.path.join(OUT_DIR, "results.json")
REPORT_HTML = os.path.join(OUT_DIR, "report.html")
HAIR_STYLE_NAMES = {
"1": "椭圆", "2": "花瓣", "3": "心形", "4": "直线", "5": "波浪",
}
os.makedirs(OUT_DIR, exist_ok=True)
def get_vram():
try:
out = subprocess.check_output(
["nvidia-smi", "--query-gpu=memory.used,memory.free",
"--format=csv,noheader,nounits"], text=True, timeout=5).strip()
used, free = out.split(",")
return int(used.strip()), int(free.strip())
except Exception:
return -1, -1
def _multipart(fields, files=None):
boundary = "----If2Test" + str(int(time.time() * 1000))
parts = []
for k, v in fields.items():
parts.append(f"--{boundary}\r\n".encode())
parts.append(f'Content-Disposition: form-data; name="{k}"\r\n\r\n'.encode())
parts.append(str(v).encode())
parts.append(b"\r\n")
if files:
for field_name, (filename, data, mime) in files.items():
parts.append(f"--{boundary}\r\n".encode())
parts.append(f'Content-Disposition: form-data; name="{field_name}"; filename="{filename}"\r\n'.encode())
parts.append(f"Content-Type: {mime}\r\n\r\n".encode())
parts.append(data)
parts.append(b"\r\n")
parts.append(f"--{boundary}--\r\n".encode())
return b"".join(parts), boundary
def call_iface2_female(image_path, hair_style):
"""接口2 女性 + 指定发型(走 swapHair 路径)"""
with open(image_path, "rb") as f:
img_data = f.read()
fields = {"gender": "female", "hair_style": str(hair_style),
"prompt": "填充遮罩区域的头发"}
body, boundary = _multipart(
fields, {"image_file": (os.path.basename(image_path), img_data, "image/jpeg")})
req = urllib.request.Request(f"{API_BASE}/api/v1/hair/grow", data=body, method="POST")
req.add_header("Content-Type", f"multipart/form-data; boundary={boundary}")
req.add_header("X-Internal-Token", TOKEN)
t0 = time.time()
try:
with urllib.request.urlopen(req, timeout=600) as resp:
data = json.loads(resp.read())
return data, time.time() - t0, None
except Exception as e:
return {}, time.time() - t0, str(e)
def run_test():
results = []
print(f"接口2女性5种发型测试 — Plan={PLAN_TAG}")
print(f"图片:{GIRL_IMG}\n")
for hs in range(1, 6):
name = HAIR_STYLE_NAMES[str(hs)]
print(f"\n[{hs}/5] hair_style={hs} ({name}) ...")
vram_before, vram_free_before = get_vram()
data, elapsed, error = call_iface2_female(GIRL_IMG, hs)
vram_after, vram_free_after = get_vram()
code = data.get("code", -1) if data else -1
ok = (code == 0)
# 保存生成图(如果有)
img_path = None
if ok and data.get("data"):
try:
items = data["data"].get("results") or []
if items and isinstance(items, list):
first = items[0]
b64 = first.get("grown_image_base64") or ""
if b64.startswith("data:"):
b64 = b64.split(",", 1)[1]
if b64:
img_path = os.path.join(OUT_DIR, f"hair_style_{hs}_{name}.jpg")
with open(img_path, "wb") as f:
f.write(base64.b64decode(b64))
except Exception as e:
print(f" 保存图片失败: {e}")
record = {
"plan": PLAN_TAG,
"hair_style": hs,
"hair_style_name": name,
"timestamp": datetime.now().strftime("%H:%M:%S"),
"elapsed_s": round(elapsed, 2),
"success": ok,
"code": code,
"error": error,
"vram_before_mb": vram_before,
"vram_after_mb": vram_after,
"vram_delta_mb": vram_after - vram_before,
"image_path": img_path,
}
results.append(record)
status = "" if ok else ""
print(f"{status} code={code} time={elapsed:.1f}s "
f"vram={vram_before}{vram_after}MB (Δ{vram_after-vram_before:+d}) "
f"img={'saved' if img_path else 'none'}")
with open(RESULT_JSON, "w") as f:
json.dump(results, f, indent=2, ensure_ascii=False)
generate_html(results)
generate_html(results)
print(f"\n✅ 完成!{RESULT_JSON}")
print(f"📄 报告:{REPORT_HTML}")
def generate_html(results):
total = len(results)
success = sum(1 for r in results if r["success"])
times = [r["elapsed_s"] for r in results if r["success"]]
avg_time = sum(times) / len(times) if times else 0
html = f"""<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><title>接口2女5种发型 - Plan {PLAN_TAG}</title>
<style>
*{{margin:0;padding:0;box-sizing:border-box}}
body{{font-family:sans-serif;background:#1a1a2e;color:#e0e0e0;padding:20px}}
h1{{text-align:center;color:#00d4ff;margin-bottom:10px}}
.subtitle{{text-align:center;color:#888;margin-bottom:30px;font-size:14px}}
.summary{{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:15px;margin-bottom:30px}}
.card{{background:#16213e;border-radius:12px;padding:20px;text-align:center;border:1px solid #333}}
.card .num{{font-size:28px;font-weight:700}}
.card .label{{font-size:12px;color:#888;margin-top:5px}}
.card.ok .num{{color:#0f0}} .card.err .num{{color:#f44}} .card.time .num{{color:#00d4ff}}
table{{width:100%;border-collapse:collapse;margin-bottom:30px;background:#16213e;border-radius:12px;overflow:hidden}}
th{{background:#0f3460;padding:10px 8px;text-align:center;font-size:13px;color:#fff}}
td{{padding:8px;text-align:center;border-bottom:1px solid #222;font-size:13px}}
tr:hover{{background:#1a1a3e}}
.section-title{{font-size:18px;font-weight:600;margin:30px 0 15px;color:#00d4ff;border-bottom:1px solid #333;padding-bottom:10px}}
.ok{{color:#0f0}} .fail{{color:#f44}}
.time-bar{{display:inline-block;height:18px;background:linear-gradient(90deg,#0f3460,#00d4ff);border-radius:3px;vertical-align:middle;min-width:2px}}
.gallery{{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px}}
.gallery img{{width:100%;border-radius:8px;border:1px solid #333}}
.gallery .item{{text-align:center}}
.gallery .cap{{margin-top:5px;font-size:12px;color:#888}}
</style></head><body>
<h1>📊 接口2女性5种发型测试报告</h1>
<div class="subtitle">Plan {PLAN_TAG} {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} {total} 次调用</div>
<div class="summary">
<div class="card ok"><div class="num">{success}</div><div class="label">成功</div></div>
<div class="card err"><div class="num">{total-success}</div><div class="label">失败</div></div>
<div class="card time"><div class="num">{avg_time:.1f}s</div><div class="label">平均耗时</div></div>
</div>
<div class="section-title">详细结果</div>
<table><thead><tr><th>发型</th><th>名称</th><th>耗时</th><th>显存前</th><th>显存后</th><th>Δ</th><th>状态</th></tr></thead><tbody>
"""
for r in results:
status = '<span class="ok">✓</span>' if r["success"] else f'<span class="fail">✗ {str(r.get("error",""))[:30]}</span>'
html += f'<tr><td>style {r["hair_style"]}</td><td>{r["hair_style_name"]}</td>' \
f'<td><div class="time-bar" style="width:{min(r["elapsed_s"]*15,200)}px"></div> {r["elapsed_s"]:.1f}s</td>' \
f'<td>{r["vram_before_mb"]}MB</td><td>{r["vram_after_mb"]}MB</td>' \
f'<td>{r["vram_delta_mb"]:+d}</td><td>{status}</td></tr>'
html += '</tbody></table>'
# 图片画廊
saved = [r for r in results if r.get("image_path") and os.path.isfile(r["image_path"])]
if saved:
html += '<div class="section-title">生成图片</div><div class="gallery">'
for r in saved:
rel = os.path.relpath(r["image_path"], OUT_DIR)
html += f'<div class="item"><img src="{rel}"><div class="cap">{r["hair_style_name"]} ({r["elapsed_s"]:.1f}s)</div></div>'
html += '</div>'
html += '</body></html>'
with open(REPORT_HTML, "w", encoding="utf-8") as f:
f.write(html)
if __name__ == "__main__":
run_test()
+43
View File
@@ -0,0 +1,43 @@
nohup: ignoring input
9B vs 4B 对比测试 — Plan=A
图片: ['girl1.jpg', 'girl7.jpg', 'girl13.jpg']
发型: [(1, '椭圆'), (2, '花瓣'), (3, '心形'), (4, '直线'), (5, '波浪')]
总调用: 15
[warmup] 预热请求 (girl13, style=1) ...
warmup: 25.7s
[img1/3] girl1.jpg style=1(椭圆) ...
→ ✓ code=0 time=9.4s vram=26207→29449MB (Δ+3242) img=saved
[img1/3] girl1.jpg style=2(花瓣) ...
→ ✓ code=0 time=10.5s vram=29449→26341MB (Δ-3108) img=saved
[img1/3] girl1.jpg style=3(心形) ...
→ ✓ code=0 time=10.5s vram=26341→26051MB (Δ-290) img=saved
[img1/3] girl1.jpg style=4(直线) ...
→ ✓ code=0 time=10.4s vram=26051→26257MB (Δ+206) img=saved
[img1/3] girl1.jpg style=5(波浪) ...
→ ✓ code=0 time=10.8s vram=26257→26319MB (Δ+62) img=saved
[img2/3] girl7.jpg style=1(椭圆) ...
→ ✓ code=0 time=7.9s vram=26319→28417MB (Δ+2098) img=saved
[img2/3] girl7.jpg style=2(花瓣) ...
→ ✓ code=0 time=7.8s vram=28417→28397MB (Δ-20) img=saved
[img2/3] girl7.jpg style=3(心形) ...
→ ✓ code=0 time=7.7s vram=28397→28409MB (Δ+12) img=saved
[img2/3] girl7.jpg style=4(直线) ...
→ ✓ code=0 time=8.2s vram=28409→26335MB (Δ-2074) img=saved
[img2/3] girl7.jpg style=5(波浪) ...
→ ✓ code=0 time=7.9s vram=26335→28419MB (Δ+2084) img=saved
[img3/3] girl13.jpg style=1(椭圆) ...
→ ✓ code=0 time=8.9s vram=28419→28835MB (Δ+416) img=saved
[img3/3] girl13.jpg style=2(花瓣) ...
→ ✓ code=0 time=9.1s vram=28835→28817MB (Δ-18) img=saved
[img3/3] girl13.jpg style=3(心形) ...
→ ✓ code=0 time=8.8s vram=28817→28795MB (Δ-22) img=saved
[img3/3] girl13.jpg style=4(直线) ...
→ ✓ code=0 time=8.8s vram=28795→28845MB (Δ+50) img=saved
[img3/3] girl13.jpg style=5(波浪) ...
→ ✓ code=0 time=9.0s vram=28845→26291MB (Δ-2554) img=saved
✅ 完成!/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/results.json
📊 成功: 15/15
⏱ 平均: 9.06s
Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

+257
View File
@@ -0,0 +1,257 @@
[
{
"plan": "A",
"image_idx": 1,
"image_name": "girl1.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl1.jpg",
"hair_style": 1,
"hair_style_name": "椭圆",
"timestamp": "14:04:32",
"elapsed_s": 9.41,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 26207,
"vram_after_mb": 29449,
"vram_delta_mb": 3242,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img1_style1.jpg"
},
{
"plan": "A",
"image_idx": 1,
"image_name": "girl1.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl1.jpg",
"hair_style": 2,
"hair_style_name": "花瓣",
"timestamp": "14:04:42",
"elapsed_s": 10.51,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 29449,
"vram_after_mb": 26341,
"vram_delta_mb": -3108,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img1_style2.jpg"
},
{
"plan": "A",
"image_idx": 1,
"image_name": "girl1.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl1.jpg",
"hair_style": 3,
"hair_style_name": "心形",
"timestamp": "14:04:53",
"elapsed_s": 10.49,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 26341,
"vram_after_mb": 26051,
"vram_delta_mb": -290,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img1_style3.jpg"
},
{
"plan": "A",
"image_idx": 1,
"image_name": "girl1.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl1.jpg",
"hair_style": 4,
"hair_style_name": "直线",
"timestamp": "14:05:03",
"elapsed_s": 10.45,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 26051,
"vram_after_mb": 26257,
"vram_delta_mb": 206,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img1_style4.jpg"
},
{
"plan": "A",
"image_idx": 1,
"image_name": "girl1.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl1.jpg",
"hair_style": 5,
"hair_style_name": "波浪",
"timestamp": "14:05:14",
"elapsed_s": 10.85,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 26257,
"vram_after_mb": 26319,
"vram_delta_mb": 62,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img1_style5.jpg"
},
{
"plan": "A",
"image_idx": 2,
"image_name": "girl7.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl7.jpg",
"hair_style": 1,
"hair_style_name": "椭圆",
"timestamp": "14:05:22",
"elapsed_s": 7.91,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 26319,
"vram_after_mb": 28417,
"vram_delta_mb": 2098,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img2_style1.jpg"
},
{
"plan": "A",
"image_idx": 2,
"image_name": "girl7.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl7.jpg",
"hair_style": 2,
"hair_style_name": "花瓣",
"timestamp": "14:05:30",
"elapsed_s": 7.81,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28417,
"vram_after_mb": 28397,
"vram_delta_mb": -20,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img2_style2.jpg"
},
{
"plan": "A",
"image_idx": 2,
"image_name": "girl7.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl7.jpg",
"hair_style": 3,
"hair_style_name": "心形",
"timestamp": "14:05:38",
"elapsed_s": 7.74,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28397,
"vram_after_mb": 28409,
"vram_delta_mb": 12,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img2_style3.jpg"
},
{
"plan": "A",
"image_idx": 2,
"image_name": "girl7.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl7.jpg",
"hair_style": 4,
"hair_style_name": "直线",
"timestamp": "14:05:46",
"elapsed_s": 8.17,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28409,
"vram_after_mb": 26335,
"vram_delta_mb": -2074,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img2_style4.jpg"
},
{
"plan": "A",
"image_idx": 2,
"image_name": "girl7.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl7.jpg",
"hair_style": 5,
"hair_style_name": "波浪",
"timestamp": "14:05:54",
"elapsed_s": 7.92,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 26335,
"vram_after_mb": 28419,
"vram_delta_mb": 2084,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img2_style5.jpg"
},
{
"plan": "A",
"image_idx": 3,
"image_name": "girl13.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl13.jpg",
"hair_style": 1,
"hair_style_name": "椭圆",
"timestamp": "14:06:03",
"elapsed_s": 8.95,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28419,
"vram_after_mb": 28835,
"vram_delta_mb": 416,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img3_style1.jpg"
},
{
"plan": "A",
"image_idx": 3,
"image_name": "girl13.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl13.jpg",
"hair_style": 2,
"hair_style_name": "花瓣",
"timestamp": "14:06:12",
"elapsed_s": 9.07,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28835,
"vram_after_mb": 28817,
"vram_delta_mb": -18,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img3_style2.jpg"
},
{
"plan": "A",
"image_idx": 3,
"image_name": "girl13.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl13.jpg",
"hair_style": 3,
"hair_style_name": "心形",
"timestamp": "14:06:21",
"elapsed_s": 8.82,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28817,
"vram_after_mb": 28795,
"vram_delta_mb": -22,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img3_style3.jpg"
},
{
"plan": "A",
"image_idx": 3,
"image_name": "girl13.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl13.jpg",
"hair_style": 4,
"hair_style_name": "直线",
"timestamp": "14:06:30",
"elapsed_s": 8.84,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28795,
"vram_after_mb": 28845,
"vram_delta_mb": 50,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img3_style4.jpg"
},
{
"plan": "A",
"image_idx": 3,
"image_name": "girl13.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl13.jpg",
"hair_style": 5,
"hair_style_name": "波浪",
"timestamp": "14:06:39",
"elapsed_s": 8.96,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28845,
"vram_after_mb": 26291,
"vram_delta_mb": -2554,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_A/img3_style5.jpg"
}
]
+43
View File
@@ -0,0 +1,43 @@
nohup: ignoring input
9B vs 4B 对比测试 — Plan=Bplus
图片: ['girl1.jpg', 'girl7.jpg', 'girl13.jpg']
发型: [(1, '椭圆'), (2, '花瓣'), (3, '心形'), (4, '直线'), (5, '波浪')]
总调用: 15
[warmup] 预热请求 (girl13, style=1) ...
warmup: 12.1s
[img1/3] girl1.jpg style=1(椭圆) ...
→ ✓ code=0 time=7.7s vram=25531→28691MB (Δ+3160) img=saved
[img1/3] girl1.jpg style=2(花瓣) ...
→ ✓ code=0 time=8.8s vram=28691→28655MB (Δ-36) img=saved
[img1/3] girl1.jpg style=3(心形) ...
→ ✓ code=0 time=9.1s vram=28655→28651MB (Δ-4) img=saved
[img1/3] girl1.jpg style=4(直线) ...
→ ✓ code=0 time=8.7s vram=28651→28641MB (Δ-10) img=saved
[img1/3] girl1.jpg style=5(波浪) ...
→ ✓ code=0 time=8.8s vram=28641→28637MB (Δ-4) img=saved
[img2/3] girl7.jpg style=1(椭圆) ...
→ ✓ code=0 time=7.0s vram=28637→25539MB (Δ-3098) img=saved
[img2/3] girl7.jpg style=2(花瓣) ...
→ ✓ code=0 time=6.7s vram=25539→27599MB (Δ+2060) img=saved
[img2/3] girl7.jpg style=3(心形) ...
→ ✓ code=0 time=6.7s vram=27599→27597MB (Δ-2) img=saved
[img2/3] girl7.jpg style=4(直线) ...
→ ✓ code=0 time=7.0s vram=27597→25533MB (Δ-2064) img=saved
[img2/3] girl7.jpg style=5(波浪) ...
→ ✓ code=0 time=6.7s vram=25533→27585MB (Δ+2052) img=saved
[img3/3] girl13.jpg style=1(椭圆) ...
→ ✓ code=0 time=7.6s vram=27585→28023MB (Δ+438) img=saved
[img3/3] girl13.jpg style=2(花瓣) ...
→ ✓ code=0 time=7.8s vram=28023→27997MB (Δ-26) img=saved
[img3/3] girl13.jpg style=3(心形) ...
→ ✓ code=0 time=7.8s vram=27997→25557MB (Δ-2440) img=saved
[img3/3] girl13.jpg style=4(直线) ...
→ ✓ code=0 time=7.6s vram=25557→28011MB (Δ+2454) img=saved
[img3/3] girl13.jpg style=5(波浪) ...
→ ✓ code=0 time=7.8s vram=28011→28017MB (Δ+6) img=saved
✅ 完成!/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/results.json
📊 成功: 15/15
⏱ 平均: 7.72s
Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

+257
View File
@@ -0,0 +1,257 @@
[
{
"plan": "Bplus",
"image_idx": 1,
"image_name": "girl1.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl1.jpg",
"hair_style": 1,
"hair_style_name": "椭圆",
"timestamp": "14:08:20",
"elapsed_s": 7.69,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25531,
"vram_after_mb": 28691,
"vram_delta_mb": 3160,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img1_style1.jpg"
},
{
"plan": "Bplus",
"image_idx": 1,
"image_name": "girl1.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl1.jpg",
"hair_style": 2,
"hair_style_name": "花瓣",
"timestamp": "14:08:28",
"elapsed_s": 8.77,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28691,
"vram_after_mb": 28655,
"vram_delta_mb": -36,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img1_style2.jpg"
},
{
"plan": "Bplus",
"image_idx": 1,
"image_name": "girl1.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl1.jpg",
"hair_style": 3,
"hair_style_name": "心形",
"timestamp": "14:08:38",
"elapsed_s": 9.07,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28655,
"vram_after_mb": 28651,
"vram_delta_mb": -4,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img1_style3.jpg"
},
{
"plan": "Bplus",
"image_idx": 1,
"image_name": "girl1.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl1.jpg",
"hair_style": 4,
"hair_style_name": "直线",
"timestamp": "14:08:46",
"elapsed_s": 8.73,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28651,
"vram_after_mb": 28641,
"vram_delta_mb": -10,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img1_style4.jpg"
},
{
"plan": "Bplus",
"image_idx": 1,
"image_name": "girl1.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl1.jpg",
"hair_style": 5,
"hair_style_name": "波浪",
"timestamp": "14:08:55",
"elapsed_s": 8.81,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28641,
"vram_after_mb": 28637,
"vram_delta_mb": -4,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img1_style5.jpg"
},
{
"plan": "Bplus",
"image_idx": 2,
"image_name": "girl7.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl7.jpg",
"hair_style": 1,
"hair_style_name": "椭圆",
"timestamp": "14:09:02",
"elapsed_s": 7.04,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28637,
"vram_after_mb": 25539,
"vram_delta_mb": -3098,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img2_style1.jpg"
},
{
"plan": "Bplus",
"image_idx": 2,
"image_name": "girl7.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl7.jpg",
"hair_style": 2,
"hair_style_name": "花瓣",
"timestamp": "14:09:09",
"elapsed_s": 6.67,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25539,
"vram_after_mb": 27599,
"vram_delta_mb": 2060,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img2_style2.jpg"
},
{
"plan": "Bplus",
"image_idx": 2,
"image_name": "girl7.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl7.jpg",
"hair_style": 3,
"hair_style_name": "心形",
"timestamp": "14:09:16",
"elapsed_s": 6.73,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 27599,
"vram_after_mb": 27597,
"vram_delta_mb": -2,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img2_style3.jpg"
},
{
"plan": "Bplus",
"image_idx": 2,
"image_name": "girl7.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl7.jpg",
"hair_style": 4,
"hair_style_name": "直线",
"timestamp": "14:09:23",
"elapsed_s": 6.96,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 27597,
"vram_after_mb": 25533,
"vram_delta_mb": -2064,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img2_style4.jpg"
},
{
"plan": "Bplus",
"image_idx": 2,
"image_name": "girl7.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl7.jpg",
"hair_style": 5,
"hair_style_name": "波浪",
"timestamp": "14:09:29",
"elapsed_s": 6.74,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25533,
"vram_after_mb": 27585,
"vram_delta_mb": 2052,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img2_style5.jpg"
},
{
"plan": "Bplus",
"image_idx": 3,
"image_name": "girl13.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl13.jpg",
"hair_style": 1,
"hair_style_name": "椭圆",
"timestamp": "14:09:37",
"elapsed_s": 7.62,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 27585,
"vram_after_mb": 28023,
"vram_delta_mb": 438,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img3_style1.jpg"
},
{
"plan": "Bplus",
"image_idx": 3,
"image_name": "girl13.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl13.jpg",
"hair_style": 2,
"hair_style_name": "花瓣",
"timestamp": "14:09:45",
"elapsed_s": 7.84,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28023,
"vram_after_mb": 27997,
"vram_delta_mb": -26,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img3_style2.jpg"
},
{
"plan": "Bplus",
"image_idx": 3,
"image_name": "girl13.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl13.jpg",
"hair_style": 3,
"hair_style_name": "心形",
"timestamp": "14:09:53",
"elapsed_s": 7.83,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 27997,
"vram_after_mb": 25557,
"vram_delta_mb": -2440,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img3_style3.jpg"
},
{
"plan": "Bplus",
"image_idx": 3,
"image_name": "girl13.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl13.jpg",
"hair_style": 4,
"hair_style_name": "直线",
"timestamp": "14:10:00",
"elapsed_s": 7.59,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25557,
"vram_after_mb": 28011,
"vram_delta_mb": 2454,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img3_style4.jpg"
},
{
"plan": "Bplus",
"image_idx": 3,
"image_name": "girl13.jpg",
"image_path": "/home/ubuntu/hair/image/girl_img/girl13.jpg",
"hair_style": 5,
"hair_style_name": "波浪",
"timestamp": "14:10:08",
"elapsed_s": 7.77,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28011,
"vram_after_mb": 28017,
"vram_delta_mb": 6,
"saved_image_path": "/home/ubuntu/hair/benchmark_out/9b_vs_4b_Bplus/img3_style5.jpg"
}
]
File diff suppressed because one or more lines are too long
+292
View File
@@ -0,0 +1,292 @@
nohup: ignoring input
找到 19 张图片
总计 95 次调用,已完成 0,剩余 95
[1/19] girl1.jpg → 椭圆形(1) ...
→ HTTP code=0 time=11.8s vram=22353→22353MB preview=✓ grown=✓
[1/19] girl1.jpg → 花瓣形(2) ...
→ HTTP code=0 time=13.7s vram=22353→22389MB preview=✓ grown=✓
[1/19] girl1.jpg → 心形(3) ...
→ HTTP code=0 time=13.6s vram=22389→22375MB preview=✓ grown=✓
[1/19] girl1.jpg → 直线形(4) ...
→ HTTP code=0 time=14.0s vram=22375→22359MB preview=✓ grown=✓
[1/19] girl1.jpg → 波浪形(5) ...
→ HTTP code=0 time=13.6s vram=22359→22393MB preview=✓ grown=✓
[2/19] girl10.jpg → 椭圆形(1) ...
→ HTTP code=0 time=9.6s vram=22393→22413MB preview=✓ grown=✓
[2/19] girl10.jpg → 花瓣形(2) ...
→ HTTP code=0 time=9.8s vram=22413→22397MB preview=✓ grown=✓
[2/19] girl10.jpg → 心形(3) ...
→ HTTP code=0 time=9.8s vram=22397→22391MB preview=✓ grown=✓
[2/19] girl10.jpg → 直线形(4) ...
→ HTTP code=0 time=9.8s vram=22391→22393MB preview=✓ grown=✓
[2/19] girl10.jpg → 波浪形(5) ...
→ HTTP code=0 time=9.8s vram=22393→22395MB preview=✓ grown=✓
[3/19] girl11.jpg → 椭圆形(1) ...
→ HTTP code=0 time=10.0s vram=22395→22421MB preview=✓ grown=✓
[3/19] girl11.jpg → 花瓣形(2) ...
→ HTTP code=0 time=10.3s vram=22421→22393MB preview=✓ grown=✓
[3/19] girl11.jpg → 心形(3) ...
→ HTTP code=0 time=10.4s vram=22393→22375MB preview=✓ grown=✓
[3/19] girl11.jpg → 直线形(4) ...
→ HTTP code=0 time=10.4s vram=22375→22385MB preview=✓ grown=✓
[3/19] girl11.jpg → 波浪形(5) ...
→ HTTP code=0 time=9.9s vram=22385→22389MB preview=✓ grown=✓
[4/19] girl12.jpg → 椭圆形(1) ...
→ HTTP code=0 time=13.0s vram=22389→22389MB preview=✓ grown=✓
[4/19] girl12.jpg → 花瓣形(2) ...
→ HTTP code=0 time=12.9s vram=22389→22385MB preview=✓ grown=✓
[4/19] girl12.jpg → 心形(3) ...
→ HTTP code=0 time=12.9s vram=22385→22389MB preview=✓ grown=✓
[4/19] girl12.jpg → 直线形(4) ...
→ HTTP code=0 time=13.0s vram=22389→22389MB preview=✓ grown=✓
[4/19] girl12.jpg → 波浪形(5) ...
→ HTTP code=0 time=13.3s vram=22389→22409MB preview=✓ grown=✓
[5/19] girl13.jpg → 椭圆形(1) ...
→ HTTP code=0 time=11.5s vram=22409→22365MB preview=✓ grown=✓
[5/19] girl13.jpg → 花瓣形(2) ...
→ HTTP code=0 time=11.2s vram=22365→22395MB preview=✓ grown=✓
[5/19] girl13.jpg → 心形(3) ...
→ HTTP code=0 time=11.2s vram=22395→22421MB preview=✓ grown=✓
[5/19] girl13.jpg → 直线形(4) ...
→ HTTP code=0 time=11.4s vram=22421→22409MB preview=✓ grown=✓
[5/19] girl13.jpg → 波浪形(5) ...
→ HTTP code=0 time=11.2s vram=22409→22431MB preview=✓ grown=✓
[6/19] girl14.jpg → 椭圆形(1) ...
→ HTTP code=0 time=11.5s vram=22431→22391MB preview=✓ grown=✓
[6/19] girl14.jpg → 花瓣形(2) ...
→ HTTP code=0 time=11.5s vram=22391→22375MB preview=✓ grown=✓
[6/19] girl14.jpg → 心形(3) ...
→ HTTP code=0 time=11.5s vram=22375→22409MB preview=✓ grown=✓
[6/19] girl14.jpg → 直线形(4) ...
→ HTTP code=0 time=11.5s vram=22409→22359MB preview=✓ grown=✓
[6/19] girl14.jpg → 波浪形(5) ...
→ HTTP code=0 time=11.5s vram=22359→22393MB preview=✓ grown=✓
[7/19] girl15.jpg → 椭圆形(1) ...
→ HTTP code=0 time=11.2s vram=22393→22385MB preview=✓ grown=✓
[7/19] girl15.jpg → 花瓣形(2) ...
→ HTTP code=0 time=11.3s vram=22385→22387MB preview=✓ grown=✓
[7/19] girl15.jpg → 心形(3) ...
→ HTTP code=0 time=11.4s vram=22387→22357MB preview=✓ grown=✓
[7/19] girl15.jpg → 直线形(4) ...
→ HTTP code=0 time=11.3s vram=22357→22383MB preview=✓ grown=✓
[7/19] girl15.jpg → 波浪形(5) ...
→ HTTP code=0 time=11.7s vram=22383→22387MB preview=✓ grown=✓
[8/19] girl16.jpg → 椭圆形(1) ...
→ HTTP code=0 time=11.2s vram=22387→22377MB preview=✓ grown=✓
[8/19] girl16.jpg → 花瓣形(2) ...
→ HTTP code=0 time=11.2s vram=22377→22359MB preview=✓ grown=✓
[8/19] girl16.jpg → 心形(3) ...
→ HTTP code=0 time=11.2s vram=22359→22347MB preview=✓ grown=✓
[8/19] girl16.jpg → 直线形(4) ...
→ HTTP code=0 time=11.3s vram=22347→22367MB preview=✓ grown=✓
[8/19] girl16.jpg → 波浪形(5) ...
→ HTTP code=0 time=11.4s vram=22367→22399MB preview=✓ grown=✓
[9/19] girl17.jpg → 椭圆形(1) ...
→ HTTP code=0 time=13.0s vram=22399→22403MB preview=✓ grown=✓
[9/19] girl17.jpg → 花瓣形(2) ...
→ HTTP code=0 time=13.0s vram=22403→22421MB preview=✓ grown=✓
[9/19] girl17.jpg → 心形(3) ...
→ HTTP code=0 time=13.1s vram=22421→22381MB preview=✓ grown=✓
[9/19] girl17.jpg → 直线形(4) ...
→ HTTP code=0 time=13.1s vram=22381→22419MB preview=✓ grown=✓
[9/19] girl17.jpg → 波浪形(5) ...
→ HTTP code=0 time=13.0s vram=22419→22399MB preview=✓ grown=✓
[10/19] girl18.jpg → 椭圆形(1) ...
→ HTTP code=0 time=11.6s vram=22399→22419MB preview=✓ grown=✓
[10/19] girl18.jpg → 花瓣形(2) ...
→ HTTP code=0 time=11.2s vram=22419→22383MB preview=✓ grown=✓
[10/19] girl18.jpg → 心形(3) ...
→ HTTP code=0 time=11.4s vram=22383→22409MB preview=✓ grown=✓
[10/19] girl18.jpg → 直线形(4) ...
→ HTTP code=0 time=11.4s vram=22409→22375MB preview=✓ grown=✓
[10/19] girl18.jpg → 波浪形(5) ...
→ HTTP code=0 time=11.3s vram=22375→22413MB preview=✓ grown=✓
[11/19] girl19.jpg → 椭圆形(1) ...
→ HTTP code=0 time=11.1s vram=22413→22385MB preview=✓ grown=✓
[11/19] girl19.jpg → 花瓣形(2) ...
→ HTTP code=0 time=11.1s vram=22385→22393MB preview=✓ grown=✓
[11/19] girl19.jpg → 心形(3) ...
→ HTTP code=0 time=11.3s vram=22393→22387MB preview=✓ grown=✓
[11/19] girl19.jpg → 直线形(4) ...
→ HTTP code=0 time=11.2s vram=22387→22415MB preview=✓ grown=✓
[11/19] girl19.jpg → 波浪形(5) ...
→ HTTP code=0 time=11.2s vram=22415→22405MB preview=✓ grown=✓
[12/19] girl2.jpg → 椭圆形(1) ...
→ HTTP code=0 time=13.1s vram=22405→22395MB preview=✓ grown=✓
[12/19] girl2.jpg → 花瓣形(2) ...
→ HTTP code=0 time=12.9s vram=22395→22387MB preview=✓ grown=✓
[12/19] girl2.jpg → 心形(3) ...
→ HTTP code=0 time=12.9s vram=22387→22381MB preview=✓ grown=✓
[12/19] girl2.jpg → 直线形(4) ...
→ HTTP code=0 time=12.9s vram=22381→22387MB preview=✓ grown=✓
[12/19] girl2.jpg → 波浪形(5) ...
→ HTTP code=0 time=12.9s vram=22387→22309MB preview=✓ grown=✓
[13/19] girl3.jpg → 椭圆形(1) ...
→ HTTP code=0 time=9.8s vram=22309→22381MB preview=✓ grown=✓
[13/19] girl3.jpg → 花瓣形(2) ...
→ HTTP code=0 time=9.8s vram=22381→22403MB preview=✓ grown=✓
[13/19] girl3.jpg → 心形(3) ...
→ HTTP code=0 time=9.9s vram=22403→22419MB preview=✓ grown=✓
[13/19] girl3.jpg → 直线形(4) ...
→ HTTP code=0 time=9.9s vram=22419→22401MB preview=✓ grown=✓
[13/19] girl3.jpg → 波浪形(5) ...
→ HTTP code=0 time=9.9s vram=22401→22385MB preview=✓ grown=✓
[14/19] girl4.jpg → 椭圆形(1) ...
→ HTTP code=0 time=10.2s vram=22385→22377MB preview=✓ grown=✓
[14/19] girl4.jpg → 花瓣形(2) ...
→ HTTP code=0 time=10.5s vram=22377→22387MB preview=✓ grown=✓
[14/19] girl4.jpg → 心形(3) ...
→ HTTP code=0 time=10.0s vram=22387→22365MB preview=✓ grown=✓
[14/19] girl4.jpg → 直线形(4) ...
→ HTTP code=0 time=9.9s vram=22365→22371MB preview=✓ grown=✓
[14/19] girl4.jpg → 波浪形(5) ...
→ HTTP code=0 time=10.0s vram=22371→22389MB preview=✓ grown=✓
[15/19] girl5.jpg → 椭圆形(1) ...
→ HTTP code=0 time=8.5s vram=22389→23903MB preview=✓ grown=✓
[15/19] girl5.jpg → 花瓣形(2) ...
→ HTTP code=0 time=8.6s vram=23903→23929MB preview=✓ grown=✓
[15/19] girl5.jpg → 心形(3) ...
→ HTTP code=0 time=8.6s vram=23929→23925MB preview=✓ grown=✓
[15/19] girl5.jpg → 直线形(4) ...
→ HTTP code=0 time=8.7s vram=23925→23931MB preview=✓ grown=✓
[15/19] girl5.jpg → 波浪形(5) ...
→ HTTP code=0 time=8.7s vram=23931→23951MB preview=✓ grown=✓
[16/19] girl6.jpg → 椭圆形(1) ...
→ HTTP code=0 time=8.4s vram=23951→23729MB preview=✓ grown=✓
[16/19] girl6.jpg → 花瓣形(2) ...
→ HTTP code=0 time=8.7s vram=23729→22377MB preview=✓ grown=✓
[16/19] girl6.jpg → 心形(3) ...
→ HTTP code=0 time=8.4s vram=22377→23685MB preview=✓ grown=✓
[16/19] girl6.jpg → 直线形(4) ...
→ HTTP code=0 time=8.3s vram=23685→23731MB preview=✓ grown=✓
[16/19] girl6.jpg → 波浪形(5) ...
→ HTTP code=0 time=8.4s vram=23731→23713MB preview=✓ grown=✓
[17/19] girl7.jpg → 椭圆形(1) ...
→ HTTP code=0 time=9.9s vram=23713→24481MB preview=✓ grown=✓
[17/19] girl7.jpg → 花瓣形(2) ...
→ HTTP code=0 time=10.1s vram=24481→24447MB preview=✓ grown=✓
[17/19] girl7.jpg → 心形(3) ...
→ HTTP code=0 time=10.0s vram=24447→24481MB preview=✓ grown=✓
[17/19] girl7.jpg → 直线形(4) ...
→ HTTP code=0 time=10.0s vram=24481→24469MB preview=✓ grown=✓
[17/19] girl7.jpg → 波浪形(5) ...
→ HTTP code=0 time=10.0s vram=24469→24475MB preview=✓ grown=✓
[18/19] girl8.jpg → 椭圆形(1) ...
→ HTTP code=0 time=11.4s vram=24475→24861MB preview=✓ grown=✓
[18/19] girl8.jpg → 花瓣形(2) ...
→ HTTP code=0 time=11.5s vram=24861→24823MB preview=✓ grown=✓
[18/19] girl8.jpg → 心形(3) ...
→ HTTP code=0 time=11.3s vram=24823→22393MB preview=✓ grown=✓
[18/19] girl8.jpg → 直线形(4) ...
→ HTTP code=0 time=11.1s vram=22393→22389MB preview=✓ grown=✓
[18/19] girl8.jpg → 波浪形(5) ...
→ HTTP code=0 time=11.1s vram=22389→22365MB preview=✓ grown=✓
[19/19] girl9.jpg → 椭圆形(1) ...
→ HTTP code=0 time=11.9s vram=22365→22391MB preview=✓ grown=✓
[19/19] girl9.jpg → 花瓣形(2) ...
→ HTTP code=0 time=11.9s vram=22391→22375MB preview=✓ grown=✓
[19/19] girl9.jpg → 心形(3) ...
→ HTTP code=0 time=11.9s vram=22375→22391MB preview=✓ grown=✓
[19/19] girl9.jpg → 直线形(4) ...
→ HTTP code=0 time=11.9s vram=22391→22371MB preview=✓ grown=✓
[19/19] girl9.jpg → 波浪形(5) ...
→ HTTP code=0 time=11.8s vram=22371→22373MB preview=✓ grown=✓
✅ 完成!结果: /home/ubuntu/hair/benchmark_out/benchmark_results.json
📄 报告: /home/ubuntu/hair/benchmark_out/benchmark_report.html
📊 总调用: 95/95
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
nohup: ignoring input
接口2女性5种发型测试 — Plan=B
图片:/home/ubuntu/hair/image/girl_img/girl13.jpg
[1/5] hair_style=1 (椭圆) ...
→ ✓ code=0 time=8.7s vram=24981→25025MB (Δ+44) img=saved
[2/5] hair_style=2 (花瓣) ...
→ ✓ code=0 time=8.4s vram=25025→27451MB (Δ+2426) img=saved
[3/5] hair_style=3 (心形) ...
→ ✓ code=0 time=8.4s vram=27451→27431MB (Δ-20) img=saved
[4/5] hair_style=4 (直线) ...
→ ✓ code=0 time=8.5s vram=27431→27433MB (Δ+2) img=saved
[5/5] hair_style=5 (波浪) ...
→ ✓ code=0 time=8.4s vram=27433→27407MB (Δ-26) img=saved
✅ 完成!/home/ubuntu/hair/benchmark_out/iface2_female_B/results.json
📄 报告:/home/ubuntu/hair/benchmark_out/iface2_female_B/report.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

+34
View File
@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><title>接口2女5种发型 - Plan B</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:sans-serif;background:#1a1a2e;color:#e0e0e0;padding:20px}
h1{text-align:center;color:#00d4ff;margin-bottom:10px}
.subtitle{text-align:center;color:#888;margin-bottom:30px;font-size:14px}
.summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:15px;margin-bottom:30px}
.card{background:#16213e;border-radius:12px;padding:20px;text-align:center;border:1px solid #333}
.card .num{font-size:28px;font-weight:700}
.card .label{font-size:12px;color:#888;margin-top:5px}
.card.ok .num{color:#0f0} .card.err .num{color:#f44} .card.time .num{color:#00d4ff}
table{width:100%;border-collapse:collapse;margin-bottom:30px;background:#16213e;border-radius:12px;overflow:hidden}
th{background:#0f3460;padding:10px 8px;text-align:center;font-size:13px;color:#fff}
td{padding:8px;text-align:center;border-bottom:1px solid #222;font-size:13px}
tr:hover{background:#1a1a3e}
.section-title{font-size:18px;font-weight:600;margin:30px 0 15px;color:#00d4ff;border-bottom:1px solid #333;padding-bottom:10px}
.ok{color:#0f0} .fail{color:#f44}
.time-bar{display:inline-block;height:18px;background:linear-gradient(90deg,#0f3460,#00d4ff);border-radius:3px;vertical-align:middle;min-width:2px}
.gallery{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px}
.gallery img{width:100%;border-radius:8px;border:1px solid #333}
.gallery .item{text-align:center}
.gallery .cap{margin-top:5px;font-size:12px;color:#888}
</style></head><body>
<h1>📊 接口2女性5种发型测试报告</h1>
<div class="subtitle">Plan B 2026-07-18 13:52:20 5 次调用</div>
<div class="summary">
<div class="card ok"><div class="num">5</div><div class="label">成功</div></div>
<div class="card err"><div class="num">0</div><div class="label">失败</div></div>
<div class="card time"><div class="num">8.5s</div><div class="label">平均耗时</div></div>
</div>
<div class="section-title">详细结果</div>
<table><thead><tr><th>发型</th><th>名称</th><th>耗时</th><th>显存前</th><th>显存后</th><th>Δ</th><th>状态</th></tr></thead><tbody>
<tr><td>style 1</td><td>椭圆</td><td><div class="time-bar" style="width:130.65px"></div> 8.7s</td><td>24981MB</td><td>25025MB</td><td>+44</td><td><span class="ok"></span></td></tr><tr><td>style 2</td><td>花瓣</td><td><div class="time-bar" style="width:125.54999999999998px"></div> 8.4s</td><td>25025MB</td><td>27451MB</td><td>+2426</td><td><span class="ok"></span></td></tr><tr><td>style 3</td><td>心形</td><td><div class="time-bar" style="width:125.54999999999998px"></div> 8.4s</td><td>27451MB</td><td>27431MB</td><td>-20</td><td><span class="ok"></span></td></tr><tr><td>style 4</td><td>直线</td><td><div class="time-bar" style="width:127.5px"></div> 8.5s</td><td>27431MB</td><td>27433MB</td><td>+2</td><td><span class="ok"></span></td></tr><tr><td>style 5</td><td>波浪</td><td><div class="time-bar" style="width:126.0px"></div> 8.4s</td><td>27433MB</td><td>27407MB</td><td>-26</td><td><span class="ok"></span></td></tr></tbody></table><div class="section-title">生成图片</div><div class="gallery"><div class="item"><img src="hair_style_1_椭圆.jpg"><div class="cap">椭圆 (8.7s)</div></div><div class="item"><img src="hair_style_2_花瓣.jpg"><div class="cap">花瓣 (8.4s)</div></div><div class="item"><img src="hair_style_3_心形.jpg"><div class="cap">心形 (8.4s)</div></div><div class="item"><img src="hair_style_4_直线.jpg"><div class="cap">直线 (8.5s)</div></div><div class="item"><img src="hair_style_5_波浪.jpg"><div class="cap">波浪 (8.4s)</div></div></div></body></html>
@@ -0,0 +1,72 @@
[
{
"plan": "B",
"hair_style": 1,
"hair_style_name": "椭圆",
"timestamp": "13:51:46",
"elapsed_s": 8.71,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 24981,
"vram_after_mb": 25025,
"vram_delta_mb": 44,
"image_path": "/home/ubuntu/hair/benchmark_out/iface2_female_B/hair_style_1_椭圆.jpg"
},
{
"plan": "B",
"hair_style": 2,
"hair_style_name": "花瓣",
"timestamp": "13:51:55",
"elapsed_s": 8.37,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25025,
"vram_after_mb": 27451,
"vram_delta_mb": 2426,
"image_path": "/home/ubuntu/hair/benchmark_out/iface2_female_B/hair_style_2_花瓣.jpg"
},
{
"plan": "B",
"hair_style": 3,
"hair_style_name": "心形",
"timestamp": "13:52:03",
"elapsed_s": 8.37,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 27451,
"vram_after_mb": 27431,
"vram_delta_mb": -20,
"image_path": "/home/ubuntu/hair/benchmark_out/iface2_female_B/hair_style_3_心形.jpg"
},
{
"plan": "B",
"hair_style": 4,
"hair_style_name": "直线",
"timestamp": "13:52:11",
"elapsed_s": 8.5,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 27431,
"vram_after_mb": 27433,
"vram_delta_mb": 2,
"image_path": "/home/ubuntu/hair/benchmark_out/iface2_female_B/hair_style_4_直线.jpg"
},
{
"plan": "B",
"hair_style": 5,
"hair_style_name": "波浪",
"timestamp": "13:52:20",
"elapsed_s": 8.4,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 27433,
"vram_after_mb": 27407,
"vram_delta_mb": -26,
"image_path": "/home/ubuntu/hair/benchmark_out/iface2_female_B/hair_style_5_波浪.jpg"
}
]
+22
View File
@@ -0,0 +1,22 @@
nohup: ignoring input
接口2女性5种发型测试 — Plan=Bplus
图片:/home/ubuntu/hair/image/girl_img/girl13.jpg
[1/5] hair_style=1 (椭圆) ...
→ ✓ code=0 time=7.9s vram=25875→25889MB (Δ+14) img=saved
[2/5] hair_style=2 (花瓣) ...
→ ✓ code=0 time=8.0s vram=25889→28337MB (Δ+2448) img=saved
[3/5] hair_style=3 (心形) ...
→ ✓ code=0 time=7.8s vram=28337→28355MB (Δ+18) img=saved
[4/5] hair_style=4 (直线) ...
→ ✓ code=0 time=7.6s vram=28355→28333MB (Δ-22) img=saved
[5/5] hair_style=5 (波浪) ...
→ ✓ code=0 time=8.3s vram=28333→25885MB (Δ-2448) img=saved
✅ 完成!/home/ubuntu/hair/benchmark_out/iface2_female_Bplus/results.json
📄 报告:/home/ubuntu/hair/benchmark_out/iface2_female_Bplus/report.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8"><title>接口2女5种发型 - Plan Bplus</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:sans-serif;background:#1a1a2e;color:#e0e0e0;padding:20px}
h1{text-align:center;color:#00d4ff;margin-bottom:10px}
.subtitle{text-align:center;color:#888;margin-bottom:30px;font-size:14px}
.summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:15px;margin-bottom:30px}
.card{background:#16213e;border-radius:12px;padding:20px;text-align:center;border:1px solid #333}
.card .num{font-size:28px;font-weight:700}
.card .label{font-size:12px;color:#888;margin-top:5px}
.card.ok .num{color:#0f0} .card.err .num{color:#f44} .card.time .num{color:#00d4ff}
table{width:100%;border-collapse:collapse;margin-bottom:30px;background:#16213e;border-radius:12px;overflow:hidden}
th{background:#0f3460;padding:10px 8px;text-align:center;font-size:13px;color:#fff}
td{padding:8px;text-align:center;border-bottom:1px solid #222;font-size:13px}
tr:hover{background:#1a1a3e}
.section-title{font-size:18px;font-weight:600;margin:30px 0 15px;color:#00d4ff;border-bottom:1px solid #333;padding-bottom:10px}
.ok{color:#0f0} .fail{color:#f44}
.time-bar{display:inline-block;height:18px;background:linear-gradient(90deg,#0f3460,#00d4ff);border-radius:3px;vertical-align:middle;min-width:2px}
.gallery{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px}
.gallery img{width:100%;border-radius:8px;border:1px solid #333}
.gallery .item{text-align:center}
.gallery .cap{margin-top:5px;font-size:12px;color:#888}
</style></head><body>
<h1>📊 接口2女性5种发型测试报告</h1>
<div class="subtitle">Plan Bplus 2026-07-18 13:50:43 5 次调用</div>
<div class="summary">
<div class="card ok"><div class="num">5</div><div class="label">成功</div></div>
<div class="card err"><div class="num">0</div><div class="label">失败</div></div>
<div class="card time"><div class="num">7.9s</div><div class="label">平均耗时</div></div>
</div>
<div class="section-title">详细结果</div>
<table><thead><tr><th>发型</th><th>名称</th><th>耗时</th><th>显存前</th><th>显存后</th><th>Δ</th><th>状态</th></tr></thead><tbody>
<tr><td>style 1</td><td>椭圆</td><td><div class="time-bar" style="width:119.10000000000001px"></div> 7.9s</td><td>25875MB</td><td>25889MB</td><td>+14</td><td><span class="ok"></span></td></tr><tr><td>style 2</td><td>花瓣</td><td><div class="time-bar" style="width:120.6px"></div> 8.0s</td><td>25889MB</td><td>28337MB</td><td>+2448</td><td><span class="ok"></span></td></tr><tr><td>style 3</td><td>心形</td><td><div class="time-bar" style="width:116.55px"></div> 7.8s</td><td>28337MB</td><td>28355MB</td><td>+18</td><td><span class="ok"></span></td></tr><tr><td>style 4</td><td>直线</td><td><div class="time-bar" style="width:114.6px"></div> 7.6s</td><td>28355MB</td><td>28333MB</td><td>-22</td><td><span class="ok"></span></td></tr><tr><td>style 5</td><td>波浪</td><td><div class="time-bar" style="width:123.89999999999999px"></div> 8.3s</td><td>28333MB</td><td>25885MB</td><td>-2448</td><td><span class="ok"></span></td></tr></tbody></table><div class="section-title">生成图片</div><div class="gallery"><div class="item"><img src="hair_style_1_椭圆.jpg"><div class="cap">椭圆 (7.9s)</div></div><div class="item"><img src="hair_style_2_花瓣.jpg"><div class="cap">花瓣 (8.0s)</div></div><div class="item"><img src="hair_style_3_心形.jpg"><div class="cap">心形 (7.8s)</div></div><div class="item"><img src="hair_style_4_直线.jpg"><div class="cap">直线 (7.6s)</div></div><div class="item"><img src="hair_style_5_波浪.jpg"><div class="cap">波浪 (8.3s)</div></div></div></body></html>
@@ -0,0 +1,72 @@
[
{
"plan": "Bplus",
"hair_style": 1,
"hair_style_name": "椭圆",
"timestamp": "13:50:11",
"elapsed_s": 7.94,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25875,
"vram_after_mb": 25889,
"vram_delta_mb": 14,
"image_path": "/home/ubuntu/hair/benchmark_out/iface2_female_Bplus/hair_style_1_椭圆.jpg"
},
{
"plan": "Bplus",
"hair_style": 2,
"hair_style_name": "花瓣",
"timestamp": "13:50:19",
"elapsed_s": 8.04,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25889,
"vram_after_mb": 28337,
"vram_delta_mb": 2448,
"image_path": "/home/ubuntu/hair/benchmark_out/iface2_female_Bplus/hair_style_2_花瓣.jpg"
},
{
"plan": "Bplus",
"hair_style": 3,
"hair_style_name": "心形",
"timestamp": "13:50:27",
"elapsed_s": 7.77,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28337,
"vram_after_mb": 28355,
"vram_delta_mb": 18,
"image_path": "/home/ubuntu/hair/benchmark_out/iface2_female_Bplus/hair_style_3_心形.jpg"
},
{
"plan": "Bplus",
"hair_style": 4,
"hair_style_name": "直线",
"timestamp": "13:50:35",
"elapsed_s": 7.64,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28355,
"vram_after_mb": 28333,
"vram_delta_mb": -22,
"image_path": "/home/ubuntu/hair/benchmark_out/iface2_female_Bplus/hair_style_4_直线.jpg"
},
{
"plan": "Bplus",
"hair_style": 5,
"hair_style_name": "波浪",
"timestamp": "13:50:43",
"elapsed_s": 8.26,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28333,
"vram_after_mb": 25885,
"vram_delta_mb": -2448,
"image_path": "/home/ubuntu/hair/benchmark_out/iface2_female_Bplus/hair_style_5_波浪.jpg"
}
]
File diff suppressed because one or more lines are too long
@@ -0,0 +1,562 @@
[
{
"round": 1,
"test_id": "iface1",
"test_name": "接口1-测量",
"timestamp": "13:36:27",
"elapsed_s": 0.37,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25303,
"vram_after_mb": 25425,
"vram_free_before_mb": 6818,
"vram_free_after_mb": 6696,
"vram_delta_mb": 122,
"procs_before": {
"858": 3242,
"24879": 8476,
"271640": 12524,
"298802": 1015
},
"procs_after": {
"858": 3242,
"24879": 8476,
"271640": 12524,
"298802": 1137
},
"procs_changed": true
},
{
"round": 1,
"test_id": "iface2f",
"test_name": "接口2-女-椭圆",
"timestamp": "13:36:35",
"elapsed_s": 7.54,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25425,
"vram_after_mb": 25887,
"vram_free_before_mb": 6696,
"vram_free_after_mb": 6234,
"vram_delta_mb": 462,
"procs_before": {
"858": 3242,
"24879": 8476,
"271640": 12524,
"298802": 1137
},
"procs_after": {
"858": 3242,
"24879": 8522,
"271640": 12492,
"298802": 1585
},
"procs_changed": true
},
{
"round": 1,
"test_id": "iface2m",
"test_name": "接口2-男-椭圆",
"timestamp": "13:36:39",
"elapsed_s": 4.11,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25887,
"vram_after_mb": 28383,
"vram_free_before_mb": 6234,
"vram_free_after_mb": 3738,
"vram_delta_mb": 2496,
"procs_before": {
"858": 3242,
"24879": 8522,
"271640": 12492,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8522,
"271640": 14988,
"298802": 1585
},
"procs_changed": true
},
{
"round": 1,
"test_id": "iface3",
"test_name": "接口3-B端生发",
"timestamp": "13:36:42",
"elapsed_s": 3.12,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28383,
"vram_after_mb": 28415,
"vram_free_before_mb": 3738,
"vram_free_after_mb": 3706,
"vram_delta_mb": 32,
"procs_before": {
"858": 3242,
"24879": 8522,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8522,
"271640": 15020,
"298802": 1585
},
"procs_changed": true
},
{
"round": 2,
"test_id": "iface1",
"test_name": "接口1-测量",
"timestamp": "13:36:42",
"elapsed_s": 0.08,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28415,
"vram_after_mb": 28415,
"vram_free_before_mb": 3706,
"vram_free_after_mb": 3706,
"vram_delta_mb": 0,
"procs_before": {
"858": 3242,
"24879": 8522,
"271640": 15020,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8522,
"271640": 15020,
"298802": 1585
},
"procs_changed": false
},
{
"round": 2,
"test_id": "iface2f",
"test_name": "接口2-女-椭圆",
"timestamp": "13:36:50",
"elapsed_s": 7.51,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28415,
"vram_after_mb": 28353,
"vram_free_before_mb": 3706,
"vram_free_after_mb": 3768,
"vram_delta_mb": -62,
"procs_before": {
"858": 3242,
"24879": 8522,
"271640": 15020,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8492,
"271640": 14988,
"298802": 1585
},
"procs_changed": true
},
{
"round": 2,
"test_id": "iface2m",
"test_name": "接口2-男-椭圆",
"timestamp": "13:36:54",
"elapsed_s": 4.0,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28353,
"vram_after_mb": 28353,
"vram_free_before_mb": 3768,
"vram_free_after_mb": 3768,
"vram_delta_mb": 0,
"procs_before": {
"858": 3242,
"24879": 8492,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8492,
"271640": 14988,
"298802": 1585
},
"procs_changed": false
},
{
"round": 2,
"test_id": "iface3",
"test_name": "接口3-B端生发",
"timestamp": "13:36:57",
"elapsed_s": 3.27,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28353,
"vram_after_mb": 28353,
"vram_free_before_mb": 3768,
"vram_free_after_mb": 3768,
"vram_delta_mb": 0,
"procs_before": {
"858": 3242,
"24879": 8492,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8492,
"271640": 14988,
"298802": 1585
},
"procs_changed": false
},
{
"round": 3,
"test_id": "iface1",
"test_name": "接口1-测量",
"timestamp": "13:36:58",
"elapsed_s": 0.07,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28353,
"vram_after_mb": 28353,
"vram_free_before_mb": 3768,
"vram_free_after_mb": 3768,
"vram_delta_mb": 0,
"procs_before": {
"858": 3242,
"24879": 8492,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8492,
"271640": 14988,
"298802": 1585
},
"procs_changed": false
},
{
"round": 3,
"test_id": "iface2f",
"test_name": "接口2-女-椭圆",
"timestamp": "13:37:05",
"elapsed_s": 7.63,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28353,
"vram_after_mb": 25855,
"vram_free_before_mb": 3768,
"vram_free_after_mb": 6266,
"vram_delta_mb": -2498,
"procs_before": {
"858": 3242,
"24879": 8492,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8490,
"271640": 12492,
"298802": 1585
},
"procs_changed": true
},
{
"round": 3,
"test_id": "iface2m",
"test_name": "接口2-男-椭圆",
"timestamp": "13:37:09",
"elapsed_s": 4.1,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25855,
"vram_after_mb": 28351,
"vram_free_before_mb": 6266,
"vram_free_after_mb": 3770,
"vram_delta_mb": 2496,
"procs_before": {
"858": 3242,
"24879": 8490,
"271640": 12492,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8490,
"271640": 14988,
"298802": 1585
},
"procs_changed": true
},
{
"round": 3,
"test_id": "iface3",
"test_name": "接口3-B端生发",
"timestamp": "13:37:13",
"elapsed_s": 3.13,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28351,
"vram_after_mb": 28351,
"vram_free_before_mb": 3770,
"vram_free_after_mb": 3770,
"vram_delta_mb": 0,
"procs_before": {
"858": 3242,
"24879": 8490,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8490,
"271640": 14988,
"298802": 1585
},
"procs_changed": false
},
{
"round": 4,
"test_id": "iface1",
"test_name": "接口1-测量",
"timestamp": "13:37:13",
"elapsed_s": 0.07,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28351,
"vram_after_mb": 28351,
"vram_free_before_mb": 3770,
"vram_free_after_mb": 3770,
"vram_delta_mb": 0,
"procs_before": {
"858": 3242,
"24879": 8490,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8490,
"271640": 14988,
"298802": 1585
},
"procs_changed": false
},
{
"round": 4,
"test_id": "iface2f",
"test_name": "接口2-女-椭圆",
"timestamp": "13:37:20",
"elapsed_s": 7.32,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28351,
"vram_after_mb": 28337,
"vram_free_before_mb": 3770,
"vram_free_after_mb": 3784,
"vram_delta_mb": -14,
"procs_before": {
"858": 3242,
"24879": 8490,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8476,
"271640": 14988,
"298802": 1585
},
"procs_changed": true
},
{
"round": 4,
"test_id": "iface2m",
"test_name": "接口2-男-椭圆",
"timestamp": "13:37:24",
"elapsed_s": 4.21,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28337,
"vram_after_mb": 28337,
"vram_free_before_mb": 3784,
"vram_free_after_mb": 3784,
"vram_delta_mb": 0,
"procs_before": {
"858": 3242,
"24879": 8476,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8476,
"271640": 14988,
"298802": 1585
},
"procs_changed": false
},
{
"round": 4,
"test_id": "iface3",
"test_name": "接口3-B端生发",
"timestamp": "13:37:28",
"elapsed_s": 3.18,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28337,
"vram_after_mb": 28337,
"vram_free_before_mb": 3784,
"vram_free_after_mb": 3784,
"vram_delta_mb": 0,
"procs_before": {
"858": 3242,
"24879": 8476,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8476,
"271640": 14988,
"298802": 1585
},
"procs_changed": false
},
{
"round": 5,
"test_id": "iface1",
"test_name": "接口1-测量",
"timestamp": "13:37:28",
"elapsed_s": 0.08,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28337,
"vram_after_mb": 28337,
"vram_free_before_mb": 3784,
"vram_free_after_mb": 3784,
"vram_delta_mb": 0,
"procs_before": {
"858": 3242,
"24879": 8476,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8476,
"271640": 14988,
"298802": 1585
},
"procs_changed": false
},
{
"round": 5,
"test_id": "iface2f",
"test_name": "接口2-女-椭圆",
"timestamp": "13:37:35",
"elapsed_s": 7.43,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28337,
"vram_after_mb": 25855,
"vram_free_before_mb": 3784,
"vram_free_after_mb": 6266,
"vram_delta_mb": -2482,
"procs_before": {
"858": 3242,
"24879": 8476,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8490,
"271640": 12492,
"298802": 1585
},
"procs_changed": true
},
{
"round": 5,
"test_id": "iface2m",
"test_name": "接口2-男-椭圆",
"timestamp": "13:37:39",
"elapsed_s": 4.07,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 25855,
"vram_after_mb": 28351,
"vram_free_before_mb": 6266,
"vram_free_after_mb": 3770,
"vram_delta_mb": 2496,
"procs_before": {
"858": 3242,
"24879": 8490,
"271640": 12492,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8490,
"271640": 14988,
"298802": 1585
},
"procs_changed": true
},
{
"round": 5,
"test_id": "iface3",
"test_name": "接口3-B端生发",
"timestamp": "13:37:43",
"elapsed_s": 3.11,
"success": true,
"code": 0,
"error": null,
"vram_before_mb": 28351,
"vram_after_mb": 28383,
"vram_free_before_mb": 3770,
"vram_free_after_mb": 3738,
"vram_delta_mb": 32,
"procs_before": {
"858": 3242,
"24879": 8490,
"271640": 14988,
"298802": 1585
},
"procs_after": {
"858": 3242,
"24879": 8490,
"271640": 15020,
"298802": 1585
},
"procs_changed": true
}
]
+87
View File
@@ -0,0 +1,87 @@
nohup: ignoring input
轮换测试:4 个接口 × 5 轮 = 20 次调用
测试图片:/home/ubuntu/hair/image/girl_img/girl13.jpg
============================================================
第 1/5 轮
============================================================
[1/5] 接口1-测量 ...
→ ✓ code=0 time=0.4s vram=25303→25425MB (Δ+122) ⚠模型换出!
[1/5] 接口2-女-椭圆 ...
→ ✓ code=0 time=7.5s vram=25425→25887MB (Δ+462) ⚠模型换出!
[1/5] 接口2-男-椭圆 ...
→ ✓ code=0 time=4.1s vram=25887→28383MB (Δ+2496) ⚠模型换出!
[1/5] 接口3-B端生发 ...
→ ✓ code=0 time=3.1s vram=28383→28415MB (Δ+32) ⚠模型换出!
============================================================
第 2/5 轮
============================================================
[2/5] 接口1-测量 ...
→ ✓ code=0 time=0.1s vram=28415→28415MB (Δ+0)
[2/5] 接口2-女-椭圆 ...
→ ✓ code=0 time=7.5s vram=28415→28353MB (Δ-62) ⚠模型换出!
[2/5] 接口2-男-椭圆 ...
→ ✓ code=0 time=4.0s vram=28353→28353MB (Δ+0)
[2/5] 接口3-B端生发 ...
→ ✓ code=0 time=3.3s vram=28353→28353MB (Δ+0)
============================================================
第 3/5 轮
============================================================
[3/5] 接口1-测量 ...
→ ✓ code=0 time=0.1s vram=28353→28353MB (Δ+0)
[3/5] 接口2-女-椭圆 ...
→ ✓ code=0 time=7.6s vram=28353→25855MB (Δ-2498) ⚠模型换出!
[3/5] 接口2-男-椭圆 ...
→ ✓ code=0 time=4.1s vram=25855→28351MB (Δ+2496) ⚠模型换出!
[3/5] 接口3-B端生发 ...
→ ✓ code=0 time=3.1s vram=28351→28351MB (Δ+0)
============================================================
第 4/5 轮
============================================================
[4/5] 接口1-测量 ...
→ ✓ code=0 time=0.1s vram=28351→28351MB (Δ+0)
[4/5] 接口2-女-椭圆 ...
→ ✓ code=0 time=7.3s vram=28351→28337MB (Δ-14) ⚠模型换出!
[4/5] 接口2-男-椭圆 ...
→ ✓ code=0 time=4.2s vram=28337→28337MB (Δ+0)
[4/5] 接口3-B端生发 ...
→ ✓ code=0 time=3.2s vram=28337→28337MB (Δ+0)
============================================================
第 5/5 轮
============================================================
[5/5] 接口1-测量 ...
→ ✓ code=0 time=0.1s vram=28337→28337MB (Δ+0)
[5/5] 接口2-女-椭圆 ...
→ ✓ code=0 time=7.4s vram=28337→25855MB (Δ-2482) ⚠模型换出!
[5/5] 接口2-男-椭圆 ...
→ ✓ code=0 time=4.1s vram=25855→28351MB (Δ+2496) ⚠模型换出!
[5/5] 接口3-B端生发 ...
→ ✓ code=0 time=3.1s vram=28351→28383MB (Δ+32) ⚠模型换出!
✅ 完成!/home/ubuntu/hair/benchmark_out/rotation/rotation_results.json
📄 报告:/home/ubuntu/hair/benchmark_out/rotation/rotation_report.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Some files were not shown because too many files have changed in this diff Show More