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

5.1 KiB
Raw Blame History

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

对应接口11 /api/v1/hairline/grow、接口12 /api/v1/hairline/grow_v2mask_type=pushed。 代码:face_analysis/hairline_grow.py_extract_hairline / _pushed_mask / compute_mask)。

概述

pushed 模式是发际线生发的默认遮罩算法(接口11/12 的 mask_type 三选一:eroded / closed / pushed,当前只用 pushed)。它从头发分割结果中提取「头发/皮肤交界线」(发际线),以眉心为圆心逐点径向外推一段距离,与 baseline 组成闭合区域作为最终遮罩。这样遮罩顶部会覆盖现有头发下沿一小段,贴回生发结果时顶部与真头发重叠、过渡自然。

算法流程(5 步)

①-a baseline 分割线      ← 眉骨/glabella 关键点折线(含 151 中心点)
①-b 上半区 upper         ← baseline 以上的区域(裁剪范围)
①-c 头发分割 hair_mask    ← segformer/bisenet 的原始头发像素
①-f 头发/皮肤交界线       ← 逐列取头发下沿,用 baseline 水平 y 线截断
①-g 径向外推 + 闭合      ← 以 151 点为圆心逐点外推,与 baseline 组闭合区域 = 最终遮罩

①-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

  1. 逐列取头发下沿:对每个 x 列,取 hair_mask 中最靠下的头发像素 y 坐标(column 模式)。
  2. baseline 水平截断:逐列计算 baseline 折线的 y 值 baseline_y[x],只保留「发际线 y < baseline_y」的列(baseline 线以上 = 额头+发际线区域;baseline 以下 = 脸下半部,丢弃)。
  3. 结果 hairline_y[x]:长度 = 图宽的数组,baseline 以下或无头发处为 NaN。

关键点:截断是水平方向用 baseline 的 y 值切割,不是竖线、不是 baseline 的 x 范围。这样得到的是真实的头发/皮肤交界弧线。

mode=contourcv2.findContours 取轮廓代替逐列下沿,但实测它会混入头顶边缘(y 异常偏小),推荐用 column(默认)

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

代码 _pushed_mask

  1. 逐点径向外推:圆心 = 151 点 (cx, cy)。对每个发际线有效点 (x, y),计算从圆心指向它的单位向量 (ux, uy),外推后新位置 (x + ux·push_px, y + uy·push_px)。额头正上方的点往上推,两侧的点斜向外上推。push_px = hairline_push_cm × px_per_cm(默认 1cm)。
  2. 按列归并 + 插值填补锯齿:外推后新 x 坐标可能落在相邻列,逐列取最靠上的 y 作为遮罩顶界;径向归并产生的空列用线性插值填补,保证遮罩顶界连续(否则会被连通域分析切成碎片)。
  3. 与 baseline 组闭合区域:逐列从外推后发际线 pushed_y[x] 填充到 baseline_y[x],得到遮罩。
  4. 后处理& upper 去掉越界部分,_largest_cc 保留最大连通域。

最终遮罩 = [径向外推发际线 → baseline] 之间的闭合区域,顶部含现有头发下沿约 push_cm,底部到 baseline。

关键参数

参数 默认 说明
mask_type eroded(接口默认)/ pushed(当前推荐) pushed 走上述流程;eroded/closed 走旧的 top_fill→closed/eroded 流程
hairline_push_cm 1.0 发际线径向外推距离(厘米),= push_px / px_per_cm。px_per_cm 由虹膜直径标定
hairline_edge column 发际线提取方式:column(逐列下沿,推荐)/ contour(轮廓,易混入头顶)

与旧模式(eroded/closed)的区别

eroded/closed pushed(当前)
遮罩顶界 top_fill(头发向下填充含额头)外缘内缩 头发/皮肤交界线 径向外推
是否用 baseline 截断 用 baseline 组上半区 upper 用 baseline 水平 y 线截断发际线 + 作遮罩底界
遮罩形状 整个额头闭合区域 发际线附近一带(顶部覆盖现有头发 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 外推+遮罩 / 最终遮罩 / 生成 / 贴回 / 融合