一、心率检测原理:光电容积描记法
心率检测,说白了就是测量心脏每分钟跳了多少下。在Android设备上,我们用的最多的就是PPG信号——光电容积描记法。
我记得第一次接触这个技术时,觉得挺神奇的。一个LED灯加一个光敏传感器,就能测出心跳?后来深入研究才发现,原理其实很朴素。
PPG信号是怎么来的?
心脏每跳动一次,血液就会泵到全身。血液流过毛细血管时,血管会扩张收缩。这个过程中,血液对光的吸收量也在变化。
- 心脏收缩时:血液量增加,吸收更多光,反射光变弱
- 心脏舒张时:血液量减少,吸收更少光,反射光变强
传感器捕捉到的就是这种光强变化,形成一条波浪线——这就是PPG信号。
核心公式:心率(HR) = 60 / 平均心跳间隔(RR间期)
单位是bpm(beats per minute),也就是每分钟心跳次数。
Android心率传感器硬件
目前主流方案有两种:
| 方案类型 | 代表芯片 | 特点 |
|---|---|---|
| 单LED方案 | MAX30102 | 成本低,功耗小,精度一般 |
| 多LED方案 | MAX30101 | 抗干扰强,精度高,功耗略大 |
嗯,这里要注意:不是所有Android手机都内置了心率传感器。很多手环、手表用的是外挂方案,通过蓝牙连接手机。
二、PPG信号处理:从原始数据到心率值
原始PPG信号长什么样?说白了就是一条抖动的曲线。直接拿它算心率,结果会惨不忍睹。我踩过这个坑,第一次拿到数据就急着算,结果心率显示200多bpm——吓我一跳。
信号处理流程
我个人习惯把处理流程分成三步:
- 去噪:滤掉高频噪声和工频干扰
- 基线漂移消除:去掉呼吸、运动造成的低频漂移
- 峰值检测:找到每个心跳波峰的位置
我的经验:去噪用带通滤波器就够了,通带设在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,排查了半天才发现是权限问题。
实时数据显示
拿到心率值后,我建议用Handler或LiveData更新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);
}
五、知识体系总览
下面这张图是我整理的心率传感器开发知识体系,你可以对照着查漏补缺。
这张图把整个开发流程串起来了。从硬件采集到信号处理,再到算法计算,最后落到应用层。每个环节都有坑,但踩过去就是成长。
我的建议:初学者先从硬件层入手,买个开发板跑通原始数据。别一上来就搞算法,容易劝退。我当年就是先玩了一个月的MAX30102,才慢慢理解PPG信号的特征。
好了,这一章的内容就到这。心率传感器开发其实不难,关键是理解信号处理的基本原理。下一章我们会深入讲峰值检测算法,到时候再聊。
公众号:蓝海资料掘金营,微信deep3321