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>
This commit is contained in:
xsl
2026-06-14 23:32:21 +08:00
co-authored by Claude Opus 4.8
parent 554b64a916
commit 4c69bb4623
2 changed files with 119 additions and 5 deletions
+111 -2
View File
@@ -242,5 +242,114 @@ transformers>=4.40 # SegFormer 人脸分割
---
> **文档版本**: v1.0 **创建日期**: 2026-06-14 算法来源: head3d502 点 mesh + UV)| 运行位置: worker(GPU)
> **本期产出**: 发际线曲线叠加预览图(非最终生发图)
## 10. 第二步:生发图生成(ComfyUI + Flux inpaint)★ 新增
> 在「发际线预览」基础上,**新增真实生发后图片**:把发际线划线 + 遮罩送入本机
> ComfyUIFlux-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 step2headmark 用 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*(1mask))` # **透明=重绘区**,对齐 ComfyUI `mask=1alpha`
- `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 时每种附带 grownhandler 返回新字段 | curlresults 含 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-14v1.1:新增 §10 生发图生成 ComfyUI 管线)
> 算法来源: head3d502 点 mesh + UV+ Flux-2 Klein 9bComfyUI add_hair.json)| 运行位置: worker(GPU) + 本机 ComfyUI(8182)
> **产出**: ① 发际线曲线叠加预览图 ② 生发后图片(植发 3 个月效果)
+8 -3
View File
@@ -198,10 +198,15 @@
| 字段 | 类型 | 说明 |
|------|------|------|
| image_url | string | 方案预览图 URL当前 = 发际线叠加图) |
| image_url | string | 方案**预览图** URL(发际线曲线叠加图) |
| grown_image_url | string | **生发后图片** URLComfyUI/Flux「植发 3 个月」效果图) |
| hairline_type | string | 发际线类型 key`ellipse`/`flower`/`heart`/`straight`/`wave`female),`ellipse`/`m`/`straight`/`inverse_arc`male |
| order | int | 排序序号(当前阶段固定 `1..N`,按贴图顺序,暂不计算合适度) |
> ⚠️ 生发图由本机 ComfyUIFlux-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 }
]
}
}