From 4c69bb46231eb715330f0b3adada042202298e29 Mon Sep 17 00:00:00 2001 From: xsl Date: Sun, 14 Jun 2026 23:32:21 +0800 Subject: [PATCH] =?UTF-8?q?docs(=E6=8E=A5=E5=8F=A32):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=94=9F=E5=8F=91=E5=9B=BE=E8=AE=BE=E8=AE=A1(=C2=A710=20ComfyU?= =?UTF-8?q?I+Flux)=20+=20headmark=E9=81=AE=E7=BD=A9=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 技术方案 §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 --- docs/接口2-C端生发-技术实现方案.md | 113 ++++++++++++++++++++++++++++- docs/接口文档.md | 11 ++- 2 files changed, 119 insertions(+), 5 deletions(-) diff --git a/docs/接口2-C端生发-技术实现方案.md b/docs/接口2-C端生发-技术实现方案.md index 83d1f71..126ff12 100644 --- a/docs/接口2-C端生发-技术实现方案.md +++ b/docs/接口2-C端生发-技术实现方案.md @@ -242,5 +242,114 @@ transformers>=4.40 # SegFormer 人脸分割 --- -> **文档版本**: v1.0 | **创建日期**: 2026-06-14 | 算法来源: head3d(502 点 mesh + UV)| 运行位置: worker(GPU) -> **本期产出**: 发际线曲线叠加预览图(非最终生发图) +## 10. 第二步:生发图生成(ComfyUI + Flux inpaint)★ 新增 + +> 在「发际线预览」基础上,**新增真实生发后图片**:把发际线划线 + 遮罩送入本机 +> ComfyUI(Flux-2 Klein 9b,端口 **8182**)跑 `add_hair.json` 工作流,得到「植发 3 +> 个月」效果图。**worker 不跑 Flux**,只做图像准备 + 调 ComfyUI HTTP API + 取回结果。 + +### 10.1 需求与决策(已与需求方确认) + +| 项 | 决策 | +|----|------| +| 生成数量 | **一次请求生成该性别全部 N 种**(female 5 / male 4),与预览一一对应 | +| 遮罩区域 | **头发区域 ∪ 新发际线以下** —— SegFormer 头部(头发)区域,下边界拓到新发际线曲线 | +| 返回方式 | **同步阻塞**到 ComfyUI 出图再返回(N 张串行,单请求耗时可达数分钟,网关需调大超时) | +| ComfyUI 接入 | 标准 HTTP API(`/upload/image` + `/prompt` + `/history` + `/view`),8182 无鉴权,自定义节点已装齐,`noise_seed` 每次随机 | + +### 10.2 `add_hair.json` 工作流解读 + +Flux-2 Klein 9b 的参考式局部重绘(denoise=1 + ReferenceLatent): +- **节点 26 `LoadImage`** 是唯一外部输入,同时给出 **图像**([0])和 **遮罩**([1],从 PNG 的 + alpha 通道取,ComfyUI 约定 `mask = 1 − alpha`,即 **alpha 透明处 = 要重绘的区域**)。 +- 提示词(节点 60):保留原图一切,**先清除画面内所有黑色标注划线**,仅在划线范围内生成 + 「植发 3 个月」头发,发际线边界刚好止于划线处,与原生发自然衔接。 +- 节点 32/37/39/44 做遮罩填洞、缩放到 1024、ImageAndMaskPreview 组装 → VAEEncode 参考。 +- 节点 10 VAEDecode → 节点 62 ColorMatch(与原图调色一致)→ 节点 17 SaveImage = 生发图。 + +> **关键**:worker 要做的就是**程序化复现「手绘 painted-masked」的输入**——给节点 26 一张 +> RGBA:**RGB = 画了黑色发际线划线的照片,alpha = 遮罩(重绘区透明)**。工作流其余不动。 + +### 10.3 遮罩算法(参考 `/home/xsl/headmark`,用黑贴图简化) + +headmark(发际线蒙板工具)的 5 步法:① MediaPipe 取**额头上半区域** → ② **整个头部分割** → +③ 两者**交集** = ROI → ④ 在 ROI 内**找发际线**(手绘划线)→ ⑤ 发际线 + 头型**围成闭合区域**填充 = 遮罩。 + +> **我们的简化**:发际线不是手绘、需要检测的;而是用 `hairline_texture_black/` 的黑曲线 +> **程序化渲染**出来——位置已知,**省掉 headmark 第 4 步的检测**,直接拿渲染出的曲线当边界。 + +``` +已有:502 点、SegFormer parse_map +build_inpaint_mask(photo, parse_map, landmarks, hairline_texture_black/t): + [1] upper_region = MediaPipe 额头边界关键点 + [21,68,104,69,108,151,337,299,333,298,251] 连线,向上+两侧补到图像边缘,填充 + (= headmark step1:发际线以上的"上部区域") + [2] head_mask = SegFormer 头部轮廓(hair ∪ skin ∪ 其余面部类,排除 bg/neck/cloth) + (= headmark step2;headmark 用 head-segmentation 包/Selfie,本项目复用已加载的 SegFormer) + [3] roi = upper_region ∩ head_mask (= headmark step3) + [4] 划线图 marked + curve_mask = render(photo, 502点, 黑贴图 t) # 烧黑线 + 得到曲线像素 + [5] mask = roi 中"在发际线曲线以上(更小 y)"的部分 → 闭运算去洞 + 取最大连通域填充 + 轻羽化 + (= headmark step5:发际线曲线 + ROI 上边界围成的闭合区域) + return marked(划线图), mask +``` + +for 每种发际线贴图 t(该性别全部): +- `marked, mask = build_inpaint_mask(...)` +- `comfy_input = RGBA(rgb=marked, alpha=255*(1−mask))` # **透明=重绘区**,对齐 ComfyUI `mask=1−alpha` +- `grown = comfyui_run(comfy_input)`(§10.4) +- `results[t] = { preview(白线预览,已有), grown(生发图,新增) }` + +- `hairline_texture_black/`:与 `hairline_texture/` 同 9 张曲线,但**黑色**,烧划线 + 当遮罩下边界。 +- **头部分割来源**:先复用已加载的 SegFormer(零新增依赖);若头型轮廓不够干净,可改用 + headmark 同款 `head-segmentation` 包(子进程,避免与 MediaPipe GPU 冲突)。 +- 遮罩边界精度 **M5 必须 dump 可视化核验**(划线图 / ROI / 最终 mask 三张叠图)。 + +### 10.4 ComfyUI 客户端(`hairline/comfyui.py`,新增) + +``` +COMFYUI_URL = env COMFYUI_URL (默认 http://127.0.0.1:8182) +WORKFLOW = add_hair.json(启动时加载一次) +run(comfy_input_png_bytes): + 1. POST /upload/image (multipart) → {name, subfolder, type:"input"} + 2. wf = deepcopy(WORKFLOW); wf["26"]["inputs"]["image"] = name + wf["6"]["inputs"]["noise_seed"] = 随机 + 3. POST /prompt {prompt: wf, client_id} → prompt_id + 4. 轮询 GET /history/{prompt_id} 直到完成(带超时) + 5. node "17".images[0] → GET /view?filename&subfolder&type=output → PNG bytes + 返回 PNG bytes +``` + +### 10.5 接口契约变更(同步更新 `接口文档.md`) + +`/api/v1/hair/grow` 的 `results[]` 每项**新增生发图字段**(worker 返回 base64,网关落盘改 URL): + +| 字段 | 说明 | +|------|------| +| `image_base64` | (已有)发际线**预览图**(白线叠加) | +| `grown_image_base64` | (新增)**生发后图片**(ComfyUI 出图) | +| `hairline_type` / `order` | 同前 | + +> ⚠️ **同步 + N 张 Flux** → 单请求很慢。网关/前端超时要放大;worker 自身并发=1。 +> 失败处理:某张 ComfyUI 失败时该项 `grown_image_base64` 置空并标记,不整请求失败(待定,实现期确认)。 + +### 10.6 开发步骤(M5+) + +| 阶段 | 内容 | 验证 | +|------|------|------| +| **M5 遮罩** | `build_inpaint_mask` + 黑线渲染 + 合成 RGBA | 目视:划线图正确、遮罩=头发∪发际线以下,透明区对 | +| **M6 ComfyUI 客户端** | `comfyui.py` 跑通一张(8182 起服务后) | 上传→prompt→取回 PNG,得到生发图 | +| **M7 接 service/app** | generate 时每种附带 grown,handler 返回新字段 | curl:results 含 grown_image_base64(合法 PNG) | +| **M8 文档/测试** | 更新接口文档;mock ComfyUI 的单测 + 真机冒烟 | pytest 绿;真机端到端出生发图 | + +### 10.7 新增风险 + +1. **耗时**:同步 N 张 Flux,单请求数分钟级;需评估是否后续改异步/队列。 +2. **ComfyUI 依赖外部进程**:8182 未起/模型未加载/节点缺失 → 该接口失败;worker `/health` 不体现 ComfyUI 状态(可加可选探测)。 +3. **遮罩精度**:直接决定生发位置与自然度;M5 必须可视化核验,必要时拿手绘样本标定。 +4. **GPU 共享**:ComfyUI 与接口1/2 的 CPU 推理同机;显存/算力调度需观察(ComfyUI 自带 torch,支持 5090)。 + +--- + +> **文档版本**: v1.1 | **创建日期**: 2026-06-14(v1.1:新增 §10 生发图生成 ComfyUI 管线) +> 算法来源: head3d(502 点 mesh + UV)+ Flux-2 Klein 9b(ComfyUI add_hair.json)| 运行位置: worker(GPU) + 本机 ComfyUI(8182) +> **产出**: ① 发际线曲线叠加预览图 ② 生发后图片(植发 3 个月效果) diff --git a/docs/接口文档.md b/docs/接口文档.md index 27b2dab..1ccf3d4 100644 --- a/docs/接口文档.md +++ b/docs/接口文档.md @@ -198,10 +198,15 @@ | 字段 | 类型 | 说明 | |------|------|------| -| image_url | string | 方案预览图 URL(当前 = 发际线叠加图) | +| image_url | string | 方案**预览图** URL(发际线曲线叠加图) | +| grown_image_url | string | **生发后图片** URL(ComfyUI/Flux「植发 3 个月」效果图) | | hairline_type | string | 发际线类型 key:`ellipse`/`flower`/`heart`/`straight`/`wave`(female),`ellipse`/`m`/`straight`/`inverse_arc`(male) | | order | int | 排序序号(当前阶段固定 `1..N`,按贴图顺序,暂不计算合适度) | +> ⚠️ 生发图由本机 ComfyUI(Flux-2,端口 8182)生成,**一次请求生成全部 N 张、同步返回**, +> 单请求耗时可达数分钟,调用方超时需放大。worker 侧返回 `image_base64` / `grown_image_base64`, +> 网关落盘后改写为上表的 `image_url` / `grown_image_url`。 + ### 响应示例 ```json @@ -211,8 +216,8 @@ "request_id": "mock-request-id", "data": { "results": [ - { "image_url": "https://hair.xiangsilian.com/static/annotations/uuid1.png", "hairline_type": "ellipse", "order": 1 }, - { "image_url": "https://hair.xiangsilian.com/static/annotations/uuid2.png", "hairline_type": "flower", "order": 2 } + { "image_url": "https://hair.xiangsilian.com/static/annotations/uuid1.png", "grown_image_url": "https://hair.xiangsilian.com/static/annotations/grown1.png", "hairline_type": "ellipse", "order": 1 }, + { "image_url": "https://hair.xiangsilian.com/static/annotations/uuid2.png", "grown_image_url": "https://hair.xiangsilian.com/static/annotations/grown2.png", "hairline_type": "flower", "order": 2 } ] } }