引言
MediaPipe 的 hair_segmentation 模块提供了一种高质量的实时头发分割方案------通过语义分割模型从画面中精确分离头发区域,并支持自定义发色渲染。与 face_mesh 的人脸关键点检测不同,hair_segmentation 关注的是像素级的头发/背景二分类,配合时序融合机制实现帧间平滑过渡。然而原版同样深度绑定 Bazel + GPU 工具链,Windows 桌面端难以独立部署。
Hair Segmentation CPU 项目将完整的 hair_segmentation 管线从 MediaPipe 0.10.10 中完全解耦,实现了:
- 🔧 CMake 构建体系:告别 Bazel,Visual Studio 2022 及以上版本一键编译,首次构建不超过 5 分钟
- 🚀 零 GPU 依赖:纯 CPU 推理,XNNPACK SIMD 加速,无需 CUDA / OpenGL
- 🎨 实时发色渲染:512×512 语义分割 + 自定义 RGB 发色,实时 20~30 FPS
- 🔄 时序一致性:前帧掩码回环融合(0.9 平滑系数),大幅减少帧间闪烁
- 🎛️ 运行时热配置:发色 RGB 实时可调,无需重新编译
- 📦 完整可执行文件仅 6.8 MB:包含分割模型,零外部运行时依赖

上图:Hair Segmentation 在 i7-12700H 纯 CPU 环境下的摄像头实时运行效果。左侧为原始画面,右侧为头发分割 + 蓝色发色渲染后的效果。
开源地址:https://github.com/lazyboooooy/hair_seg-cmake
一、项目定位
1.1 与 face_mesh / face_detection 的关系
三个项目同源,均从 MediaPipe 0.10.10 解耦,共享同一份 3rdparty 依赖和 CMake 构建体系,但定位各异:
| 维度 | face_detection | face_mesh | hair_segmentation(本项目) |
|---|---|---|---|
| 功能 | 人脸检测 + 6 个关键点 | 人脸 478 个 3D 关键点 | 头发分割 + 发色渲染 |
| 模型数量 | 1 个(225 KB) | 3 个(3.8 MB) | 1 个(782 KB) |
| 计算子图 | 5 个 | 12 个 | 0(单图,无子图) |
| 自定义 TFLite 算子 | 4 个 | 6 个 | 0 |
| 管线复杂度 | 低 | 高 | 最低 |
| 输入尺寸 | 128×128 | 128×128 + 192×192 | 512×512 RGBA |
| 独特机制 | 置信度过滤 | 跳帧优化 + ROI 旋转 | 时序融合(0.9 平滑) |
| 适用场景 | 人脸检测计数 | 面捕、虚拟主播 | 虚拟染发、美颜、AR 特效 |
关键区别 :hair_segmentation 是三个项目中最简洁 的一个------无子图、无自定义 TFLite 算子,整个管线由 10 个内置计算器在一个平铺的 .pbtxt 文件中串联完成。它的核心价值在于时序一致性------通过 PreviousLoopbackCalculator 将前帧分割掩码回环融合,实现帧间平滑过渡,这是单帧分割方案无法做到的。
1.2 为什么选择独立编译
| 问题 | 原版 MediaPipe | 本项目 |
|---|---|---|
| 构建系统 | Bazel(需 MSYS2 + Python + Bazelisk) | CMake 3.16+(VS 2022 及以上原生) |
| 首次配置 | 30~60 分钟 | 10 秒 |
| 首次编译 | 1~2 小时 | 3~5 分钟 |
| GPU 依赖 | 必须 OpenGL / ES 3.1 | 不需要 |
| 项目体积 | ~5 GB | ~250 MB |
| IDE 支持 | 无 | Visual Studio 原生 |
二、快速开始
2.1 环境要求
| 组件 | 要求 | 说明 |
|---|---|---|
| 操作系统 | Windows 10/11 x64 | --- |
| 编译器 | Visual Studio 2022 及以上 | Community 免费版,勾选"使用 C++ 的桌面开发" |
| CMake | ≥ 3.16 | VS 2022 及以上自带 |
⚠️ 不需要:Python、CUDA、OpenGL、Docker、MSYS2。所有依赖已预编译打包。
2.2 三步运行
bash
# 第一步:克隆
git clone https://github.com/lazyboooooy/hair_seg-cmake.git
cd hair_seg-cmake
# 第二步:编译
cmake -B build -S .
cmake --build build --config Release
# 第三步:运行
cd build/Release
./hair_segmentation_cpu.exe
摄像头自动打开,检测到头发后按默认蓝色渲染。按 ESC 退出。
为什么默认启动摄像头? 配置文件 hair_segmentation_config.ini 中 [video] input = 默认为空,程序检测到没有指定视频文件后自动打开摄像头 0。
2.3 配置文件详解
编辑 exe 同目录下的 hair_segmentation_config.ini,改动即时生效:
ini
[paths]
resource_root_dir = resource
graph_config = graphs/hair_segmentation_desktop_live.pbtxt
[video]
input = # 视频文件路径(留空用摄像头)
output = # 输出 MP4(预留)
[color]
# 发色 RGB(0-255),默认蓝色
r = 0
g = 0
b = 255
[execution]
num_threads = 2 # TFLite 推理线程(0 = 自动)
xnnpack_enable = true # XNNPACK SIMD 加速
配置优先级:命令行参数 > INI 文件 > 默认值。
2.4 发色预设
| 颜色 | R | G | B | 视觉效果 |
|---|---|---|---|---|
| 蓝色(默认) | 0 | 0 | 255 | 纯净蓝发 |
| 红色 | 255 | 0 | 0 | 鲜艳红发 |
| 粉色 | 255 | 105 | 180 | 柔和粉发 |
| 金色 | 255 | 215 | 0 | 亮丽金发 |
| 紫色 | 128 | 0 | 128 | 深邃紫发 |
| 绿色 | 0 | 255 | 0 | 个性绿发 |
2.5 命令行参数
| 参数 | 说明 |
|---|---|
--config_file=D:/my.ini |
自定义配置文件路径 |
--input_video_path=D:/test.mp4 |
视频文件路径 |
--resource_root_dir=D:/resource |
资源目录 |
三、管线架构
3.1 数据流
摄像头帧 (OpenCV)
→ FlowLimiterCalculator(限流:同时只飞 1 帧)
→ ImageTransformationCalculator(缩放至 512×512,STRETCH 模式)
→ PreviousLoopbackCalculator(回环上一帧分割掩码)
→ ColorConvertCalculator(RGBA 掩码 → 单通道灰阶掩码)
→ SetAlphaCalculator(掩码嵌入当前帧 Alpha 通道 → RGBA 4 通道)
→ TfLiteConverterCalculator(图像 → TFLite 张量,max_num_channels=4)
→ TfLiteInferenceCalculator(512×512 推理,XNNPACK 加速)
→ TfLiteTensorsToSegmentationCalculator(张量 → 掩码,时序融合 0.9)
→ RecolorCalculator(按 INI 配置的 RGB 渲染发色到原图)
→ 输出帧 (OpenCV imshow)
3.2 核心机制
FlowLimiterCalculator(限流):确保管线内同时只有 1 帧在处理,杜绝帧积压和内存膨胀。
PreviousLoopbackCalculator(掩码回环) :将上一帧的分割掩码缓存,在下一次推理时通过 SetAlphaCalculator 嵌入当前帧的 Alpha 通道。这使得模型输入为 RGBA 4 通道(RGB + 前帧掩码),模型可以利用前一帧的分割结果来引导当前帧的预测。
时序融合(Temporal Fusion) :TfLiteTensorsToSegmentationCalculator 解码当前帧的 2 通道分割张量后,将其与上一帧掩码按 0.9 : 0.1 的比例加权融合:
当前掩码 = 0.1 × 当前模型输出 + 0.9 × 上一帧掩码
0.9 的高融合比例使分割边缘在帧间平滑过渡,大幅减少闪烁。这是 MediaPipe hair_segmentation 管线相比单帧语义分割方案的独有优势。
3.3 10 个管线组件一览
| 组件 | 类型 | 功能 |
|---|---|---|
| FlowLimiterCalculator | 核心 | 背压控制,管线内最多 1 帧 |
| ImageTransformationCalculator | 图像 | 缩放至 512×512(STRETCH 模式) |
| PreviousLoopbackCalculator | 核心 | 缓存前帧掩码,替换时间戳后回环 |
| ColorConvertCalculator | 图像 | RGBA → 单通道灰阶掩码 |
| SetAlphaCalculator | 图像 | 掩码嵌入 Alpha 通道,生成 RGBA 输入 |
| TfLiteConverterCalculator | TFLite | 图像 → TFLite 张量(max_num_channels=4) |
| TfLiteCustomOpResolverCalculator | TFLite | 注册自定义算子解析器 |
| TfLiteInferenceCalculator | TFLite | CPU 推理(XNNPACK 加速) |
| TfLiteTensorsToSegmentationCalculator | 工具 | 2 通道张量 → 分割掩码 + 时序融合 |
| RecolorCalculator | 工具 | 按 RGB 颜色渲染发色到原图 |
四、分割模型
4.1 模型规格
| 参数 | 值 |
|---|---|
| 架构 | 语义分割(Encoder-Decoder) |
| 输入尺寸 | 512×512 RGBA(4 通道) |
| 输入通道含义 | RGB(当前帧)+ Alpha(前帧掩码) |
| 输出 | 2 通道(头发 / 背景) |
| 时序融合比例 | 0.9(combine_with_previous_ratio) |
| 量化 | FP32(全精度) |
| 模型大小 | 782 KB |
4.2 为什么是 512×512
头发分割属于像素级语义分割任务,不同于人脸检测只需要回答"有没有、在哪"。分割模型需要为每个像素分配类别标签(头发/背景),输入分辨率直接决定分割边界的精细度。512×512 的输入尺寸使模型能够捕捉发丝的细节纹理,输出高质量的头发轮廓掩码。
4.3 为什么是 4 通道 RGBA
标准 RGB 输入只能提供当前帧的色彩信息。通过 SetAlphaCalculator 将前帧掩码嵌入 Alpha 通道,模型同时获得:
- RGB 通道:当前帧的视觉信息(颜色、纹理、边缘)
- Alpha 通道:前一帧的分割结果("上一帧哪里是头发")
这种设计让模型具备时序感知能力------即使当前帧的某些头发区域因光照或运动而难以分辨,前帧的掩码信息也能引导模型做出正确预测。这是 MediaPipe hair_segmentation 区别于普通语义分割模型的核心设计。
五、与 face_mesh / face_detection 的横向对比
5.1 规模对比
| 指标 | face_detection | face_mesh | hair_segmentation |
|---|---|---|---|
| 源码文件数 | ~130 个 .cc | ~150 个 .cc | ~100 个 .cc |
| Proto 文件数 | ~25 个 | ~30 个 | ~18 个 |
| 计算子图 | 5 个 | 12 个 | 0 |
| 自定义算子 | 4 个 | 6 个 | 0 |
| 模型数 | 1 | 3 | 1 |
| INI 配置项 | ~10 | ~12 | ~7 |
| 编译时间(增量) | ~30 秒 | ~40 秒 | ~20 秒 |
hair_segmentation 是三个项目中最精简的------零子图、零自定义算子,使其成为理解 MediaPipe CalculatorGraph 工作机制的最佳入门项目。
5.2 适用场景分工
| 需求 | 推荐项目 | 理由 |
|---|---|---|
| 人脸检测 / 计数 / 位置追踪 | face_detection | 225 KB 模型,极简 |
| 面部捕捉 / 虚拟主播 / 表情分析 | face_mesh | 478 关键点,虹膜定位 |
| 虚拟染发 / 美颜发色 / AR 特效 | hair_segmentation | 时序融合平滑,发色实时可调 |
| 移动端直接集成 | Google ML Kit | 原生 SDK |
六、性能实测
6.1 测试环境
| 项目 | 配置 |
|---|---|
| CPU | Intel Core i7-12700H(14 核 20 线程,P-core 4.7 GHz) |
| 内存 | 16 GB DDR5-4800 |
| OS | Windows 11 Pro |
| 编译器 | MSVC 2022,Release /O2 /arch:AVX2 |
| TFLite 后端 | XNNPACK(SSE4.1 + AVX2 自动调度) |
| 推理线程 | 2 线程 |
6.2 逐帧耗时
| 阶段 | 耗时(ms) | 占比 | 说明 |
|---|---|---|---|
| 帧捕获 + BGR→RGB | 1~3 | ~8% | 摄像头 grab/retrieve + 色彩转换 |
| ImageFrame 包装 | 1~2 | ~5% | cv::Mat → ImageFrame 内存拷贝 |
| 图像缩放(→ 512×512) | 1~2 | ~5% | STRETCH 模式,无额外插值开销 |
| 掩码嵌入(RGBA 合成) | 0.5~1 | ~3% | SetAlphaCalculator Alpha 通道写入 |
| TFLite 推理(512×512) | 10~20 | ~55% | 计算最密集的阶段 |
| 分割解码 + 时序融合 | 2~5 | ~12% | 2 通道解码 + 0.9 加权平滑 |
| 发色渲染 | 2~4 | ~12% | RecolorCalculator 逐像素着色 |
| 单帧总计 | 16~34 | 100% |
6.3 整体指标
| 指标 | 数值 |
|---|---|
| 实时帧率 | 20~30 FPS |
| 内存占用 | ~160 MB |
| CPU 占用 | 25~35%(i7-12700H) |
| 可执行文件 | 6.8 MB |
| 模型大小 | 782 KB |
推理耗时与画面中头发占比无明显关联------512×512 全图推理的特性使得每帧计算量几乎恒定,帧率波动小,体验稳定。
6.4 与同类方案对比
| 方案 | 分割精度 | CPU FPS | 时序平滑 | 模型大小 | 自定义发色 | 构建难度 |
|---|---|---|---|---|---|---|
| 本项目 | 高 | 20~30 | ✅ 0.9 融合 | 782 KB | ✅ INI 热配置 | 极低 |
| MediaPipe 原版 | 高 | 20~30 | ✅ | 782 KB | ❌ 需改 pbtxt | 极高 |
| OpenCV GrabCut | 中 | 5~10 | ❌ | 无模型 | ❌ | 低 |
| 传统颜色阈值 | 低 | 60+ | ❌ | 无模型 | ❌ | 低 |
七、项目目录结构
hair_segmentation/
├── CMakeLists.txt # CMake 构建配置(C++17, MSVC)
├── README.md / README_EN.md # 中英文文档
├── LICENSE # Apache 2.0
├── demo/
│ └── hair_seg.gif # 运行效果图
├── src/
│ ├── main.cc # 入口:图加载、摄像头、发色替换
│ ├── glog_config.h # GLog 预包含头文件
│ └── mediapipe/
│ ├── abseil_log/ # Abseil 日志桩
│ ├── calculators/ # 计算器(core/image/tensor/tflite/util)
│ ├── framework/ # 图执行引擎、包管理
│ ├── gpu/ # GPU 缓冲管理(Image 类型,无 OpenGL)
│ ├── tflite_kernels/ # TFLite 内部符号补齐
│ └── util/tflite/ # OpResolver、算子注册
├── resource/
│ ├── hair_segmentation_config.ini # 运行时配置文件
│ ├── graphs/
│ │ └── hair_segmentation_desktop_live.pbtxt # 计算图(单文件,无子图)
│ └── mediapipe/models/
│ └── hair_segmentation.tflite # 分割模型(782 KB)
├── 3rdparty/ # 预编译第三方依赖(~240 MB)
│ ├── tflite/ opencv/ protobuf/ abseil/
│ ├── XNNPACK/ eigen/ flatbuffers/
│ └── ...
└── cmake/
├── protoc.exe # Protocol Buffer 编译器
└── generate_protos.bat # Proto 编译脚本
八、常见问题排查
Q1: 双击 exe 窗口一闪就退出
在命令行运行查看错误信息:
bash
cd build/Release
./hair_segmentation_cpu.exe
| 错误信息 | 原因 | 解决方案 |
|---|---|---|
Cannot open webcam (camera 0) |
摄像头被占用 | 关闭微信/腾讯会议/浏览器等占用摄像头的程序 |
Resource not found: graphs/... |
resource/ 目录缺失 | 检查 build/Release/resource/,重新执行 cmake --build |
Could not load config |
INI 文件缺失 | 检查 build/Release/hair_segmentation_config.ini |
protoc not found |
杀毒软件误删 cmake/protoc.exe |
从 Windows Defender 恢复,或下载 protoc-3.21.12 |
Q2: 画面卡顿,FPS 低于预期
ini
[execution]
num_threads = 4 # 增加推理线程
xnnpack_enable = true # 确认 XNNPACK 已启用
也可以在任务管理器中将 hair_segmentation_cpu.exe 的 CPU 优先级设为"高"。
Q3: 分割边缘闪烁
时序融合已内置 0.9 平滑系数,正常情况下闪烁很轻微。如果仍有明显闪烁,可能是以下原因:
- 摄像头光线不足 → 提高环境亮度
- 人物移动过快 → 正常现象,时序融合需要 2~3 帧收敛
- 头发与背景颜色相近 → 模型区分能力下降
Q4: 编译报错 "Cannot open include file: 'Eigen/Core'"
Eigen3 目录联结创建失败:
bash
# 手动创建联结
mklink /J "build\generated\third_party\eigen3\Eigen" "3rdparty\eigen\include\Eigen"
# 如果 mklink 不可用,手动拷贝
xcopy /E /I "3rdparty\eigen\include\Eigen" "build\generated\third_party\eigen3\Eigen"
Q5: OpenCV DLL 缺失
bash
copy 3rdparty\opencv\bin\opencv_world3410.dll build\Release\
九、3rdparty 依赖树
所有第三方依赖预编译打包,实现一次性下载、离线编译:
3rdparty/
├── tflite/ (94 MB) TensorFlow Lite 推理引擎
├── opencv/ (62 MB) OpenCV 3.4.10(仅 world 模块)
├── protobuf/ (36 MB) Protocol Buffers 3.21.12
├── abseil/ (18 MB) Abseil C++ LTS 20230125
├── XNNPACK/ (3.7 MB) SIMD 推理加速
├── eigen/ (仅头文件) Eigen 3.4.0(模板库)
├── flatbuffers/ (1.2 MB) 序列化
├── cpuinfo/ (0.8 MB) CPU 特性检测(SSE/AVX)
├── pthreadpool/ (0.3 MB) 线程池
├── farmhash/ (0.1 MB) 哈希函数
├── ruy/ (0.5 MB) 矩阵乘法微内核
├── fft2d/ (0.2 MB) 2D FFT
├── FP16/ (0.1 MB) 半精度浮点
├── FXdiv/ (0.05 MB) 快速整数除法
├── gemmlowp/ (0.3 MB) 低精度 GEMM(预留)
└── abseil_extra_obj/(1.5 MB) 日志符号桩
hair_segmentation 的依赖精简策略与 face_mesh 一致:头文件 3000+ → 652、Proto 50+ → 18、计算器 100+ → 10。由于无需子图和自定义算子,精简效果最显著。
十、局限性
| 局限 | 影响 | 改进方向 |
|---|---|---|
| 固定 512×512 输入 | 非正方形画面有轻微拉伸 | 改为 LETTERBOX 缩放模式 |
| 仅单人场景 | 多人会被分割为同一头发区域 | 先做实例分割再分别渲染 |
| 深色/短发的边缘精度 | 与背景对比度低时边界模糊 | 模型微调 + 边缘细化后处理 |
| 仅 Windows | 不支持 Linux/macOS | 替换平台 API |
| 无视频输出 | MP4 写入尚未实现 | 集成 OpenCV VideoWriter |
十一、总结
三项目选型建议:
| 需求 | 推荐 |
|---|---|
| 人脸检测 / 计数 | face_detection-cmake |
| 面部捕捉 / 表情分析 / AR 特效 | face_mesh-cmake |
| 虚拟染发 / 发色渲染 / 美颜 | hair_seg-cmake(本项目) |
hair_segmentation 是三个 MediaPipe 解耦项目中最简洁的一个:无子图、无自定义算子、仅 1 个模型、7 个 INI 配置项。它的价值在于:
- 时序融合是区别于所有单帧分割方案的核心优势------0.9 平滑系数让发色渲染结果在帧间丝滑过渡
- 发色热配置让换色像改 RGB 数值一样简单,无需重新训练或编译
- 作为学习项目,它的扁平管线架构比 face_mesh 的多层子图更容易理解 MediaPipe CalculatorGraph 的工作方式
开源地址:https://github.com/lazyboooooy/hair_seg-cmake
参考文献
- Lugaresi C, Tang J, Nash H, et al. MediaPipe: A Framework for Building Perception Pipelines . arXiv:1906.08172, 2019.
- Google. MediaPipe Hair Segmentation . MediaPipe Documentation
- Google. XNNPACK: High-efficiency Floating-point Neural Network Inference Operators . GitHub
- Sandler M, Howard A, Zhu M, et al. MobileNetV2: Inverted Residuals and Linear Bottlenecks . CVPR 2018. arXiv:1801.04381
- Google. TensorFlow Lite --- ML for Mobile and Edge Devices . TensorFlow Lite