Android传感器融合实战:从姿态解算到行人航位推算
1. Android传感器融合基础入门第一次接触传感器融合时我也被各种专业术语绕晕了。简单来说就是把手机里加速度计、陀螺仪这些传感器的数据拌在一起就像做菜时把不同调料混合最终得到更准确的结果。举个例子单用加速度计测量手机倾斜角度时手部抖动会产生误差而陀螺仪虽然对旋转敏感但会随时间产生漂移。把它们的数据融合后就能互相弥补不足。现代Android手机通常配备以下核心传感器加速度计测量三轴线性加速度单位m/s²陀螺仪检测三轴角速度单位rad/s磁力计感知地磁场方向单位μT在代码中获取传感器实例很简单SensorManager manager (SensorManager) getSystemService(SENSOR_SERVICE); Sensor accelerometer manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); Sensor gyroscope manager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);但要注意采样率设置。实测发现在步行检测场景中SENSOR_DELAY_GAME20ms间隔能平衡功耗和精度SENSOR_DELAY_FASTEST0ms虽然数据密集但会导致手机明显发热2. 传感器数据采集与预处理数据采集是整个过程的地基。我曾遇到一个坑不同厂商手机的传感器坐标系不一致。比如华为手机的Y轴加速度方向与小米相反必须统一转换到东北天坐标系ENU才能使用。完整的数据采集流程应包括注册传感器监听器添加高精度时间戳System.nanoTime()数据归一化处理通过TCP/UDP实时传输到PC端这里分享一个实用的数据封装方法public class SensorData { long timestamp; // 纳秒级时间戳 float[] values; // 三轴数据 int type; // 传感器类型 String toCSV() { return timestamp , type , values[0] , values[1] , values[2]; } }在电脑端用Python接收数据的示例import socket server socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind((0.0.0.0, 8086)) conn, addr server.accept() while True: data conn.recv(1024).decode(utf-8) if data: timestamp, sensor_type, x, y, z data.split(,) # 后续处理...3. 姿态解算实战从理论到代码姿态解算的核心是求出手机的三个欧拉角俯仰角Pitch手机上下倾斜角度滚转角Roll手机左右侧翻角度航向角Yaw手机指向的方位角最初我尝试用简单的互补滤波算法# 简易互补滤波实现 def complementary_filter(accel_data, gyro_data, dt): # 加速度计计算的角度 acc_pitch atan2(accel_data[1], accel_data[2]) acc_roll atan2(-accel_data[0], sqrt(accel_data[1]**2 accel_data[2]**2)) # 与陀螺仪数据融合 pitch 0.98 * (pitch gyro_data[0] * dt) 0.02 * acc_pitch roll 0.98 * (roll gyro_data[1] * dt) 0.02 * acc_roll return pitch, roll但实测发现当手机快速移动时误差很大。后来改用扩展卡尔曼滤波EKF精度提升明显。EKF的核心步骤包括状态预测基于陀螺仪数据测量更新融合加速度计和磁力计数据协方差矩阵迭代一个简化版的EKF实现框架class EKF: def __init__(self): self.Q eye(4) * 0.01 # 过程噪声 self.R eye(4) * 0.1 # 观测噪声 self.P eye(4) # 协方差矩阵 def predict(self, gyro_data, dt): # 状态转移矩阵构建 F build_state_transition_matrix(gyro_data, dt) # 状态预测 self.x dot(F, self.x) # 协方差预测 self.P dot(dot(F, self.P), F.T) self.Q def update(self, accel_data, mag_data): # 观测模型构建 H build_observation_matrix() # 卡尔曼增益计算 K dot(dot(self.P, H.T), inv(dot(dot(H, self.P), H.T) self.R)) # 状态更新 z get_measurement_vector(accel_data, mag_data) self.x self.x dot(K, (z - dot(H, self.x))) # 协方差更新 self.P dot((eye(4) - dot(K, H)), self.P)4. 行人航位推算(PDR)完整实现有了准确的姿态数据就可以开始轨迹推算。行人航位推算主要包含三个核心环节4.1 步态检测通过分析加速度幅值变化检测步伐def detect_step(accel_norm, threshold1.2, window_size10): # 滑动窗口均值滤波 avg_accel np.convolve(accel_norm, np.ones(window_size)/window_size, modevalid) # 寻找波峰 peaks, _ find_peaks(avg_accel, heightthreshold) return len(peaks) # 返回步数实测发现将Z轴加速度与总加速度结合分析能减少误检accel_norm np.linalg.norm([ax, ay, az]) vertical_accel az - 9.8 # 去除重力影响 combined_signal 0.7*accel_norm 0.3*abs(vertical_accel)4.2 步长估计基于身高和步频的动态模型效果较好def estimate_stride(height_cm, step_freq): # 经验公式步长与身高、步频的关系 return height_cm * (0.415 0.0026*step_freq) / 100 # 转换为米4.3 航向融合融合陀螺仪和磁力计数据是关键。这里有个实用技巧当检测到手机处于口袋中时自动切换为磁力计主导模式if is_in_pocket(accel_data): yaw magnetometer_yaw * 0.9 gyro_yaw * 0.1 else: yaw magnetometer_yaw * 0.3 gyro_yaw * 0.7完整的轨迹推算流程检测到步伐时触发位置更新根据当前航向和步长计算位移累加位移得到轨迹positions [(0, 0)] # 初始位置 for step in steps: dx stride_length * sin(yaw) dy stride_length * cos(yaw) new_pos (positions[-1][0] dx, positions[-1][1] dy) positions.append(new_pos)5. 误差分析与优化技巧在商场实测时累计误差曾达到每10米偏移2-3米。通过分析发现主要误差源包括陀螺仪漂移每小时会产生5-10度的偏差磁力计干扰在电梯、自动扶梯处误差突增步长变异上下楼梯时步长变化明显优化方案效果对比优化措施误差降低幅度计算开销增加零速修正(ZUPT)40%低地磁校准25%中步频自适应15%低特别推荐零速修正技术当检测到脚部落地的静止瞬间通过加速度和角速度判断重置速度为零def detect_stationary(gyro_norm, accel_norm, threshold0.2): return gyro_norm threshold and abs(accel_norm - 9.8) 0.5 if detect_stationary(gyro_data, accel_data): velocity [0, 0, 0] # 速度归零另一个实用技巧是引入地标辅助当识别到电梯、楼梯等固定特征时进行位置校正。这需要预先建立场地特征地图。