把手机变成听诊器!摄像头 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!

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

相关推荐
为码消得人憔悴3 小时前
Android perfetto - 记录分析memory
android·性能优化
尤老师FPGA4 小时前
使用ZYNQ芯片和LVGL框架实现用户高刷新UI设计系列教程(第四十二讲)
android·java·ui
成都大菠萝4 小时前
2-2-29 快速掌握Kotlin-过滤函数filter
android
成都大菠萝4 小时前
2-2-18 快速掌握Kotlin-扩展属性
android
成都大菠萝4 小时前
2-2-21 快速掌握Kotlin-定义扩展文件
android
成都大菠萝4 小时前
2-2-19 快速掌握Kotlin-可空类型扩展函数
android
成都大菠萝4 小时前
2-2-23 快速掌握Kotlin-apply函数详解
android
2501_916007475 小时前
iOS 证书如何创建,从能生成到能长期使用
android·macos·ios·小程序·uni-app·cocoa·iphone
Just_Paranoid5 小时前
【AOSP】Android Dump 信息快速定位方法
android·adb·framework·service·aosp·dumpsys