把手机变成听诊器!摄像头 30 秒隔空测心率 - 开箱即用

把手机变成听诊器!Android 摄像头 30 秒隔空测心率 ------ 基于 MediaPipe + POS 算法的 rPPG 实战

关键词:rPPG、非接触心率、Android、CameraX、MediaPipe、POS 算法、开源 Demo

源码地址:github.com/liyufengrex...


1. 引言:为什么刷脸就能知道心跳?

传统心率测量需要佩戴手环、电极或血氧探头,而 远程光电容积脉搏波描记法(rPPG) 只需要手机摄像头。

原理一句话:血液对光的吸收量随心跳周期性变化 → 皮肤颜色发生微弱变化 → 用算法把"颜色变化"翻译成"心率"

本文基于开源项目 RPPG-Android ,带你拆解「检测-跟踪-提取-滤波-计算」5 步流程,30 行核心 Kotlin 代码即可跑通 Demo,误差 ≤ 3 bpm(静息状态)。


2. 参考方案与依赖

模块 选型 版本
相机框架 CameraX 1.3.0
人脸关键点 MediaPipe Face Landmarker com.google.mediapipe:tasks-vision:0.10.9
信号处理 POS(Plane-Orthogonal-to-Skin) 2014 IEEE T-IP 论文算法
语言 & IDE Kotlin + Android Studio Hedgehog JDK 17

POS 算法优势:

① 无需训练数据;② 对光照变化、头部平移/旋转鲁棒;③ 计算量小,中端手机 30 ms/帧。


3. 实现步骤(含关键代码片段)

3.1 项目结构速览

arduino 复制代码
app/
├─ RPPGAct.kt          // UI + CameraX 生命周期
├─ FaceAnalyzer.kt     // 人脸检测 & ROI 提取
└─ RppgProcessor.kt    // POS 滤波 + 峰值检测 + 生理指标

3.2 第 1 步:CameraX 实时采集

kotlin 复制代码
val analysis = ImageAnalysis.Builder()
    .setBackpressureStrategy(STRATEGY_KEEP_ONLY_LATEST)
    .build()
analysis.setAnalyzer(executor, FaceAnalyzer(::onFrame))
cameraProvider.bindToLifecycle(lifecycle, cameraSelector, preview, analysis)
  • 分辨率 640×480,YUV_420_888 格式,帧率 30 fps。
  • 每帧耗时 < 50 ms 即可保证不丢帧。

3.3 第 2 步:MediaPipe 人脸关键点检测

kotlin 复制代码
val baseOptions = BaseOptions.builder()
    .setModelAssetPath("face_landmarker.task") // 确保 assets 目录下有此模型文件
    .build()

val options = FaceLandmarkerOptions.builder()
    .setBaseOptions(baseOptions)
    .setRunningMode(RunningMode.IMAGE) // 使用同步模式
    .setNumFaces(1)
    .build()

faceLandmarker = FaceLandmarker.createFromOptions(context, options)
  • 检测策略:每 15 帧全量检测一次,其余帧复用上一帧 ROI,降低 CPU 占用 40%。
  • ROI 选取:额头中心关键点 10、左角 109、右角 338 → 正方形边长 = 0.2 × |109-338|,避开头发/眉毛。

3.4 第 3 步:空间平均 → RGB 信号

kotlin 复制代码
val roi = getForeheadRect(landmarks)
var rSum = 0L; var gSum = 0L; var bSum = 0L
for (y in roi.top until roi.bottom) {
    for (x in roi.left until roi.right) {
        val px = rgbBitmap.getPixel(x, y)
        rSum += red(px); gSum += green(px); bSum += blue(px)
    }
}
val pixelCount = roi.width() * roi.height()
val rgb = floatArrayOf(rSum/pixelCount, gSum/pixelCount, bSum/pixelCount)
  • 640×480 帧中 ROI 约 4 000 像素,空间平均有效抑制随机噪声。
  • 输出 3 条时间序列 R(t), G(t), B(t),采样率 = 30 Hz。

3.5 第 4 步:POS 算法消除镜面反射

kotlin 复制代码
// 1. 归一化
val mean = rgb.clone().apply { forEachIndexed { i, _ -> this[i] /= windowSize } }
val norm = rgb.map { it / mean }.toFloatArray()

// 2. 正交投影
val s1 = norm[1] - norm[2]          // G - B
val s2 = norm[1] + norm[2] - 2*norm[0]  // G + B - 2R
val alpha = std(s1) / (std(s2) + 1e-6f)
val bvp = s1 + alpha * s2
  • 仅 6 行代码,0 浮点矩阵分解,在普通手机上耗时 < 0.5 ms。
  • 有效去除灯光镜面高光、头部抖动带来的共模干扰。

3.6 第 5 步:带通滤波 + 峰值检测

生理指标 频段 实现方式
心率 0.7--4 Hz (42--240 bpm) 滑动平均差分 + 4 阶 Butterworth IIR
呼吸率 0.15--0.5 Hz (9--30 rpm) 同上,低频通道
kotlin 复制代码
val peaks = findPeaks(bvp, minDistance = 30)   // 30 帧 ≈ 1 s
val ibi = peaks.zipWithNext { a, b -> b - a }  // 单位:帧
val hr = 60f * fps / ibi.average()
  • SDNN (心率变异性)= ibi.map{ it * 1000 / fps }.std(),单位 ms。
  • 呼吸率同理,在低频通道做峰值检测即可。

3.7 第 6 步:UI 实时展示

组件 更新频率 数据来源
TextView hrText 1 Hz RppgProcessor.hr
TextView rrText 0.2 Hz RppgProcessor.rr
LineChart 15 fps 原始 BVP 曲线
  • 使用 MPAndroidChart 库,横轴 5 s 滑动窗口,纵轴自动缩放。
  • 心率数字做 3 点滑动平均,防止跳变造成用户焦虑。

4. 实验结果

场景 样本数 平均误差 备注
静息室内 20 人 1.8 bpm 光照 300--500 lux
步行后 10 人 3.4 bpm 头部轻微晃动
室外逆光 10 人 5.1 bpm 镜面反射强烈,误差增大

提示:室外建议开启 前置摄像头 + 手动遮挡头发,可降误差到 3 bpm 以内。


5. 常见问题 FAQ

Q1: 必须 30 fps 吗?

15 fps 也能跑,但频域分辨率减半,心率上限降到 120 bpm。

Q2: 支持多人脸吗?

MediaPipe 已支持,但 ROI 重叠会导致信号串扰,建议单人场景。

Q3: 能否测血氧?

理论上可用双波长,但手机无可控红外光源,误差 > 5%,不建议医疗用途


6. 结论 & 展望

  • 整套方案 零硬件成本,在千元机上 30 秒给出心率+RR+SDNN,适合居家健康筛查。
  • 下一步:
    ① 引入 BCG (头部微动)多模态融合,提升运动鲁棒性;
    ② 使用 TensorFlow Lite 端到端回归,直接输出 HR,跳过传统信号处理;
    ③ 通过 Health Service API 将数据同步到 Google Fit。

7. 源码 & 引用 & 效果演示

GitHub - RPPG-Android 欢迎 Star & PR!

如果本文帮到了你,记得点个赞 ❤️ 再走~

相关推荐
千里马学框架18 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台18 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone18 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc19 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo20 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770021 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone1 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen1 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone1 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui