一、心率检测原理:光电容积描记法

心率检测,说白了就是测量心脏每分钟跳了多少下。在Android设备上,我们用的最多的就是PPG信号——光电容积描记法。

我记得第一次接触这个技术时,觉得挺神奇的。一个LED灯加一个光敏传感器,就能测出心跳?后来深入研究才发现,原理其实很朴素。

PPG信号是怎么来的?

心脏每跳动一次,血液就会泵到全身。血液流过毛细血管时,血管会扩张收缩。这个过程中,血液对光的吸收量也在变化。

  • 心脏收缩时:血液量增加,吸收更多光,反射光变弱
  • 心脏舒张时:血液量减少,吸收更少光,反射光变强

传感器捕捉到的就是这种光强变化,形成一条波浪线——这就是PPG信号。

核心公式:心率(HR) = 60 / 平均心跳间隔(RR间期)

单位是bpm(beats per minute),也就是每分钟心跳次数。

Android心率传感器硬件

目前主流方案有两种:

方案类型 代表芯片 特点
单LED方案 MAX30102 成本低,功耗小,精度一般
多LED方案 MAX30101 抗干扰强,精度高,功耗略大

嗯,这里要注意:不是所有Android手机都内置了心率传感器。很多手环、手表用的是外挂方案,通过蓝牙连接手机。

二、PPG信号处理:从原始数据到心率值

原始PPG信号长什么样?说白了就是一条抖动的曲线。直接拿它算心率,结果会惨不忍睹。我踩过这个坑,第一次拿到数据就急着算,结果心率显示200多bpm——吓我一跳。

信号处理流程

我个人习惯把处理流程分成三步:

  1. 去噪:滤掉高频噪声和工频干扰
  2. 基线漂移消除:去掉呼吸、运动造成的低频漂移
  3. 峰值检测:找到每个心跳波峰的位置

我的经验:去噪用带通滤波器就够了,通带设在0.5Hz~5Hz之间。这个范围刚好覆盖人体心率(30~300bpm)。

代码实现:实时PPG滤波

// 带通滤波器实现(简化版)
public class BandpassFilter {
    private float[] buffer;
    private int index = 0;
    private float lowCut = 0.5f;  // 低频截止
    private float highCut = 5.0f; // 高频截止
    
    public float filter(float rawValue) {
        // 滑动窗口均值滤波 + 差分处理
        buffer[index] = rawValue;
        index = (index + 1) % buffer.length;
        
        float sum = 0;
        for (float v : buffer) sum += v;
        float mean = sum / buffer.length;
        
        // 减去直流分量,保留交流信号
        return rawValue - mean;
    }
}

你想想看,如果不去掉直流分量,峰值检测会一直受基线影响。我刚开始做的时候,峰值检测总是多检或少检,后来加上这个滤波,效果立竿见影。

三、实时心率监测:Android Sensor API实战

Android官方提供了Sensor.TYPE_HEART_RATE,但说实话,这个API返回的是已经处理好的心率值。如果你想拿到原始PPG信号做深度处理,得走厂商私有接口。

注册心率传感器

SensorManager sensorManager = 
    (SensorManager) getSystemService(Context.SENSOR_SERVICE);
Sensor heartRateSensor = 
    sensorManager.getDefaultSensor(Sensor.TYPE_HEART_RATE);

if (heartRateSensor == null) {
    // 设备不支持心率传感器
    return;
}

sensorManager.registerListener(
    this, 
    heartRateSensor, 
    SensorManager.SENSOR_DELAY_NORMAL
);

注意:Android 10以上需要动态申请BODY_SENSORS权限。我曾经在适配时忘了这个,结果用户反馈说心率一直为0,排查了半天才发现是权限问题。

实时数据显示

拿到心率值后,我建议用HandlerLiveData更新UI。别直接在传感器回调里更新,那个线程太快了,UI会卡死。

@Override
public void onSensorChanged(SensorEvent event) {
    if (event.sensor.getType() == Sensor.TYPE_HEART_RATE) {
        float heartRate = event.values[0];
        // 用Handler发送到主线程
        mHandler.post(() -> updateUI(heartRate));
    }
}

四、运动健康应用开发:从数据到体验

光有数据不够,用户要的是体验。我参与过几个运动健康App的开发,踩了不少坑,分享几个关键点。

运动场景下的抗干扰

跑步时测心率,PPG信号会被运动噪声严重污染。怎么办?

  • 加速度计辅助:用加速度计检测运动强度,动态调整滤波器参数
  • 滑动窗口平均:取最近5秒的心率平均值,平滑显示
  • 异常值剔除:心率突变超过20bpm时,先缓存不显示

避坑指南:我曾经在跑步App里直接显示原始心率,结果用户跑着跑着心率从120跳到180又跳回100,用户以为App坏了。后来加了平滑处理,体验好多了。

数据可视化

用户喜欢看心率曲线。我建议用自定义View绘制实时折线图,别用WebView,太耗电。

// 自定义View绘制心率曲线(核心逻辑)
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    
    Path path = new Path();
    path.moveTo(0, getHeight() / 2);
    
    for (int i = 0; i < heartRateData.size(); i++) {
        float x = i * (getWidth() / maxDataPoints);
        float y = getHeight() - 
            (heartRateData.get(i) / maxHeartRate) * getHeight();
        path.lineTo(x, y);
    }
    
    canvas.drawPath(path, linePaint);
}

五、知识体系总览

下面这张图是我整理的心率传感器开发知识体系,你可以对照着查漏补缺。

心率传感器开发知识体系 硬件层 LED + 光敏传感器 信号层 PPG信号采集与滤波 算法层 峰值检测与心率计算 应用层 实时监测 · 运动健康 · 数据可视化 关键挑战与解决方案 运动噪声 → 加速度计辅助滤波 低功耗 → 动态采样率调节 精度提升 → 多LED融合算法

这张图把整个开发流程串起来了。从硬件采集到信号处理,再到算法计算,最后落到应用层。每个环节都有坑,但踩过去就是成长。

我的建议:初学者先从硬件层入手,买个开发板跑通原始数据。别一上来就搞算法,容易劝退。我当年就是先玩了一个月的MAX30102,才慢慢理解PPG信号的特征。

好了,这一章的内容就到这。心率传感器开发其实不难,关键是理解信号处理的基本原理。下一章我们会深入讲峰值检测算法,到时候再聊。


公众号:蓝海资料掘金营,微信deep3321