HarmonyOS APP开发:加速度计原理与运动感知

举报
Jack20 发表于 2026/06/21 15:50:39 2026/06/21
【摘要】 HarmonyOS APP开发:加速度计原理与运动感知核心要点:本文深入解析加速度计的物理原理与MEMS实现机制,详解HarmonyOS中加速度计数据获取、坐标系定义、低通/高通滤波算法、运动检测与计步器实现,帮助开发者构建精准的运动感知应用。 一、背景与动机加速度计是移动设备中最基础也最常用的传感器。从屏幕自动旋转到计步器,从游戏体感操控到跌倒检测,加速度计数据支撑着大量核心应用场景。然...

HarmonyOS APP开发:加速度计原理与运动感知

核心要点:本文深入解析加速度计的物理原理与MEMS实现机制,详解HarmonyOS中加速度计数据获取、坐标系定义、低通/高通滤波算法、运动检测与计步器实现,帮助开发者构建精准的运动感知应用。


一、背景与动机

加速度计是移动设备中最基础也最常用的传感器。从屏幕自动旋转到计步器,从游戏体感操控到跌倒检测,加速度计数据支撑着大量核心应用场景。然而,许多开发者对加速度计的理解停留在"获取xyz三个值"的层面,忽视了:

  • 物理原理:加速度计测量的是什么?为什么静止时z轴读数约为9.8?
  • 坐标系定义:HarmonyOS的设备坐标系与世界坐标系如何映射?
  • 数据噪声:原始加速度数据包含大量高频噪声,如何有效滤波?
  • 重力分离:如何从合加速度中分离出重力分量和线性运动分量?
  • 运动识别:如何从加速度时序数据中识别走路、跑步、跳跃等运动模式?

本文将从物理原理出发,逐步深入到算法实现,帮助开发者真正掌握加速度计的应用开发。


二、核心原理

2.1 加速度计物理原理

加速度计的核心原理基于牛顿第二定律(F = ma)和惯性效应。MEMS(微机电系统)加速度计内部包含一个微型质量块,通过弹性结构悬挂在硅基底上。当设备加速时,质量块因惯性产生相对位移,通过电容或压阻检测该位移即可推算出加速度。
图片.png

关键物理概念

  • 加速度计测量的是比力(Specific Force),即物体所受非重力外力与质量之比
  • 静止状态下,加速度计z轴读数约为9.8 m/s²,这是地面支撑力(非重力本身)
  • 自由落体时,加速度计读数为0(失重状态),因为质量块与外壳同步运动无相对位移

2.2 设备坐标系定义

HarmonyOS采用右手坐标系,定义如下:

flowchart LR
    subgraph Coord["设备坐标系(右手系)"]
        direction TB
        X["X轴 → 水平向右"]
        Y["Y轴 → 垂直向上"]
        Z["Z轴 → 屏幕正面朝外"]
    end

    subgraph Gravity["静止时重力分量"]
        direction TB
        G1["竖屏静止: gx≈0, gy≈9.8, gz≈0"]
        G2["横屏静止: gx≈9.8, gy≈0, gz≈0"]
        G3["平放桌面: gx≈0, gy≈0, gz≈9.8"]
    end

    Coord --- Gravity

    classDef coordStyle fill:#4A90D9,stroke:#2C5F8A,color:#fff,font-weight:bold
    classDef gravStyle fill:#50C878,stroke:#2E8B57,color:#fff,font-weight:bold

    class X,Y,Z coordStyle
    class G1,G2,G3 gravStyle
设备姿态 gx (m/s²) gy (m/s²) gz (m/s²)
竖屏正面朝上 ≈0 ≈9.8 ≈0
横屏正面朝上 ≈9.8 ≈0 ≈0
平放桌面 ≈0 ≈0 ≈9.8
竖屏倒置 ≈0 ≈-9.8 ≈0
自由落体 ≈0 ≈0 ≈0

2.3 重力与线性加速度分离

加速度计原始数据 = 重力分量 + 线性运动分量。分离两者是运动感知的关键:

a_total = a_gravity + a_linear
a_linear = a_total - a_gravity

重力分量可通过低通滤波提取(因为重力是低频恒定信号),线性运动分量则通过高通滤波获取。


三、代码实战

3.1 加速度计数据获取与实时可视化

// accelerometer_visual.ets
// 功能:加速度计数据获取与实时波形可视化
import sensor from '@ohos.sensor';
import { BusinessError } from '@ohos.base';

interface AccelSample {
  x: number;
  y: number;
  z: number;
  magnitude: number; // 合加速度
  timestamp: number;
}

@Entry
@Component
struct AccelerometerVisualPage {
  @State samples: AccelSample[] = [];
  @State currentData: AccelSample = { x: 0, y: 0, z: 0, magnitude: 0, timestamp: 0 };
  @State isRunning: boolean = false;
  @State gravityX: number = 0;
  @State gravityY: number = 0;
  @State gravityZ: number = 0;

  private maxSamples: number = 200; // 保留最近200个采样点
  private accelCallback: (data: sensor.AccelerometerResponse) => void = () => {};
  // 低通滤波系数(α越小,滤波越平滑,延迟越大)
  private alpha: number = 0.8;

  aboutToAppear() {
    this.initGravity();
  }

  aboutToDisappear() {
    this.stopAccelerometer();
  }

  // 功能:初始化重力分量估计值
  initGravity() {
    this.gravityX = 0;
    this.gravityY = 0;
    this.gravityZ = 9.8; // 假设设备平放
  }

  // 功能:启动加速度计订阅
  startAccelerometer() {
    try {
      this.accelCallback = (data: sensor.AccelerometerResponse) => {
        // 低通滤波提取重力分量
        this.gravityX = this.alpha * this.gravityX + (1 - this.alpha) * data.x;
        this.gravityY = this.alpha * this.gravityY + (1 - this.alpha) * data.y;
        this.gravityZ = this.alpha * this.gravityZ + (1 - this.alpha) * data.z;

        // 计算线性加速度(去除重力)
        const linearX = data.x - this.gravityX;
        const linearY = data.y - this.gravityY;
        const linearZ = data.z - this.gravityZ;

        // 计算合加速度
        const magnitude = Math.sqrt(
          linearX * linearX + linearY * linearY + linearZ * linearZ
        );

        const sample: AccelSample = {
          x: parseFloat(linearX.toFixed(3)),
          y: parseFloat(linearY.toFixed(3)),
          z: parseFloat(linearZ.toFixed(3)),
          magnitude: parseFloat(magnitude.toFixed(3)),
          timestamp: data.timestamp
        };

        this.currentData = sample;
        this.samples = [...this.samples, sample].slice(-this.maxSamples);
      };

      sensor.on(sensor.SensorType.ACCELEROMETER, this.accelCallback, {
        interval: 'game' // 50Hz高频采样
      });

      this.isRunning = true;
    } catch (error) {
      const err = error as BusinessError;
      console.error(`[AccelVisual] 启动失败: ${err.code} - ${err.message}`);
    }
  }

  // 功能:停止加速度计订阅
  stopAccelerometer() {
    if (!this.isRunning) return;
    try {
      sensor.off(sensor.SensorType.ACCELEROMETER, this.accelCallback);
      this.isRunning = false;
    } catch (error) {
      console.error(`[AccelVisual] 停止失败: ${JSON.stringify(error)}`);
    }
  }

  build() {
    Scroll() {
      Column() {
        // 标题
        Text('加速度计实时监测')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 16 })

        // 当前数据卡片
        this.CurrentDataCard()

        // 重力分量显示
        this.GravityCard()

        // 控制按钮
        Row() {
          Button(this.isRunning ? '停止监测' : '开始监测')
            .backgroundColor(this.isRunning ? '#FF6B6B' : '#50C878')
            .fontColor('#ffffff')
            .onClick(() => {
              if (this.isRunning) {
                this.stopAccelerometer();
              } else {
                this.startAccelerometer();
              }
            })
            .width('60%')
        }
        .width('100%')
        .justifyContent(FlexAlign.Center)
        .margin({ top: 16 })

        // 采样历史
        Text(`采样点数: ${this.samples.length}`)
          .fontSize(14)
          .fontColor('#888888')
          .margin({ top: 12 })
      }
      .width('100%')
      .padding(20)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#0d0d1a')
  }

  @Builder
  CurrentDataCard() {
    Column() {
      Text('线性加速度(去重力)')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .margin({ bottom: 12 })

      Row() {
        this.AxisDisplay('X', this.currentData.x, '#FF6B6B')
        this.AxisDisplay('Y', this.currentData.y, '#4ECDC4')
        this.AxisDisplay('Z', this.currentData.z, '#45B7D1')
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceAround)

      // 合加速度显示
      Row() {
        Text('合加速度 |a| = ')
          .fontSize(16)
          .fontColor('#cccccc')
        Text(`${this.currentData.magnitude}`)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FFD93D')
        Text(' m/s²')
          .fontSize(14)
          .fontColor('#888888')
      }
      .margin({ top: 12 })
    }
    .width('100%')
    .padding(20)
    .borderRadius(16)
    .backgroundColor('#1a1a2e')
    .margin({ bottom: 12 })
  }

  @Builder
  GravityCard() {
    Column() {
      Text('重力分量(低通滤波)')
        .fontSize(16)
        .fontWeight(FontWeight.Medium)
        .margin({ bottom: 8 })

      Row() {
        Text(`gx: ${this.gravityX.toFixed(2)}`)
          .fontSize(14)
          .fontColor('#FF6B6B')
          .layoutWeight(1)
        Text(`gy: ${this.gravityY.toFixed(2)}`)
          .fontSize(14)
          .fontColor('#4ECDC4')
          .layoutWeight(1)
        Text(`gz: ${this.gravityZ.toFixed(2)}`)
          .fontSize(14)
          .fontColor('#45B7D1')
          .layoutWeight(1)
      }
      .width('100%')
    }
    .width('100%')
    .padding(16)
    .borderRadius(12)
    .backgroundColor('#1a1a2e')
    .margin({ bottom: 12 })
  }

  @Builder
  AxisDisplay(label: string, value: number, color: string) {
    Column() {
      Text(label)
        .fontSize(14)
        .fontColor(color)
      Text(`${value}`)
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
        .fontColor('#ffffff')
        .margin({ top: 4 })
      Text('m/s²')
        .fontSize(11)
        .fontColor('#888888')
    }
    .alignItems(HorizontalAlign.Center)
  }
}

3.2 低通滤波与高通滤波算法实现

// accel_filter.ets
// 功能:加速度计数据滤波算法库

// ============ 一阶低通滤波器(提取重力分量)============
// 原理:重力是低频恒定信号,低通滤波可以平滑提取
// 公式: gravity[n] = α * gravity[n-1] + (1-α) * input[n]
// α 越大,滤波越平滑,但延迟越大
export class LowPassFilter {
  private alpha: number;
  private output: number = 0;
  private initialized: boolean = false;

  constructor(cutoffFrequency: number, sampleRate: number) {
    // 根据截止频率和采样率计算α
    const rc = 1 / (2 * Math.PI * cutoffFrequency);
    const dt = 1 / sampleRate;
    this.alpha = rc / (rc + dt);
  }

  // 功能:处理单个采样值
  process(input: number): number {
    if (!this.initialized) {
      this.output = input;
      this.initialized = true;
      return this.output;
    }
    this.output = this.alpha * this.output + (1 - this.alpha) * input;
    return this.output;
  }

  // 功能:重置滤波器状态
  reset() {
    this.output = 0;
    this.initialized = false;
  }
}

// ============ 一阶高通滤波器(提取线性运动)============
// 原理:从合加速度中去除低频重力分量,保留高频运动信号
// 公式: output[n] = α * (output[n-1] + input[n] - input[n-1])
export class HighPassFilter {
  private alpha: number;
  private output: number = 0;
  private prevInput: number = 0;
  private initialized: boolean = false;

  constructor(cutoffFrequency: number, sampleRate: number) {
    const rc = 1 / (2 * Math.PI * cutoffFrequency);
    const dt = 1 / sampleRate;
    this.alpha = rc / (rc + dt);
  }

  process(input: number): number {
    if (!this.initialized) {
      this.prevInput = input;
      this.initialized = true;
      return 0;
    }
    this.output = this.alpha * (this.output + input - this.prevInput);
    this.prevInput = input;
    return this.output;
  }

  reset() {
    this.output = 0;
    this.prevInput = 0;
    this.initialized = false;
  }
}

// ============ 均值滤波器(滑动窗口平滑)============
// 原理:对最近N个采样值取平均,消除随机噪声
export class MovingAverageFilter {
  private window: number[] = [];
  private windowSize: number;

  constructor(windowSize: number = 5) {
    this.windowSize = windowSize;
  }

  process(input: number): number {
    this.window.push(input);
    if (this.window.length > this.windowSize) {
      this.window.shift();
    }
    const sum = this.window.reduce((acc, val) => acc + val, 0);
    return sum / this.window.length;
  }

  reset() {
    this.window = [];
  }
}

// ============ 综合滤波管道 ============
// 功能:将多个滤波器串联,实现重力分离+噪声消除
export class AccelFilterPipeline {
  // 低通滤波器提取重力(截止频率0.5Hz,假设采样率50Hz)
  private lpfX: LowPassFilter = new LowPassFilter(0.5, 50);
  private lpfY: LowPassFilter = new LowPassFilter(0.5, 50);
  private lpfZ: LowPassFilter = new LowPassFilter(0.5, 50);

  // 均值滤波器平滑线性加速度
  private mafX: MovingAverageFilter = new MovingAverageFilter(3);
  private mafY: MovingAverageFilter = new MovingAverageFilter(3);
  private mafZ: MovingAverageFilter = new MovingAverageFilter(3);

  // 功能:处理三轴加速度数据
  process(x: number, y: number, z: number): {
    gravity: { x: number; y: number; z: number };
    linear: { x: number; y: number; z: number };
    smoothed: { x: number; y: number; z: number };
  } {
    // 步骤1:低通滤波提取重力
    const gx = this.lpfX.process(x);
    const gy = this.lpfY.process(y);
    const gz = this.lpfZ.process(z);

    // 步骤2:减去重力得到线性加速度
    const lx = x - gx;
    const ly = y - gy;
    const lz = z - gz;

    // 步骤3:均值滤波平滑线性加速度
    const sx = this.mafX.process(lx);
    const sy = this.mafY.process(ly);
    const sz = this.mafZ.process(lz);

    return {
      gravity: { x: gx, y: gy, z: gz },
      linear: { x: lx, y: ly, z: lz },
      smoothed: { x: sx, y: sy, z: sz }
    };
  }

  reset() {
    this.lpfX.reset(); this.lpfY.reset(); this.lpfZ.reset();
    this.mafX.reset(); this.mafY.reset(); this.mafZ.reset();
  }
}

3.3 运动检测与计步器算法

// pedometer_algorithm.ets
// 功能:基于加速度计的计步器算法实现
import sensor from '@ohos.sensor';

// 计步器状态
enum StepState {
  IDLE,       // 静止
  WALKING,    // 行走中
  RUNNING     // 跑步中
}

// 步态检测参数
interface PedometerConfig {
  walkThreshold: number;    // 行走检测阈值(m/s²)
  runThreshold: number;     // 跑步检测阈值(m/s²)
  minStepInterval: number;  // 最小步间隔(ms)
  maxStepInterval: number;  // 最大步间隔(ms)
  windowSize: number;       // 滑动窗口大小
}

// 默认参数(经过实测调优)
const DEFAULT_CONFIG: PedometerConfig = {
  walkThreshold: 1.5,
  runThreshold: 3.0,
  minStepInterval: 250,   // 最快每秒4步
  maxStepInterval: 2000,  // 最慢每2秒1步
  windowSize: 5
};

export class PedometerAlgorithm {
  private config: PedometerConfig;
  private state: StepState = StepState.IDLE;
  private stepCount: number = 0;
  private lastStepTime: number = 0;
  private magnitudeWindow: number[] = [];
  private accelCallback: (data: sensor.AccelerometerResponse) => void = () => {};
  private isSubscribed: boolean = false;

  // 滤波管道
  private filterPipeline: AccelFilterPipeline = new AccelFilterPipeline();

  constructor(config?: Partial<PedometerConfig>) {
    this.config = { ...DEFAULT_CONFIG, ...config };
  }

  // 功能:获取当前步数
  getStepCount(): number {
    return this.stepCount;
  }

  // 功能:获取当前运动状态
  getMotionState(): string {
    switch (this.state) {
      case StepState.IDLE: return '静止';
      case StepState.WALKING: return '行走';
      case StepState.RUNNING: return '跑步';
      default: return '未知';
    }
  }

  // 功能:启动计步器
  start(onStepUpdate?: (count: number, state: string) => void) {
    if (this.isSubscribed) return;

    this.accelCallback = (data: sensor.AccelerometerResponse) => {
      // 滤波处理
      const filtered = this.filterPipeline.process(data.x, data.y, data.z);

      // 计算线性加速度合值
      const mag = Math.sqrt(
        filtered.smoothed.x ** 2 +
        filtered.smoothed.y ** 2 +
        filtered.smoothed.z ** 2
      );

      // 加入滑动窗口
      this.magnitudeWindow.push(mag);
      if (this.magnitudeWindow.length > this.config.windowSize) {
        this.magnitudeWindow.shift();
      }

      // 峰值检测算法
      this.detectStep(mag, data.timestamp, onStepUpdate);
    };

    try {
      sensor.on(sensor.SensorType.ACCELEROMETER, this.accelCallback, {
        interval: 'game'
      });
      this.isSubscribed = true;
    } catch (error) {
      console.error(`[Pedometer] 启动失败: ${JSON.stringify(error)}`);
    }
  }

  // 功能:停止计步器
  stop() {
    if (!this.isSubscribed) return;
    try {
      sensor.off(sensor.SensorType.ACCELEROMETER, this.accelCallback);
      this.isSubscribed = false;
    } catch (error) {
      console.error(`[Pedometer] 停止失败: ${JSON.stringify(error)}`);
    }
  }

  // 功能:重置计步器
  reset() {
    this.stepCount = 0;
    this.state = StepState.IDLE;
    this.lastStepTime = 0;
    this.magnitudeWindow = [];
    this.filterPipeline.reset();
  }

  // 功能:峰值检测算法 - 检测一步
  private detectStep(
    magnitude: number,
    timestamp: number,
    onStepUpdate?: (count: number, state: string) => void
  ) {
    const now = timestamp / 1_000_000; // 纳秒转毫秒
    const timeSinceLastStep = now - this.lastStepTime;

    // 判断运动状态
    if (magnitude > this.config.runThreshold) {
      this.state = StepState.RUNNING;
    } else if (magnitude > this.config.walkThreshold) {
      this.state = StepState.WALKING;
    } else {
      this.state = StepState.IDLE;
      return;
    }

    // 检查步间隔是否合法
    if (this.lastStepTime > 0) {
      if (timeSinceLastStep < this.config.minStepInterval) {
        return; // 太快,可能是噪声
      }
      if (timeSinceLastStep > this.config.maxStepInterval) {
        // 太慢,可能已停止运动,重新计数
        this.lastStepTime = now;
        this.stepCount++;
        onStepUpdate?.(this.stepCount, this.getMotionState());
        return;
      }
    }

    // 确认有效步伐
    this.stepCount++;
    this.lastStepTime = now;
    onStepUpdate?.(this.stepCount, this.getMotionState());
  }
}

// 需要引入的滤波类(简化引用,实际应从accel_filter.ets导入)
class AccelFilterPipeline {
  private lpfX: LowPassFilter = new LowPassFilter(0.5, 50);
  private lpfY: LowPassFilter = new LowPassFilter(0.5, 50);
  private lpfZ: LowPassFilter = new LowPassFilter(0.5, 50);
  private mafX: MovingAverageFilter = new MovingAverageFilter(3);
  private mafY: MovingAverageFilter = new MovingAverageFilter(3);
  private mafZ: MovingAverageFilter = new MovingAverageFilter(3);

  process(x: number, y: number, z: number) {
    const gx = this.lpfX.process(x);
    const gy = this.lpfY.process(y);
    const gz = this.lpfZ.process(z);
    const lx = x - gx; const ly = y - gy; const lz = z - gz;
    const sx = this.mafX.process(lx);
    const sy = this.mafY.process(ly);
    const sz = this.mafZ.process(lz);
    return {
      gravity: { x: gx, y: gy, z: gz },
      linear: { x: lx, y: ly, z: lz },
      smoothed: { x: sx, y: sy, z: sz }
    };
  }

  reset() {
    this.lpfX.reset(); this.lpfY.reset(); this.lpfZ.reset();
    this.mafX.reset(); this.mafY.reset(); this.mafZ.reset();
  }
}

3.4 跌倒检测算法

// fall_detection.ets
// 功能:基于加速度计的跌倒检测算法
import sensor from '@ohos.sensor';

// 跌倒检测状态机
enum FallState {
  NORMAL,          // 正常状态
  FREE_FALL,       // 自由落体检测
  IMPACT,          // 撞击检测
  POST_FALL_STILL, // 跌倒后静止
  FALL_DETECTED    // 确认跌倒
}

// 跌倒检测参数
interface FallDetectionConfig {
  freeFallThreshold: number;    // 自由落体阈值(m/s²),低于此值判定为失重
  freeFallDuration: number;     // 自由落体最短持续时间(ms)
  impactThreshold: number;      // 撞击阈值(m/s²),高于此值判定为撞击
  impactDuration: number;       // 撞击检测窗口(ms)
  stillThreshold: number;       // 静止阈值(m/s²)
  stillDuration: number;        // 跌倒后静止持续时间(ms)
}

const FALL_CONFIG: FallDetectionConfig = {
  freeFallThreshold: 2.0,    // 合加速度 < 2.0 m/s²
  freeFallDuration: 100,     // 持续 100ms
  impactThreshold: 20.0,     // 合加速度 > 20.0 m/s²
  impactDuration: 500,       // 撞击后500ms内检测
  stillThreshold: 0.5,       // 线性加速度 < 0.5 m/s²
  stillDuration: 3000        // 静止持续3秒
};

export class FallDetector {
  private config: FallDetectionConfig;
  private state: FallState = FallState.NORMAL;
  private freeFallStart: number = 0;
  private impactTime: number = 0;
  private stillStart: number = 0;
  private accelCallback: (data: sensor.AccelerometerResponse) => void = () => {};
  private isRunning: boolean = false;
  private onFallDetected?: () => void;

  constructor(config?: Partial<FallDetectionConfig>) {
    this.config = { ...FALL_CONFIG, ...config };
  }

  // 功能:启动跌倒检测
  start(onFallDetected: () => void) {
    if (this.isRunning) return;
    this.onFallDetected = onFallDetected;

    this.accelCallback = (data: sensor.AccelerometerResponse) => {
      const magnitude = Math.sqrt(data.x ** 2 + data.y ** 2 + data.z ** 2);
      const now = Date.now();

      this.processState(magnitude, now);
    };

    try {
      sensor.on(sensor.SensorType.ACCELEROMETER, this.accelCallback, {
        interval: 5000 // 200Hz高频采样,确保不遗漏撞击
      });
      this.isRunning = true;
    } catch (error) {
      console.error(`[FallDetector] 启动失败: ${JSON.stringify(error)}`);
    }
  }

  // 功能:停止跌倒检测
  stop() {
    if (!this.isRunning) return;
    try {
      sensor.off(sensor.SensorType.ACCELEROMETER, this.accelCallback);
      this.isRunning = false;
      this.state = FallState.NORMAL;
    } catch (error) {
      console.error(`[FallDetector] 停止失败: ${JSON.stringify(error)}`);
    }
  }

  // 功能:状态机处理
  private processState(magnitude: number, now: number) {
    switch (this.state) {
      case FallState.NORMAL:
        // 检测自由落体(失重)
        if (magnitude < this.config.freeFallThreshold) {
          this.freeFallStart = now;
          this.state = FallState.FREE_FALL;
          console.info('[FallDetector] 检测到自由落体');
        }
        break;

      case FallState.FREE_FALL:
        // 确认自由落体持续时间
        if (magnitude < this.config.freeFallThreshold) {
          const duration = now - this.freeFallStart;
          if (duration >= this.config.freeFallDuration) {
            // 自由落体确认,进入撞击检测阶段
            this.state = FallState.IMPACT;
            console.info('[FallDetector] 自由落体确认,等待撞击');
          }
        } else {
          // 短暂失重后恢复,非跌倒
          this.state = FallState.NORMAL;
        }
        break;

      case FallState.IMPACT:
        // 检测撞击
        if (magnitude > this.config.impactThreshold) {
          this.impactTime = now;
          this.state = FallState.POST_FALL_STILL;
          console.info('[FallDetector] 检测到撞击,等待静止确认');
        } else if (now - this.freeFallStart > this.config.impactDuration) {
          // 超时未检测到撞击,恢复正常
          this.state = FallState.NORMAL;
        }
        break;

      case FallState.POST_FALL_STILL:
        // 检测跌倒后静止
        if (magnitude < 9.8 + this.config.stillThreshold &&
            magnitude > 9.8 - this.config.stillThreshold) {
          if (this.stillStart === 0) {
            this.stillStart = now;
          }
          const stillDuration = now - this.stillStart;
          if (stillDuration >= this.config.stillDuration) {
            // 确认跌倒!
            this.state = FallState.FALL_DETECTED;
            console.warn('[FallDetector] ⚠️ 跌倒已确认!');
            this.onFallDetected?.();
            // 重置状态
            setTimeout(() => {
              this.state = FallState.NORMAL;
              this.stillStart = 0;
            }, 10000); // 10秒后重新检测
          }
        } else {
          // 有运动,可能未跌倒
          this.stillStart = 0;
          if (now - this.impactTime > this.config.stillDuration * 2) {
            this.state = FallState.NORMAL;
          }
        }
        break;

      case FallState.FALL_DETECTED:
        // 等待重置
        break;
    }
  }
}

四、踩坑与注意事项

4.1 加速度计数据精度问题

问题 原因 解决方案
静止时数据抖动 传感器噪声、量化误差 低通滤波或均值滤波
z轴偏移不为9.8 传感器零偏校准不准 启动时采集N个点取均值作为偏移补偿
不同设备数据差异大 传感器型号、量程不同 使用相对变化量而非绝对值
温度漂移 MEMS受温度影响 运行时动态校准零偏

4.2 滤波器参数选择指南

flowchart TD
    A[选择滤波策略] --> B{应用场景?}
    B -->|实时性要求高| C[一阶低通<br/>α=0.8~0.95]
    B -->|精度要求高| D[二阶Butterworth<br/>截止0.5~1Hz]
    B -->|简单快速| E[均值滤波<br/>窗口3~7]

    C --> F[延迟: 50~200ms]
    D --> G[延迟: 100~500ms]
    E --> H[延迟: 窗口×采样间隔]

    classDef decisionStyle fill:#FF8C42,stroke:#CC6B30,color:#fff,font-weight:bold
    classDef lpfStyle fill:#4A90D9,stroke:#2C5F8A,color:#fff,font-weight:bold
    classDef btfStyle fill:#9B59B6,stroke:#7D3C98,color:#fff,font-weight:bold
    classDef avgStyle fill:#50C878,stroke:#2E8B57,color:#fff,font-weight:bold

    class B decisionStyle
    class C,F lpfStyle
    class D,G btfStyle
    class E,H avgStyle

4.3 计步器算法优化要点

  1. 峰值检测 vs 过零检测:峰值检测更鲁棒,过零检测更简单但易受噪声干扰
  2. 自适应阈值:根据用户历史步态动态调整检测阈值
  3. 步频约束:正常人步频在60~130步/分钟,超出范围大概率是误检
  4. 多传感器融合:结合陀螺仪数据可显著降低误检率
  5. 使用系统计步器:HarmonyOS提供@ohos.sensorPEDOMETER类型,系统级优化更省电

4.4 跌倒检测误报处理

  • 剧烈运动误报:跑步、跳跃也会产生失重+撞击+静止序列,需结合心率或运动模式判断
  • 手机放置误报:手机从桌上滑落也会触发,需结合接近传感器判断是否贴身
  • 建议:跌倒检测仅作为辅助提醒,不应作为唯一医疗判断依据

五、HarmonyOS 6适配

5.1 线性加速度传感器直接获取

HarmonyOS 6增强了LINEAR_ACCELERATION传感器支持,可直接获取去重力后的线性加速度,无需手动滤波:

// harmonyos6_linear_accel.ets
// 功能:HarmonyOS 6直接获取线性加速度
import sensor from '@ohos.sensor';

// 方式1:直接订阅线性加速度传感器(推荐)
// 系统内部已完成重力分离,数据更精准
function subscribeLinearAcceleration() {
  try {
    sensor.on(sensor.SensorType.LINEAR_ACCELERATION, (data: sensor.LinearAccelerometerResponse) => {
      console.info(`[HOS6] 线性加速度: x=${data.x}, y=${data.y}, z=${data.z}`);
      // data.x/y/z 已经是去除重力后的线性加速度
    }, { interval: 'game' });
  } catch (error) {
    console.error(`[HOS6] 线性加速度订阅失败: ${JSON.stringify(error)}`);
  }
}

// 方式2:同时订阅加速度计和重力传感器,手动分离
function subscribeWithGravitySeparation() {
  let gravityData = { x: 0, y: 0, z: 0 };

  // 先订阅重力传感器获取精确重力分量
  sensor.on(sensor.SensorType.GRAVITY, (data: sensor.GravityResponse) => {
    gravityData = { x: data.x, y: data.y, z: data.z };
  }, { interval: 'game' });

  // 再订阅加速度计,减去重力得到线性加速度
  sensor.on(sensor.SensorType.ACCELEROMETER, (data: sensor.AccelerometerResponse) => {
    const linearX = data.x - gravityData.x;
    const linearY = data.y - gravityData.y;
    const linearZ = data.z - gravityData.z;
    console.info(`[HOS6] 手动分离: x=${linearX.toFixed(3)}, y=${linearY.toFixed(3)}, z=${linearZ.toFixed(3)}`);
  }, { interval: 'game' });
}

5.2 系统计步器API

HarmonyOS 6推荐使用系统计步器,功耗更低、精度更高:

// harmonyos6_pedometer.ets
import sensor from '@ohos.sensor';

// 使用系统计步器传感器
function useSystemPedometer() {
  try {
    sensor.on(sensor.SensorType.PEDOMETER, (data: sensor.PedometerResponse) => {
      console.info(`[HOS6] 系统计步: ${data.steps}`);
    }, { interval: 'normal' });
  } catch (error) {
    console.error(`[HOS6] 系统计步器不可用: ${JSON.stringify(error)}`);
  }
}

六、总结

本文从加速度计的物理原理出发,系统讲解了运动感知应用的开发方法:

flowchart TB
    A[加速度计开发路径] --> B[物理原理<br/>MEMS惯性检测]
    B --> C[数据获取<br/>sensor.on订阅]
    C --> D[滤波处理<br/>低通+高通+均值]
    D --> E{应用方向}
    E --> F[运动检测<br/>计步器/运动识别]
    E --> G[姿态估计<br/>设备朝向判断]
    E --> H[安全监测<br/>跌倒检测]

    classDef rootStyle fill:#4A90D9,stroke:#2C5F8A,color:#fff,font-weight:bold
    classDef stepStyle fill:#50C878,stroke:#2E8B57,color:#fff,font-weight:bold
    classDef branchStyle fill:#FF8C42,stroke:#CC6B30,color:#fff,font-weight:bold
    classDef leafStyle fill:#9B59B6,stroke:#7D3C98,color:#fff,font-weight:bold

    class A rootStyle
    class B,C,D stepStyle
    class E branchStyle
    class F,G,H leafStyle

核心要点回顾

  1. 物理本质:加速度计测量的是比力而非加速度,静止时z轴≈9.8是支撑力而非重力
  2. 重力分离:低通滤波提取重力,减去重力得到线性运动,是运动感知的基础
  3. 滤波选择:根据实时性和精度需求选择合适的滤波策略和参数
  4. 计步算法:峰值检测+步频约束+自适应阈值是可靠计步的关键
  5. 跌倒检测:自由落体→撞击→静止的三阶段状态机是经典方案
  6. HOS6优化:优先使用LINEAR_ACCELERATION和系统计步器,减少手动计算

下一篇将深入陀螺仪原理与姿态角计算,探索三维空间中的旋转感知。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。