这是一个基于 GPUImage2 + OpenGL ES 的实时磨皮项目,核心算法是快速导向滤波(Fast Guided Filter),并结合颜色空间皮肤检测做区域约束。
项目目标:
- 在 iOS 端以 Camera 预览实时跑磨皮
- C++ 核心逻辑可移植(不依赖旧工程里的 Filter/Context 基类)
- 通过描述化 pipeline(descriptor)组织多 pass 算法,便于 Android/其他渲染后端复用
- ios/FastGuidedSmooth/FastGuidedSmooth/ViewController.swift
- ios/FastGuidedSmooth/FastGuidedSmooth/FastGuidedSmoothOperation.swift
- ios/FastGuidedSmooth/FastGuidedSmooth/FastGuidedSmoothBridge.h
- ios/FastGuidedSmooth/FastGuidedSmooth/FastGuidedSmoothBridge.mm
职责:
- ViewController 负责 Camera -> Filter -> RenderView 的链路搭建和 UI 参数调节
- FastGuidedSmoothOperation 是 GPUImage2 的自定义节点,实现 Framebuffer 输入输出
- Bridge(ObjC++)把 GPUImage 纹理转到 C++ descriptor 执行器里,完成多 pass 渲染
- src/smooth_fast_guided/FastGuidedPipeline.h
- src/smooth_fast_guided/FastGuidedSmoothFilter.cpp
- src/smooth_fast_guided/FastGuidedStatsFilter.cpp
- src/smooth_fast_guided/FastGuidedABFilter.cpp
- src/smooth_fast_guided/FastGuidedBlurABFilter.cpp
- src/smooth_fast_guided/FastGuidedScaleFilter.cpp
- src/smooth_fast_guided/FastGuidedUnitFilter.cpp
- src/skin/ColorSpaceSkinDetectFilter.cpp
职责:
- 用 ShaderPassDescriptor / PipelineDescriptor 描述每个 pass
- 算法层只描述“图结构 + shader + uniform + 输入输出关系”
- 不绑定 iOS 平台对象,便于跨端重用
在 ViewController 中,链路是:
- Camera(sessionPreset: .hd1280x720, location: .frontFacing)
- camera --> FastGuidedSmoothOperation --> RenderView
滑杆把 [0, 1] 的值实时映射到磨皮强度 strength。
FastGuidedSmoothOperation 遵循 ImageProcessingOperation:
- 接收上游 camera 的 inputFramebuffer
- 调用 bridge.processSourceTexture(texture, width, height)
- 拿到输出纹理后,用 overriddenTexture 构建新的 outputFramebuffer
- 调用 updateTargetsWithFramebuffer(outputFramebuffer) 发送到 RenderView
这一步是 GPUImage2 图中的“自定义滤镜节点”。
FastGuidedSmoothBridge.mm 做了三件事:
- 把算法对象 FastGuidedSmoothFilter 的 descriptor 取出来
- 用 DescriptorExecutor 执行 descriptor 中定义的 pass 图
- 返回最终 outputPassKey 对应的纹理给 Operation
DescriptorExecutor 负责:
- shader 编译/链接缓存(避免重复编译)
- 每个 pass 的 render target(texture + framebuffer)缓存
- external input(source)与 pass output 的依赖解析
- uniform 和 sampler 绑定
当前主 pipeline 在 FastGuidedSmoothFilter::descriptor() 中组装,包含以下 pass:
- stats_downsample
- stats_horizontal
- stats_vertical
- ab
- blur_ab_horizontal
- blur_ab_vertical
- upsample_ab
- skin_mask
- unit(最终输出)
文件:src/smooth_fast_guided/FastGuidedStatsFilter.cpp
思路:
- 先降采样(stats_downsample),降低计算量
- 横向统计局部均值与平方均值(stats_horizontal)
- 纵向再聚合得到 mean_I 与 var_I(stats_vertical)
核心意义:为导向滤波线性模型提供局部统计量。
文件:src/smooth_fast_guided/FastGuidedABFilter.cpp
基于统计量计算线性模型系数:
在当前实现中,使用灰度引导近似并写入 RG 两通道(a,b)。
文件:src/smooth_fast_guided/FastGuidedBlurABFilter.cpp
- 对 a,b 做 separable blur:先横向再纵向
- 进一步抑制系数噪声,得到更稳定的引导重建结果
文件:src/smooth_fast_guided/FastGuidedScaleFilter.cpp
- 将低分辨率 a,b 上采样回原图分辨率
- 保证最终 unit pass 与原始输入在同尺寸空间融合
文件:src/skin/ColorSpaceSkinDetectFilter.cpp
使用颜色空间规则生成 mask:
- HSV 区间约束(H/S/V 范围)
- RGB 阈值约束(肤色常见经验阈值)
- 输出灰度 mask(皮肤=1,非皮肤=0)
文件:src/smooth_fast_guided/FastGuidedUnitFilter.cpp
输入:
- inputImageTexture(原图)
- meanABTexture(平滑后的 a,b)
- skinMaskTexture(皮肤区域 mask)
流程:
- 计算皮肤加权强度:
- 使用导向模型重建目标亮度 q
- 用 maskedStrength 在原亮度 I 与 q 之间插值
- 将亮度变化量回写到 RGB,限制变化范围,避免过磨皮
效果:
- 皮肤区域平滑明显
- 非皮肤区域尽量保持原细节
核心结构在 src/smooth_fast_guided/FastGuidedPipeline.h:
- ShaderPassDescriptor:单个 pass 的 shader、输入、uniform、输出缩放
- PassInputDescriptor:描述输入来源(external 或上游 pass)
- PipelineDescriptor:完整 pass 图 + 最终输出键
优点:
- 算法层与渲染执行层解耦
- iOS 只需提供一个执行器(当前是 OpenGL ES 执行器)
- Android/Metal/Vulkan 侧可复用同一套 pass 描述,替换执行器即可
Bridge 执行器关键点:
- 按 pass.outputScale 推导每个 pass 输出尺寸
- 每个 pass 按 key 缓存 program 和 render target
- 逐 pass 绑定输入纹理与 sampler
- 执行 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
- 以 descriptor.outputPassKey 作为最终输出纹理
同时保留了较完整的调试日志(FGS_TRACE_V3),便于排查:
- 链路是否进入 bridge
- descriptor 是否正确生成
- 各 pass 的输入输出尺寸
- 关键 GL 调用是否报错
默认参数(FastGuidedSmoothFilter.h):
- downSampleFactor = 0.25
- epsilon = 0.02
- radius = 4.0
- strength 由 UI slider 控制
调优建议:
- 想更柔和:增大 strength、适当增大 radius
- 想保细节:减小 strength 或减小 epsilon
- 想提性能:减小 downSampleFactor(更低分辨率计算)
- GPUImage2 与自定义 OpenGL pass 混用时,要避免污染全局 GL 状态
- 自定义 pass 输出纹理要与 GPUImage Framebuffer 生命周期对齐
- RenderView/主线程相关调用需保证线程正确
- 打开 ios/FastGuidedSmooth/FastGuidedSmooth.xcodeproj
- 选择 FastGuidedSmooth scheme
- 运行到真机(Info.plist 已配置相机权限)
- 通过底部 slider 调节磨皮强度
TODO:
- 将 DescriptorExecutor 抽象为可替换后端(OpenGL/Metal)
- Android Demo

