4. 动态阈值调整策略
任务说明
本案例针对固定阈值在步行、跑步等不同运动强度下误检率偏高的问题,实现一种基于运动强度的动态阈值调整策略。核心思路是:利用加速度计三轴合成幅值的短期能量变化估计当前运动强度,并据此自适应调整峰值检测阈值——强度越大,阈值越高,从而在慢走与快跑场景下均能保持稳定的步数检测准确性。
核心算法设计
采用滑动窗口内的加速度幅值标准差作为运动强度指标。设窗口长度为 \( N \),窗口内合成加速度幅值序列为 \( a_i \),则强度指标 \( S = \sqrt{\frac{1}{N}\sum_{i=1}^{N}(a_i - \bar{a})^2} \)。动态阈值 \( T_{dyn} \) 由基线阈值 \( T_{base} \) 与强度补偿项线性叠加:\( T_{dyn} = T_{base} + k \cdot S \),其中 \( k \) 为补偿系数。峰值检测时,仅当当前幅值超过动态阈值且为局部极大值时计为一步。
代码实现
#include <stdio.h>
#include <math.h>
#define WIN_SIZE 20 // 滑动窗口长度
#define T_BASE 1.2f // 基线阈值(g)
#define K_COEF 0.8f // 强度补偿系数
#define G 9.81f // 重力加速度
// 模拟三轴加速度数据(单位:g),共60个采样点
// 前30点为慢走(幅值波动小),后30点为快跑(幅值波动大)
float ax[60] = {0.95f,1.02f,0.98f,1.05f,0.92f,1.08f,0.96f,1.01f,0.99f,1.04f,
0.93f,1.06f,0.97f,1.03f,0.94f,1.07f,0.95f,1.02f,0.98f,1.05f,
0.91f,1.09f,0.96f,1.04f,0.93f,1.06f,0.97f,1.02f,0.99f,1.03f,
0.80f,1.40f,0.75f,1.55f,0.70f,1.60f,0.82f,1.45f,0.78f,1.50f,
0.85f,1.35f,0.72f,1.58f,0.88f,1.42f,0.76f,1.52f,0.81f,1.47f,
0.74f,1.56f,0.79f,1.49f,0.83f,1.44f,0.77f,1.53f,0.86f,1.38f};
float ay[60] = {0.02f,0.03f,-0.01f,0.04f,-0.02f,0.03f,0.01f,-0.03f,0.02f,0.04f,
-0.01f,0.05f,0.00f,0.03f,-0.02f,0.04f,0.01f,-0.01f,0.03f,0.02f,
0.00f,0.04f,-0.03f,0.02f,0.01f,-0.02f,0.03f,0.00f,0.04f,-0.01f,
0.10f,-0.15f,0.12f,-0.18f,0.08f,-0.20f,0.11f,-0.14f,0.09f,-0.17f,
0.13f,-0.12f,0.07f,-0.19f,0.14f,-0.11f,0.10f,-0.16f,0.12f,-0.13f,
0.09f,-0.18f,0.11f,-0.15f,0.08f,-0.17f,0.13f,-0.12f,0.10f,-0.14f};
float az[60] = {0.10f,0.12f,0.08f,0.15f,0.09f,0.13f,0.11f,0.07f,0.14f,0.10f,
0.12f,0.09f,0.13f,0.08f,0.11f,0.14f,0.10f,0.12f,0.09f,0.15f,
0.08f,0.13f,0.11f,0.10f,0.14f,0.09f,0.12f,0.08f,0.13f,0.11f,
0.20f,-0.25f,0.22f,-0.30f,0.18f,-0.32f,0.21f,-0.24f,0.19f,-0.28f,
0.23f,-0.22f,0.17f,-0.31f,0.24f,-0.21f,0.20f,-0.27f,0.22f,-0.23f,
0.18f,-0.29f,0.21f,-0.26f,0.19f,-0.28f,0.23f,-0.22f,0.20f,-0.25f};
// 计算合成幅值
float magnitude(int idx) {
return sqrtf(ax[idx]*ax[idx] + ay[idx]*ay[idx] + az[idx]*az[idx]);
}
// 计算窗口内标准差(运动强度)
float calc_intensity(int start, int end) {
float sum = 0.0f, mean;
int n = end - start + 1;
for (int i = start; i <= end; i++) sum += magnitude(i);
mean = sum / n;
sum = 0.0f;
for (int i = start; i <= end; i++) {
float diff = magnitude(i) - mean;
sum += diff * diff;
}
return sqrtf(sum / n);
}
int main() {
int step_count = 0;
float prev_mag = magnitude(0);
float curr_mag, next_mag;
float intensity, dyn_threshold;
printf("采样点 幅值(g) 动态阈值 检测结果\n");
printf("------------------------------------\n");
for (int i = 1; i < 59; i++) {
curr_mag = magnitude(i);
next_mag = magnitude(i + 1);
// 计算当前运动强度(使用前WIN_SIZE个点,不足则从0开始)
int win_start = (i - WIN_SIZE + 1 > 0) ? i - WIN_SIZE + 1 : 0;
intensity = calc_intensity(win_start, i);
dyn_threshold = T_BASE + K_COEF * intensity;
// 峰值检测: 当前点大于前后点且超过动态阈值
if (curr_mag > prev_mag && curr_mag > next_mag && curr_mag > dyn_threshold) {
step_count++;
printf("%-9d %-9.3f %-9.3f 步数+1\n", i, curr_mag, dyn_threshold);
} else {
printf("%-9d %-9.3f %-9.3f -\n", i, curr_mag, dyn_threshold);
}
prev_mag = curr_mag;
}
printf("------------------------------------\n");
printf("总步数: %d\n", step_count);
return 0;
}
运行与验证
编译运行:
gcc -o step_counter step_counter.c -lm
./step_counter
预期输出分析:前30个采样点(慢走)幅值波动约±0.08g,强度指标约0.05,动态阈值约1.24g,幅值峰值约1.08g,不会触发误检;后30个采样点(快跑)幅值波动约±0.4g,强度指标约0.28,动态阈值约1.42g,幅值峰值约1.60g,可正确检出步数。代码逻辑可推出约检出10步(快跑段每3个点出现一个峰值)。
判定标准:程序输出中慢走段(采样点1~29)无步数检出,快跑段(采样点30~58)检出步数且每个检出的峰值幅值均大于对应动态阈值,即证明动态阈值策略有效区分了不同运动强度。
验证要点:观察输出中慢走段(采样点1~29)无任何步数检出,快跑段(采样点30~58)的峰值均超过对应动态阈值并被正确计数。若将代码中动态阈值替换为固定阈值1.2g,快跑段低幅值点(如0.75g附近)不会误检,但慢走段峰值(约1.08g)也不会误检——真正差异体现在中等强度过渡场景,本案例通过强度自适应确保阈值始终跟随运动状态变化,避免固定阈值在强度突变时产生漏检或误检。