Files
hair/docs/发际线生发遮罩算法_pushed模式.md
T
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

6.1 KiB
Raw Blame History

发际线生发遮罩算法(pushed 模式)

对应接口11 /api/v1/hairline/grow、接口12 /api/v1/hairline/grow_v2。 遮罩算法固定为 pushed,融合算法固定为 multiband(已移除其他选项)。 代码:face_analysis/hairline_grow.py_extract_hairline / _pushed_mask / compute_mask)。

概述

pushed 是发际线生发的唯一遮罩算法,multiband(多频段金字塔)是唯一融合算法。它从头发分割结果中提取「头发/皮肤交界线」(发际线),以眉心为圆心逐点径向外推一段距离,与 baseline 组成闭合区域作为最终遮罩。这样遮罩顶部会覆盖现有头发下沿一小段,贴回生发结果时顶部与真头发重叠、过渡自然。

接口12 /api/v1/hairline/grow_v2 只需传 image + hairline_id,遮罩和融合全部固定,无需任何算法选择参数。

算法流程(5 步)

①-a baseline 分割线      ← 眉骨/glabella 关键点折线(含 151 中心点)
①-b 上半区 upper         ← baseline 以上的区域(裁剪范围)
①-c 头发分割 hair_mask    ← segformer/bisenet 的原始头发像素
①-f 头发内轮廓线          ← 取头发轮廓中朝脸一侧的那段(额头弧+两侧到下颌),有序折线
①-g 径向外推 + 成带      ← 以 151 点为圆心把内轮廓逐点向外推 push_px,内轮廓↔外推线之间的带 = 最终遮罩

①-d(填充到基线 top_fill)、①-e(闭合区域 closed)是旧 eroded/closed 模式的中间产物,pushed 模式不走这条流程,前端不展示。

①-a baseline 分割线

MediaPipe 人脸关键点 [21,68,104,69,108,151,337,299,333,298,251] 连成折线(左端 21 → 中心 151 → 右端 251),再向左右边缘水平延长。151 点(glabella/眉心)是后续径向外推的圆心。代码 _baseline_points / _draw_baseline

①-b 上半区 upper

baseline 折线以上的多边形区域(_upper_region_mask)。作为后续裁剪范围,保证遮罩不越界到下半脸。

①-c 头发分割 hair_mask

segformer(默认)或 bisenet 得到的头发二值掩码。

①-f 头发内轮廓线(核心改动)

代码 _extract_hairline。目标是提取「头发区域朝脸一侧的内轮廓线」:额头弧 + 左右两侧鬓角/脸颊边界,一直向下到下颌,是一条有序折线(不再是逐列一个 y 的数组,因为两侧近乎竖直、一个 x 对多个 y)。

  1. 取头发轮廓hair_mask 最大连通域,cv2.findContours(RETR_EXTERNAL, CHAIN_APPROX_NONE) 取稠密、保序的外轮廓点。
  2. 内侧判定:轮廓同时含「朝背景的外侧剪影」和「朝脸的内轮廓」。对每个轮廓点,朝脸中心 151 方向采样 sample_px(≈0.4cm)像素,落点若是非头发像素 → 该点朝向脸(内轮廓);否则是外侧剪影,丢弃。
  3. 取最长连续内侧段:内轮廓点在闭合轮廓上本是一段连续弧,先做 1D 环形闭运算填掉判定抖动的小缝,再取最长连续 True 段并保序。
  4. 下颌截断:丢掉 y > chin_y(下巴关键点 152 的 y)的点,把两侧末端截到下颌一带 → 得到「环脸」内轮廓弧。

关键点:不再用 baseline 做水平截断、也不再逐列取下沿;截断改为「朝脸内侧」判定 + 下颌 y 截断,因此能同时拿到额头弧和两侧竖直边界。

①-g 径向外推 + 闭合区域(最终遮罩)

代码 _pushed_mask

  1. 逐点径向外推:圆心 = 151 点 (cx, cy)。对内轮廓上每个点 (x, y),沿「从圆心指向它」的单位向量 (ux, uy) 向外(远离脸中心 = 推进现有头发)外推 push_px,得到外推线(黄线)点 (x + ux·push_px, y + uy·push_px)push_px = hairline_push_cm × px_per_cm(默认 1cm)。
  2. 逐列归并:只取外推线中落在 baseline 以上的点,逐列取最靠上的 y 作为遮罩顶界 pushed_y[x];空列线性插值填补。两侧鬓角落到 baseline 以下的段落自然被排除。
  3. 与 baseline 组闭合区域:逐列从 pushed_y[x] 填充到 baseline_y[x](仅 pushed_y < baseline_y 的列),& upper 去越界、_largest_cc 保留最大连通域。

最终遮罩 = 外推发际线(①-g 黄线)与 baseline 分割线(①-a)组成的闭合区域:顶界=外推发际线(覆盖现有头发约 push_cm),底界=baseline。与旧逻辑一致,区别只是 pushed_y 现在来自修正后的整条内轮廓,额头弧已延伸到两侧鬓角,额头遮罩宽度不再被截短。

关键参数

遮罩算法(pushed)和融合算法(multiband)已固定,接口不再暴露选择参数。可调的只有:

参数 默认 说明
hairline_push_cm 1.0 内轮廓径向外推距离(厘米),= push_px / px_per_cm。px_per_cm 由虹膜直径标定
hairline_edge column 兼容保留的入参;内轮廓提取(轮廓+内侧判定)不再按它分支,取值不影响结果
mb_levels 5 多频段金字塔层数(2~6,越大低频色差抹得越宽)
erode_cm 0.6(接口12 固定) baseline 参考内缩距离,对 pushed 影响很小

与旧模式(eroded/closed,已移除)的区别

eroded/closed(已移除) pushed(当前唯一)
遮罩来源 top_fill(头发向下填充含额头)外缘内缩 头发内轮廓线 径向外推成带
截断方式 用 baseline 组上半区 upper 内侧判定 + 下颌 y 截断(不再用 baseline)
遮罩形状 整个额头闭合区域 沿内轮廓的环脸带(额头弧+两侧,压住现有头发 push_cm)

调试

  • 调试页:http://<host>:8187/static/test_interface11_debug.html(带前后端日志面板、下载日志按钮)
  • 后端日志:/home/xsl/hair/log/hairline_grow.log(按 [rid] 关联一次请求),下载接口 /api/v1/debug/hairline_log?rid=<id>&tail=500
  • 可视化步骤:①-a baseline / ①-b upper / ①-c 头发分割 / ①-f 交界线 / ①-g 外推+遮罩 / 最终遮罩 / 生成 / 贴回 / 融合