PixelFree SDK 肤色均匀:LUT 与 GPU 管线技术解析
摘要
肤色均匀是实时美颜与人像增强中的高频能力。PixelFree SDK 在 GPU 上通常将其拆成「哪里是皮肤」「怎样平滑才不掉细节」「怎样统一明暗与色偏」三层问题。本文结合 SDK 管线中典型的 人脸掩膜 → 低分辨率掩膜平滑 → 肤色区域边缘感知模糊 → 可分步二次模糊 → 基于 LUT 的暗沉提亮 链路,说明各步骤的动机与实现要点,并结合各渲染步骤中的顶点 / 片元着色器逻辑(掩膜 → 分步平滑 → LUT)。全文侧重原理与工程衔接,便于将效果参数与 FBO 顺序对齐到具体项目。
1. 为什么「肤色均匀」不能只靠一张 LUT
单向量化的 3D LUT(查表)擅长做整体或分段的色调映射:例如统一白平衡观感、批量把暗肤提亮到某一曲线。但「均匀」还包含空间上的低频一致:同一脸颊上相邻区域的明暗差、泛红与暗沉块,需要在图像空间里被压平,同时不能把毛孔、发丝边缘糊进皮肤里。
因此常见工程策略是:
- 用 Mask 把算子限制在脸/肤区域;
- 用边缘感知或双边类权重在安全范围内做低频融合;
- 最后用 LUT 在掩膜约束下对「仍偏暗的像素」做小强度色调修正,补足全局 LUT 难以精细处理的空间残差。
PixelFree SDK 的肤色均匀路径即近似这一组合;LUT 是管线尾部的局部混合查表,而不是唯一主角。
2. 总体数据流
下列流程与目录注释及着色器结构一致,便于对照阅读源码片段。
Landmark / 分割 → 肤色 Mask(及可能的缩小副本)
↓
小尺寸 Mask 上先做平滑(弱化硬边)
↓
原分辨率:仅 mask.a(或等价通道)超过阈值的像素参与「颜色距离加权」邻域合成
↓
沿两个方向各做一组类似的邻域加权(可分步的高维平滑近似)
↓
原图 FBO:根据 blur 与原图差分锁定「暗沉」,在 mask 控制下与 LUT8x8 输出 mix
分辨率上,注释中多次出现 324×576 一类的中间尺寸,表示管线常在降采样 FBO 上完成开销较大的模糊,再上采样或与全分辨率纹理复合,需在具体集成里与 texel offset(texBlurWidthOffset / texBlurHeightOffset)对齐。
3. 掩膜:从 Landmark 到可用 Alpha
管线前段注释说明:先有 Landmark 映射的 Mask,再在小图上做均匀/平滑。工程意义是:
- Landmark / 人脸网格提供稳定的脸部区域先验,避免仅靠肤色阈值在背景误触;
- 小图平滑 Mask 去掉二值或粗分割带来的锯齿,使后续
mix不会在脸轮廓周圈产生一圈亮暗边。
实现上通常将 Mask 作为独立纹理绑定(如片元里的 faceMaskTexture),用 Alpha 或单通道表示皮肤置信度,阈值(例如 skinArea > 0.1)以上的像素才进入平滑分支。
4. 肤色区域内的边缘感知式平滑
第一遍肤色平滑着色器中,片元阶段在皮肤掩膜内对当前像素周围多个固定偏移点采样。核心结构是:
- 先给当前色一个基础权重(如
0.18),再累加各邻域点; - 对每个邻居,根据
distance(curColor.rgb, neighborColor.rgb)乘以因子tolerance_factor,再裁剪到[0,1],进而得到sample_weight ∝ (1 - color_dist)。
这样,颜色相近的邻域贡献大,跨越大梯度(睫毛、唇线、五官边界)时权重自然降低,起到类似双边滤波「保边平滑」的作用,比普通高斯更适于肤色块内去「花斑」。
片元末尾对 sum_weight 分档:过小则保持原色,中间区间做 mix,足够大则输出归一化加权和。这是在「没有足够的有效邻域支持」时回退到原图,避免异常权重导致色斑。
第二遍正交平滑在注释中说明为基于上一次高方向模糊后再做一次(另一组 offset,例如竖向 texBlurHeightOffset 非零、横向为零),属于典型的 可分离模糊的两-pass 或各向异性扩展,用于扩大平滑半径、进一步压低中频色差。
下列为与第一遍平滑逻辑一致的简化片段说明(完整实现即多点 textureShift 累加与 sum_weight 分档):
// 皮肤掩膜内:邻域颜色距越大,权重越小 → 保边 + 均匀
float skinArea = texture2D(faceMaskTexture, textureCoordinate).a;
if (skinArea > 0.1) {
// sum_weight、sum:累加自身与多档 textureShift_i 采样点
// neighbor 权重 = 基准系数 * (1.0 - min(dist * tolerance_factor, 1.0));
// 再按 sum_weight 分档输出原色、混合或完全平滑色
}
5. LUT:512 纹理上的 LUT8x8 与暗沉判定
LUT 提亮着色器展示 8×8×8 颜色立方体的经典 2D 展开:蓝轴分层为 64 档,红绿映射到子方块内 UV,再在相邻蓝层之间对 fract(blueColor) 插值。函数形式为 LUT8x8(inColor, lutImageTexture),与 512×512 LUT 图(0.5/512.0 边界修正、0.125 每格)一致。
应用条件分为两层:
- 空间:
maskColor.b > 0.005,即仅在 Mask 指定区域(此处用蓝通道存权重,与前面skinArea/a 通道可能来自不同阶段或 Pack,集成时以实际纹理格式为准)。 - 亮度/暗沉:
blurColor相对srcColor在正差分上的向量长度,经dark_threshold_Factor缩放后落在(0.5, 5.0)之间,才认为是「可控暗沉」,避免高光或过暗死黑被错误提亮。
混合强度形如:
srcColor.rgb = mix(srcColor.rgb, brightColor.rgb, maskColor.b * 0.4);
即 LUT 输出最多约占四成(再乘 Mask),保证效果可逆、不过度「塑料皮」。28.86751 一类的常数与 1/sqrt(3)/阈值 类归一化在其它美颜着色器中亦常见,用于把 RGB 差分映射到合理的判定区间。
6. SDK 集成与调试建议
- FBO 顺序:先完成 原图 → 掩膜平滑 → 肤色平滑纹理,再进入 LUT pass;LUT pass 往往需要 原图、模糊图、Mask、LUT 四路采样(该 pass 中常见
inputImageTexture、blurImageTexture、faceMaskTexture、lutImageTexture等uniform)。 - 降采样:中间 324×576 与全分辨率之间要明确谁参与差分:暗沉判断通常希望 blur 是低频参考,原图保留细节,故
blurImageTexture应对应前几步平滑输出。 - 阈值与强度:
tolerance_factor、dark_threshold_Factor、以及mask * 0.4共同决定「均匀 vs 平 vs 假」。上线前建议在多肤色、多光照数据集上扫参。 - 设备差异:
mediump/highp声明在部分 GPU 上会影响梯度稳定性;肤色线性/非线性空间是否在 LUT 前做 gamma 处理,需与美术标定一致。
7. 小结
PixelFree SDK 的肤色均匀并非单点 LUT,而是 Mask 限定 + 边缘感知平滑 + 可分步扩展模糊 + 条件化 LUT 提亮 的链路:前两段解决「空间上匀」,LUT 段解决「色调与暗沉走向一致」。理解各 pass 的输入输出与权重含义,有利于在自定义业务中替换 LUT、改写 Mask 来源或把中间结果暴露给 UI 做分级美颜。
参考资料:管线环节索引
| 内容 | 对应着色器 / 环节 |
|---|---|
| 掩膜 / 低分辨率平滑 | 掩膜生成、小图掩膜平滑 |
| 肤色区加权平滑 | 第一遍边缘感知平滑 |
| 正交方向第二 pass | 第二遍正交平滑 |
| LUT8x8 与暗沉提亮 | LUT 提亮 pass |
标签建议:OpenGL GLSL 图像处理 美颜算法 LUT