2. 可穿戴设备步数统计算法实现

可穿戴设备步数统计算法实现 学习卡片

步数检测的核心问题

加速度计输出的三轴信号中,步行产生的周期性波动叠加在重力分量和手部随机抖动之上。直接对原始幅值做阈值计数,在慢走时会漏计(峰值低于阈值),在快走或摆臂时会误计(单个步态周期出现多个局部峰值)。因此算法需要解决三件事:把三轴合成为与姿态无关的幅值、用自适应方式跟踪步态节律、对候选峰值做时间间隔约束。

步数检测信号链与判定逻辑 三轴加速度 ax, ay, az @50Hz 合成幅值 m=√(ax²+ay²+az²) 带通滤波 0.5–3.5 Hz 自适应阈值 T=μ+k·σ 滑动窗 峰值检测 +间隔约束 滤波后波形与阈值判定 0 慢走 1.2Hz 快走 2.4Hz T 误计场景 快走时单个步态周期出现双峰 → 间隔约束:相邻峰值 < 250ms 丢弃 → 最小步间隔 250ms,最大 2000ms 漏计场景 慢走时峰值低于固定阈值 → 自适应阈值:T = μ + 0.8σ → 滑动窗 2s,每 0.5s 更新一次

算法实现

下面给出一个可直接在嵌入式或移动端运行的步数检测实现。输入为三轴加速度采样序列,输出为累计步数。核心参数:采样率 50Hz,带通 0.5–3.5Hz,滑动窗 2s,阈值系数 k=0.8,最小步间隔 250ms。

import math
from collections import deque

class StepCounter:
    def __init__(self, fs=50.0, win_sec=2.0, k=0.8,
                 min_step_ms=250, max_step_ms=2000):
        self.fs = fs
        self.k = k
        self.min_gap = int(min_step_ms * fs / 1000)
        self.max_gap = int(max_step_ms * fs / 1000)
        self.win = int(win_sec * fs)
        self.buf = deque(maxlen=self.win)   # 滤波后幅值
        self.steps = 0
        self.last_peak_idx = -10**9
        self.idx = 0
        # 一阶带通(高通+低通级联)状态
        self.hp_prev = 0.0
        self.lp_prev = 0.0
        self.hp_a = 0.95   # 高通系数,抑制重力
        self.lp_a = 0.20   # 低通系数,抑制抖动

    def _bandpass(self, m):
        # 高通:去除重力与缓慢漂移
        hp = self.hp_a * (self.hp_prev + m - self._hp_in_prev) \
             if hasattr(self, '_hp_in_prev') else 0.0
        self._hp_in_prev = m
        self.hp_prev = hp
        # 低通:平滑高频噪声
        lp = self.lp_prev + self.lp_a * (hp - self.lp_prev)
        self.lp_prev = lp
        return lp

    def update(self, ax, ay, az):
        m = math.sqrt(ax*ax + ay*ay + az*az)
        f = self._bandpass(m)
        self.buf.append(f)
        self.idx += 1

        if len(self.buf) < self.win:
            return self.steps

        # 自适应阈值
        mean = sum(self.buf) / len(self.buf)
        var = sum((v - mean)**2 for v in self.buf) / len(self.buf)
        thr = mean + self.k * math.sqrt(var)

        # 峰值检测:当前点大于阈值且为局部极大
        if len(self.buf) >= 3:
            a, b, c = self.buf[-3], self.buf[-2], self.buf[-1]
            if b > thr and b > a and b > c:
                gap = self.idx - self.last_peak_idx
                if self.min_gap <= gap <= self.max_gap:
                    self.steps += 1
                    self.last_peak_idx = self.idx
                elif gap > self.max_gap:
                    # 长时间无步,重置参考点,避免漏计首步
                    self.last_peak_idx = self.idx
        return self.steps

参数选择与验证

参数取值作用调整方向
采样率 fs50 Hz满足 3.5Hz 上限的奈奎斯特要求低于 25Hz 会丢失快走谐波
带通下限0.5 Hz抑制重力与姿态漂移过高会削掉慢走基频
带通上限3.5 Hz抑制手部抖动与高频噪声过低会平滑掉峰值
阈值系数 k0.8控制灵敏度k 增大→漏计增多;k 减小→误计增多
最小步间隔250 ms抑制单周期双峰误计对应最快 4 步/秒
最大步间隔2000 ms判定为停顿并重置参考过长会漏计慢走首步
验证方法:用同一段录制数据分别以 1.2Hz(慢走)和 2.4Hz(快走)回放,统计输出步数与人工标注步数的偏差。慢走场景重点观察是否漏计,快走场景重点观察是否出现约 2 倍误计。若慢走漏计,优先降低 k 或放宽带通下限;若快走误计,优先收紧最小步间隔或提高带通上限。
加速度计安装位置会显著影响信号质量。手腕佩戴时摆臂幅度大,快走易产生双峰;腰部佩戴时信号更干净但慢走幅值小。同一套参数在不同佩戴位置下表现不同,实际部署时应按佩戴位置分别标定 k 与最小步间隔。