智能手环的心率监测技术主要依赖光电容积描记法(Photoplethysmography, PPG),通过光学传感器检测血流变化来估算心率
以下是详细介绍,包括技术原理、实现步骤,以及 C#, C++, Java, 和 Python 的代码示例,结合你之前提到的 REM 检测(RMSSD)、功耗优化 和 Sleep-EDF 数据集验证的需求,提供具体实现和调试指导。通过检测 PPG 信号的峰值,计算心跳间隔(RR 间隔),进而估算心率(bpm = 60 / RR)。实现:C#(验证)、C++(嵌入式)、Java(Android)、Py
智能手环的心率监测技术主要依赖光电容积描记法(Photoplethysmography, PPG),通过光学传感器检测血流变化来估算心率。此外,结合加速度传感器和算法优化,可以提升精度并适应不同场景(如运动、睡眠)。以下是详细介绍,包括技术原理、实现步骤,以及 C#, C++, Java, 和 Python 的代码示例,结合你之前提到的 REM 检测(RMSSD)、功耗优化 和 Sleep-EDF 数据集验证的需求,提供具体实现和调试指导。
1. 心率监测技术原理
1.1 PPG 技术
-
工作原理:
-
PPG 使用 LED(通常绿色或红外光)照射皮肤,光被血液和组织吸收或反射。
-
血液体积随心跳变化,引起反射光强度的周期性波动。
-
光电传感器(如光电二极管)捕获反射光,生成 PPG 信号。
-
通过检测 PPG 信号的峰值,计算心跳间隔(RR 间隔),进而估算心率(bpm = 60 / RR)。
-
-
传感器:常见如 MAX30102(红外 + 红光 LED,适合心率和血氧检测)。
-
优点:
-
非侵入式,适合可穿戴设备。
-
低成本,易集成。
-
-
挑战:
-
运动伪影:手腕运动干扰 PPG 信号。
-
皮肤差异:肤色、纹身、佩戴松紧度影响信号质量。
-
功耗:高采样率和 LED 亮度增加能耗。
-
1.2 信号处理与算法
-
滤波:
-
带通滤波(0.5-4Hz):去除低频漂移(如呼吸)和高频噪声(如运动)。
-
卡尔曼滤波或指数移动平均(EMA):平滑信号。
-
-
峰值检测:
-
检测 PPG 信号的波峰,计算 RR 间隔。
-
算法:滑动窗口、阈值法或自适应阈值。
-
-
心率变异性(HRV):
-
RMSSD:连续 RR 间隔差的均方根,用于 REM 睡眠检测或压力分析。
-
频域分析(LF/HF 比):评估自主神经系统状态。
-
-
运动补偿:
-
使用加速度传感器数据,识别运动伪影,剔除异常 RR 间隔。
-
-
功耗优化:
-
降低采样率(如 25Hz 代替 100Hz)。
-
动态调整 LED 亮度。
-
使用硬件中断触发采样。
-
1.3 典型应用
-
实时心率:运动监测(如跑步心率)。
-
睡眠分析:结合 RMSSD 检测 REM 睡眠(见前述)。
-
压力监测:通过 HRV 分析压力水平。
-
健康预警:检测异常心率(如心动过速)。
2. 实现方案
以下实现基于 PPG 传感器(如 MAX30102)的心率监测,结合加速度数据进行运动补偿,包含 RMSSD 计算和功耗优化。代码涵盖:
-
数据处理:带通滤波、峰值检测、RMSSD 计算。
-
功耗优化:低采样率(25Hz)、硬件中断(C++)。
-
验证:使用 Sleep-EDF 数据集(Python)。
2.1 C# 实现(桌面验证)
适合后处理或模拟分析。
csharp
using System;
using System.Collections.Generic;
using System.Linq;
class HeartRateMonitor
{
public class HeartRateData
{
public double Time { get; set; }
public double BPM { get; set; }
public double RMSSD { get; set; }
}
public static List<HeartRateData> MonitorHeartRate(double[] ppg, double[] accelX, double[] accelY, double[] accelZ,
double[] timestamps, double sfreq = 25.0, int windowSize = 750)
{
List<HeartRateData> results = new List<HeartRateData>();
double accelThreshold = 0.3; // g,运动伪影阈值
// 带通滤波(简化,使用 EMA)
double[] FilterEMA(double[] data, double alpha = 0.1)
{
double[] filtered = new double[data.Length];
filtered[0] = data[0];
for (int i = 1; i < data.Length; i++)
filtered[i] = alpha * data[i] + (1 - alpha) * filtered[i - 1];
return filtered;
}
// 峰值检测
List<double> DetectPeaks(double[] signal, double threshold)
{
List<double> peaks = new List<double>();
for (int i = 1; i < signal.Length - 1; i++)
if (signal[i] > signal[i-1] && signal[i] > signal[i+1] && signal[i] > threshold)
peaks.Add(timestamps[i]);
return peaks;
}
// 计算 RMSSD
double CalculateRMSSD(List<double> rr_intervals)
{
if (rr_intervals.Count < 2) return 0;
double sum = 0.0;
for (int i = 1; i < rr_intervals.Count; i++)
sum += Math.Pow(rr_intervals[i] - rr_intervals[i-1], 2);
return Math.Sqrt(sum / (rr_intervals.Count - 1));
}
double[] filteredPPG = FilterEMA(ppg);
double[] accelMag = new double[accelX.Length];
for (int i = 0; i < accelX.Length; i++)
accelMag[i] = Math.Sqrt(accelX[i] * accelX[i] + accelY[i] * accelY[i] + accelZ[i] * accelZ[i]);
for (int i = 0; i <= ppg.Length - windowSize; i += windowSize)
{
double[] windowPPG = filteredPPG.Skip(i).Take(windowSize).ToArray();
double[] windowAccel = accelMag.Skip(i).Take(windowSize).ToArray();
double windowTime = timestamps[i];
// 检查运动伪影
double accelStd = Math.Sqrt(windowAccel.Average(v => Math.Pow(v - windowAccel.Average(), 2)));
if (accelStd > accelThreshold) continue; // 跳过高运动窗口
// 峰值检测
double threshold = windowPPG.Average() + windowPPG.StandardDeviation();
List<double> peaks = DetectPeaks(windowPPG, threshold);
List<double> rr_intervals = peaks.Skip(1).Select((t, idx) => (t - peaks[idx]) * 1000).ToList();
double bpm = peaks.Count > 1 ? 60.0 / (rr_intervals.Average() / 1000) : 0;
double rmssd = CalculateRMSSD(rr_intervals);
results.Add(new HeartRateData { Time = windowTime, BPM = bpm, RMSSD = rmssd });
}
return results;
}
static void Main()
{
int n = 3000;
double[] ppg = new double[n];
double[] accelX = new double[n];
double[] accelY = new double[n];
double[] accelZ = new double[n];
double[] timestamps = new double[n];
Random rand = new Random();
for (int i = 0; i < n; i++)
{
timestamps[i] = i / 25.0; // 25Hz
ppg[i] = 1.0 + 0.5 * Math.Sin(2 * Math.PI * 1.0 * timestamps[i]) + rand.NextDouble() * 0.1;
accelX[i] = 0.05 * Math.Sin(0.01 * i) + rand.NextDouble() * 0.1;
accelY[i] = 0.05 * Math.Sin(0.01 * i + 1) + rand.NextDouble() * 0.1;
accelZ[i] = 1.0 + 0.05 * Math.Sin(0.01 * i + 2) + rand.NextDouble() * 0.1;
}
var results = MonitorHeartRate(ppg, accelX, accelY, accelZ, timestamps);
foreach (var result in results)
Console.WriteLine($"Time: {result.Time:F2}s, BPM: {result.BPM:F1}, RMSSD: {result.RMSSD:F1}ms");
}
}
说明:
-
滤波:EMA 滤波,适合桌面验证。
-
峰值检测:动态阈值(均值 + 标准差)。
-
RMSSD:计算 RR 间隔差均方根。
-
功耗:25Hz 采样率。
2.2 C++ 实现(Arduino + MAX30102 + MPU6050)
cpp
#include <Wire.h>
#include <MPU6050.h>
#include <SparkFun_MAX3010x_Sensor_Library.h> // 需安装 MAX3010x 库
#include <LowPower.h>
MAX30105 particleSensor;
MPU6050 mpu;
const int windowSize = 750; // 30秒,25Hz
float ppgWindow[windowSize];
float accelWindow[windowSize];
int windowIndex = 0;
float accelThreshold = 0.3;
volatile bool motionFlag = false;
class KalmanFilter {
private:
float x = 0.0, P = 1.0, Q = 0.01, R = 0.1;
public:
float filter(float z) {
float x_pred = x;
float P_pred = P + Q;
float K = P_pred / (P_pred + R);
x = x_pred + K * (z - x_pred);
P = (1 - K) * P_pred;
return x;
}
};
KalmanFilter filterPPG, filterAccel;
void setup() {
Serial.begin(9600);
Wire.begin();
mpu.initialize();
if (!mpu.testConnection() || !particleSensor.begin(Wire, I2C_SPEED_FAST)) {
Serial.println("Sensor connection failed");
while (1);
}
particleSensor.setup(); // 默认设置
mpu.setIntMotionEnabled(true);
mpu.setMotionDetectionThreshold(20);
mpu.setMotionDetectionDuration(2);
attachInterrupt(digitalPinToInterrupt(2), motionDetected, RISING);
for (int i = 0; i < windowSize; i++) {
ppgWindow[i] = 0.0;
accelWindow[i] = 0.0;
}
}
void motionDetected() {
motionFlag = true;
}
float calculateRMSSD(float* ppg, int size) {
float rr_intervals[size];
int peakCount = 0;
for (int i = 1; i < size - 1; i++)
if (ppg[i] > ppg[i-1] && ppg[i] > ppg[i+1] && ppg[i] > 0.5)
rr_intervals[peakCount++] = i / 25.0 * 1000; // ms
if (peakCount < 2) return 0;
float sum = 0.0;
for (int i = 1; i < peakCount; i++)
sum += pow(rr_intervals[i] - rr_intervals[i-1], 2);
return sqrt(sum / (peakCount - 1));
}
void loop() {
if (!motionFlag) {
LowPower.powerDown(SLEEP_120MS, ADC_OFF, BOD_OFF);
return;
}
int16_t ax, ay, az;
mpu.getAcceleration(&ax, &ay, &az);
float accel = sqrt(ax * ax + ay * ay + az * az) / 16384.0;
float filteredAccel = filterAccel.filter(accel);
float ppg = particleSensor.getIR(); // 红外光数据
float filteredPPG = filterPPG.filter(ppg);
ppgWindow[windowIndex] = filteredPPG;
accelWindow[windowIndex] = filteredAccel;
windowIndex = (windowIndex + 1) % windowSize;
if (windowIndex == 0) {
float accelMean = 0.0, accelStd = 0.0;
for (int i = 0; i < windowSize; i++)
accelMean += accelWindow[i];
accelMean /= windowSize;
for (int i = 0; i < windowSize; i++)
accelStd += pow(accelWindow[i] - accelMean, 2);
accelStd = sqrt(accelStd / windowSize);
if (accelStd > accelThreshold) return;
float rmssd = calculateRMSSD(ppgWindow, windowSize);
float bpm = 0;
int peakCount = 0;
for (int i = 1; i < windowSize - 1; i++)
if (ppgWindow[i] > ppgWindow[i-1] && ppgWindow[i] > ppgWindow[i+1] && ppgWindow[i] > 0.5)
peakCount++;
if (peakCount > 1)
bpm = 60.0 / ((windowSize / peakCount) / 25.0);
Serial.print("Time: ");
Serial.print(millis() / 1000.0);
Serial.print("s, BPM: ");
Serial.print(bpm);
Serial.print(", RMSSD: ");
Serial.println(rmssd);
}
motionFlag = false;
delay(40); // 25Hz
}
说明:
-
功耗优化:25Hz 采样,MPU6050 运动中断,LowPower 睡眠。
-
RMSSD:基于 PPG 峰值计算。
-
硬件:MAX30102 + MPU6050,INT 引脚接 D2。
-
依赖:MPU6050, SparkFun_MAX3010x.
2.3 Java 实现(Android)
java
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayDeque;
import java.util.Deque;
public class HeartRateActivity extends AppCompatActivity implements SensorEventListener {
private SensorManager sensorManager;
private Sensor accelerometer;
private TextView hrView;
private final int windowSize = 750; // 30秒,25Hz
private final Deque<Double> ppgWindow = new ArrayDeque<>();
private final Deque<Double> accelWindow = new ArrayDeque<>();
private final LowPassFilter filterPPG = new LowPassFilter();
private final LowPassFilter filterAccel = new LowPassFilter();
static class LowPassFilter {
private double filteredValue = 0.0;
private final double alpha = 0.1;
public double filter(double input) {
filteredValue = alpha * input + (1 - alpha) * filteredValue;
return(filteredValue);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
hrView = findViewById(R.id.hr_view);
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_NORMAL);
}
private double calculateRMSSD(Deque<Double> ppg) {
Double[] ppgArray = ppg.toArray(new Double[0]);
List<Double> rr_intervals = new ArrayList<>();
for (int i = 1; i < ppgArray.length - 1; i++)
if (ppgArray[i] > ppgArray[i-1] && ppgArray[i] > ppgArray[i+1] && ppgArray[i] > 0.5)
rr_intervals.add(i / 25.0 * 1000);
if (rr_intervals.size() < 2) return 0;
double sum = 0.0;
for (int i = 1; i < rr_intervals.size(); i++)
sum += Math.pow(rr_intervals.get(i) - rr_intervals.get(i-1), 2);
return Math.sqrt(sum / (rr_intervals.size() - 1));
}
@Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
float x = event.values[0] / 9.81f;
float y = event.values[1] / 9.81f;
float z = event.values[2] / 9.81f;
double accel = Math.sqrt(x * x + y * y + z * z);
double filteredAccel = filterAccel.filter(accel);
double ppg = 1.0 + 0.5 * Math.sin(2 * Math.PI * 1.0 * (System.currentTimeMillis() / 1000.0)) + Math.random() * 0.1; // 模拟 PPG
double filteredPPG = filterPPG.filter(ppg);
ppgWindow.add(filteredPPG);
accelWindow.add(filteredAccel);
if (ppgWindow.size() > windowSize) {
ppgWindow.removeFirst();
accelWindow.removeFirst();
}
if (ppgWindow.size() == windowSize) {
double accelMean = 0.0, accelStd = 0.0;
for (double v : accelWindow) accelMean += v;
accelMean /= windowSize;
for (double v : accelWindow) accelStd += Math.pow(v - accelMean, 2);
accelStd = Math.sqrt(accelStd / windowSize);
if (accelStd > 0.3) return;
double rmssd = calculateRMSSD(ppgWindow);
int peakCount = 0;
Double[] ppgArray = ppgWindow.toArray(new Double[0]);
for (int i = 1; i < ppgArray.length - 1; i++)
if (ppgArray[i] > ppgArray[i-1] && ppgArray[i] > ppgArray[i+1] && ppgArray[i] > 0.5)
peakCount++;
double bpm = peakCount > 1 ? 60.0 / ((windowSize / peakCount) / 25.0) : 0;
double currentTime = System.currentTimeMillis() / 1000.0;
runOnUiThread(() -> hrView.setText(String.format("BPM: %.1f, RMSSD: %.1fms", bpm, rmssd)));
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(this);
}
}
布局(res/layout/activity_main.xml):
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/hr_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BPM: 0"/>
</LinearLayout>
说明:
-
功耗优化:SENSOR_DELAY_NORMAL(约 25Hz)。
-
RMSSD:检测 HRV。
-
注意:PPG 数据模拟,需替换为实际传感器(如 Android Wear PPG)。
2.4 Python 实现(Sleep-EDF 验证)
python
import numpy as np
import pandas as pd
import tensorflow as tf
from sklearn.preprocessing import StandardScaler
import mne
import os
def load_sleep_edf(data_dir):
psg_file = os.path.join(data_dir, "SC4001E0-PSG.edf")
hypno_file = os.path.join(data_dir, "SC4001E0-Hypnogram.edf")
psg = mne.io.read_raw_edf(psg_file, preload=True)
hypno = mne.read_annotations(hypno_file)
data = psg.get_data(picks=['ECG', 'Accel-X', 'Accel-Y', 'Accel-Z'])
sfreq = psg.info['sfreq']
timestamps = psg.times
heart_rate = np.random.uniform(50, 90, len(timestamps)) // Replace with ECG-derived HR
features = np.column_stack([data[1:4].T, heart_rate])
labels = []
for t in range(0, len(timestamps), int(sfreq * 30)):
label = hypno[int(t / sfreq)]['description']
label_id = {'W': 0, 'R': 1, '1': 2, '2': 2, '3': 3, '4': 3}.get(label, 2)
labels.append(label_id)
labels = np.array(labels[:len(features) // int(sfreq * 30)])
return features, timestamps, labels, sfreq
def calculate_rmssd(ppg_data, window_size, sfreq):
peaks = []
for i in range(1, len(ppg_data) - 1):
if ppg_data[i] > ppg_data[i-1] and ppg_data[i] > ppg_data[i+1] and ppg_data[i] > 0.5:
peaks.append(i / sfreq * 1000)
if len(peaks) < 2: return 0
diffs = np.diff(peaks)
return np.sqrt(np.mean(diffs ** 2))
def monitor_heart_rate(data, timestamps, sfreq, window_size=750):
results = []
for i in range(0, len(data) - window_size, window_size):
window = data[i:i + window_size]
accel_mag = np.sqrt(window[:, 0]**2 + window[:, 1]**2 + window[:, 2]**2)
if np.std(accel_mag) > 0.3: continue
ppg = window[:, 3]
rmssd = calculate_rmssd(ppg, window_size, sfreq)
peaks = []
for j in range(1, len(ppg) - 1):
if ppg[j] > ppg[j-1] and ppg[j] > ppg[j+1] and ppg[j] > 0.5:
peaks.append(j / sfreq)
bpm = len(peaks) > 1 ? 60.0 / (np.mean(np.diff(peaks))) : 0
results.append({"time": timestamps[i], "bpm": bpm, "rmssd": rmssd})
return results
if __name__ == "__main__":
data_dir = "path_to_sleep_edf"
features, timestamps, labels, sfreq = load_sleep_edf(data_dir)
results = monitor_heart_rate(features, timestamps, sfreq)
for result in results:
print(f"Time: {result['time']:.2f}s, BPM: {result['bpm']:.1f}, RMSSD: {result['rmssd']:.1f}ms")
说明:
-
Sleep-EDF:验证心率和 RMSSD。
-
功耗:25Hz 采样。
-
依赖:mne, numpy, pandas.
3. 数据集验证:Sleep-EDF
-
下载:
-
下载 SC* 文件(PSG 和 Hypnogram)。
-
处理:
-
提取 ECG 和加速度,计算心率和 RMSSD。
-
验证 REM 检测(RMSSD > 30ms)。
-
-
验证:
-
比较心率和 Hypnogram 标签,检查 REM 阶段准确性。
-
4. 硬件调试
-
连接:MPU6050 (VCC→5V, GND→GND, SCL→A5, SDA→A4, INT→D2), MAX30102 (VCC→3.3V, GND→GND, SCL→A5, SDA→A4).
-
问题解决:
-
PPG 信号弱:调整 MAX30102 LED 电流,紧贴皮肤。
-
运动伪影:验证加速度阈值(0.3g)。
-
中断失效:检查 INT 引脚和阈值设置。
-
-
工具:串口监视器打印 PPG、加速度、RMSSD。
5. 总结
-
心率监测:PPG 技术,结合滤波、峰值检测、RMSSD。
-
实现:C#(验证)、C++(嵌入式)、Java(Android)、Python(Sleep-EDF)。
-
功耗优化:25Hz 采样,硬件中断。
-
数据集:Sleep-EDF 验证 RMSSD 和心率。
下一步:需要 LF/HF 比、Android 数据库存储、或具体硬件调试?请告诉我,我将提供更详细代码或指导!
更多推荐



所有评论(0)