news 2026/3/26 21:50:56

三维EKF实现无人机高精度悬停控制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
三维EKF实现无人机高精度悬停控制

目录

1. 三维 EKF 设计

状态向量

状态方程(预测)

2. 观测方程(更新)

GPS 观测矩阵:

光流观测矩阵:

气压计观测矩阵:

3. 代码实现(STM32 HAL 库)

4. 集成到无人机悬停控制

5. 参数调优建议


水平位置融合(GPS + 光流)高度融合(气压计 + GPS)整合成一个统一的三维 EKF,这样代码结构更简洁,数据融合也更紧密,精度目标:

  • 水平位置:±0.5 米
  • 高度:±0.2 米

1. 三维 EKF 设计

状态向量

统一状态向量:X=​xyzvx​vy​vz​​​其中:

  • x,y,z:位置(米)
  • vx​,vy​,vz​:速度(米 / 秒)

状态方程(预测)

基于匀速模型:Xk∣k−1​=F⋅Xk−1∣k−1​+wk​状态转移矩阵:F=​100000​010000​001000​dt00100​0dt0010​00dt001​​过程噪声协方差矩阵 Q:Q=​σx2​00000​0σy2​0000​00σz2​000​000σvx​2​00​0000σvy​2​0​00000σvz​2​​​


2. 观测方程(更新)

有三个观测源:

  1. GPS:观测三维位置 (xgps​,ygps​,zgps​)
  2. 光流:观测水平位移 (Δxflow​,Δyflow​) → 转换为速度
  3. 气压计:观测高度 zbaro​

GPS 观测矩阵:

Hgps​=​100​010​001​000​000​000​​观测噪声协方差矩阵 Rgps​:Rgps​=​σgps,x2​00​0σgps,y2​0​00σgps,z2​​​

光流观测矩阵:

光流输出位移 Δxflow​,Δyflow​,转换为速度:vflow,x​=dtΔxflow​​,vflow,y​=dtΔyflow​​观测矩阵:Hflow​=[00​00​00​10​01​00​]观测噪声协方差矩阵 Rflow​:Rflow​=[σflow,x2​0​0σflow,y2​​]

气压计观测矩阵:

Hbaro​=[0​0​1​0​0​0​]观测噪声协方差矩阵 Rbaro​=σbaro2​


3. 代码实现(STM32 HAL 库)

#include <math.h> // 三维 EKF 状态向量 typedef struct { float x, y, z; // 位置 (m) float vx, vy, vz; // 速度 (m/s) } EKF3D_State; // 三维 EKF 协方差矩阵 typedef struct { float P[6][6]; } EKF3D_Covariance; // 传感器数据 typedef struct { float x, y, z; // GPS 位置 (m) } GPS_Data; typedef struct { float dx, dy; // 光流位移 (m) } Flow_Data; typedef struct { float z; // 气压计高度 (m) } Baro_Data; // 三维 EKF 参数 typedef struct { EKF3D_State state; EKF3D_Covariance cov; float dt; // 采样时间 (s) // 过程噪声 float Q_pos; // 位置过程噪声方差 float Q_vel; // 速度过程噪声方差 // 观测噪声 float R_gps_pos; // GPS 位置噪声方差 float R_flow_vel; // 光流速度噪声方差 float R_baro; // 气压计噪声方差 } EKF3D; // 初始化三维 EKF void EKF3D_Init(EKF3D *ekf, float dt) { ekf->dt = dt; ekf->Q_pos = 0.01f; // 位置过程噪声 ekf->Q_vel = 0.1f; // 速度过程噪声 ekf->R_gps_pos = 4.0f; // GPS 位置噪声方差 (2m)^2 ekf->R_flow_vel = 0.01f; // 光流速度噪声方差 (0.1m/s)^2 ekf->R_baro = 0.01f; // 气压计噪声方差 (0.1m)^2 // 初始状态 ekf->state.x = ekf->state.y = ekf->state.z = 0.0f; ekf->state.vx = ekf->state.vy = ekf->state.vz = 0.0f; // 初始协方差 for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { ekf->cov.P[i][j] = 0.0f; } } ekf->cov.P[0][0] = ekf->cov.P[1][1] = ekf->cov.P[2][2] = 1.0f; // 位置不确定度 ekf->cov.P[3][3] = ekf->cov.P[4][4] = ekf->cov.P[5][5] = 1.0f; // 速度不确定度 } // 预测步骤 void EKF3D_Predict(EKF3D *ekf) { // 状态预测 ekf->state.x += ekf->state.vx * ekf->dt; ekf->state.y += ekf->state.vy * ekf->dt; ekf->state.z += ekf->state.vz * ekf->dt; // 状态转移矩阵 F float F[6][6] = { {1, 0, 0, ekf->dt, 0, 0}, {0, 1, 0, 0, ekf->dt, 0}, {0, 0, 1, 0, 0, ekf->dt}, {0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 1, 0}, {0, 0, 0, 0, 0, 1} }; // 过程噪声 Q float Q[6][6] = {0}; Q[0][0] = Q[1][1] = Q[2][2] = ekf->Q_pos; Q[3][3] = Q[4][4] = Q[5][5] = ekf->Q_vel; // P = F*P*F^T + Q float P_temp[6][6]; for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { P_temp[i][j] = 0.0f; for (int k = 0; k < 6; k++) { P_temp[i][j] += F[i][k] * ekf->cov.P[k][j]; } } } for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { ekf->cov.P[i][j] = 0.0f; for (int k = 0; k < 6; k++) { ekf->cov.P[i][j] += P_temp[i][k] * F[j][k]; } ekf->cov.P[i][j] += Q[i][j]; } } } // 更新步骤(GPS) void EKF3D_Update_GPS(EKF3D *ekf, GPS_Data *gps) { float H[3][6] = { {1, 0, 0, 0, 0, 0}, {0, 1, 0, 0, 0, 0}, {0, 0, 1, 0, 0, 0} }; float R[3][3] = { {ekf->R_gps_pos, 0, 0}, {0, ekf->R_gps_pos, 0}, {0, 0, ekf->R_gps_pos} }; float z[3] = {gps->x, gps->y, gps->z}; float z_pred[3] = {ekf->state.x, ekf->state.y, ekf->state.z}; float y[3]; for (int i = 0; i < 3; i++) y[i] = z[i] - z_pred[i]; // S = H*P*H^T + R float S[3][3] = {0}; float H_P[3][6] = {0}; for (int i = 0; i < 3; i++) { for (int j = 0; j < 6; j++) { for (int k = 0; k < 6; k++) { H_P[i][j] += H[i][k] * ekf->cov.P[k][j]; } } } for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { for (int k = 0; k < 6; k++) { S[i][j] += H_P[i][k] * H[j][k]; } S[i][j] += R[i][j]; } } // 卡尔曼增益 K = P*H^T*S^{-1} float S_inv[3][3]; float det = S[0][0]*(S[1][1]*S[2][2]-S[1][2]*S[2][1]) - S[0][1]*(S[1][0]*S[2][2]-S[1][2]*S[2][0]) + S[0][2]*(S[1][0]*S[2][1]-S[1][1]*S[2][0]); S_inv[0][0] = (S[1][1]*S[2][2]-S[1][2]*S[2][1])/det; S_inv[0][1] = (S[0][2]*S[2][1]-S[0][1]*S[2][2])/det; S_inv[0][2] = (S[0][1]*S[1][2]-S[0][2]*S[1][1])/det; S_inv[1][0] = (S[1][2]*S[2][0]-S[1][0]*S[2][2])/det; S_inv[1][1] = (S[0][0]*S[2][2]-S[0][2]*S[2][0])/det; S_inv[1][2] = (S[0][2]*S[1][0]-S[0][0]*S[1][2])/det; S_inv[2][0] = (S[1][0]*S[2][1]-S[1][1]*S[2][0])/det; S_inv[2][1] = (S[0][1]*S[2][0]-S[0][0]*S[2][1])/det; S_inv[2][2] = (S[0][0]*S[1][1]-S[0][1]*S[1][0])/det; float K[6][3] = {0}; for (int i = 0; i < 6; i++) { for (int j = 0; j < 3; j++) { for (int k = 0; k < 6; k++) { K[i][j] += ekf->cov.P[i][k] * H[j][k]; } } } for (int i = 0; i < 6; i++) { for (int j = 0; j < 3; j++) { float temp = 0; for (int k = 0; k < 3; k++) temp += K[i][k] * S_inv[k][j]; K[i][j] = temp; } } // 更新状态 float delta[6] = {0}; for (int i = 0; i < 6; i++) { for (int j = 0; j < 3; j++) delta[i] += K[i][j] * y[j]; } ekf->state.x += delta[0]; ekf->state.y += delta[1]; ekf->state.z += delta[2]; ekf->state.vx += delta[3]; ekf->state.vy += delta[4]; ekf->state.vz += delta[5]; // 更新协方差 P = (I - K*H)*P float I_KH[6][6]; for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { I_KH[i][j] = (i == j) ? 1.0f : 0.0f; for (int k = 0; k < 3; k++) I_KH[i][j] -= K[i][k] * H[k][j]; } } float P_temp[6][6] = {0}; for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { for (int k = 0; k < 6; k++) P_temp[i][j] += I_KH[i][k] * ekf->cov.P[k][j]; } } for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) ekf->cov.P[i][j] = P_temp[i][j]; } } // 更新步骤(光流) void EKF3D_Update_Flow(EKF3D *ekf, Flow_Data *flow) { float vx_flow = flow->dx / ekf->dt; float vy_flow = flow->dy / ekf->dt; float H[2][6] = { {0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 1, 0} }; float R[2][2] = { {ekf->R_flow_vel, 0}, {0, ekf->R_flow_vel} }; float z[2] = {vx_flow, vy_flow}; float z_pred[2] = {ekf->state.vx, ekf->state.vy}; float y[2]; for (int i = 0; i < 2; i++) y[i] = z[i] - z_pred[i]; // S = H*P*H^T + R float S[2][2] = {0}; float H_P[2][6] = {0}; for (int i = 0; i < 2; i++) { for (int j = 0; j < 6; j++) { for (int k = 0; k < 6; k++) H_P[i][j] += H[i][k] * ekf->cov.P[k][j]; } } for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { for (int k = 0; k < 6; k++) S[i][j] += H_P[i][k] * H[j][k]; S[i][j] += R[i][j]; } } // 卡尔曼增益 K = P*H^T*S^{-1} float S_inv[2][2]; float det = S[0][0] * S[1][1] - S[0][1] * S[1][0]; S_inv[0][0] = S[1][1] / det; S_inv[0][1] = -S[0][1] / det; S_inv[1][0] = -S[1][0] / det; S_inv[1][1] = S[0][0] / det; float K[6][2] = {0}; for (int i = 0; i < 6; i++) { for (int j = 0; j < 2; j++) { for (int k = 0; k < 6; k++) K[i][j] += ekf->cov.P[i][k] * H[j][k]; } } for (int i = 0; i < 6; i++) { for (int j = 0; j < 2; j++) { float temp = 0; for (int k = 0; k < 2; k++) temp += K[i][k] * S_inv[k][j]; K[i][j] = temp; } } // 更新状态 float delta[6] = {0}; for (int i = 0; i < 6; i++) { for (int j = 0; j < 2; j++) delta[i] += K[i][j] * y[j]; } ekf->state.x += delta[0]; ekf->state.y += delta[1]; ekf->state.z += delta[2]; ekf->state.vx += delta[3]; ekf->state.vy += delta[4]; ekf->state.vz += delta[5]; // 更新协方差 P = (I - K*H)*P float I_KH[6][6]; for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { I_KH[i][j] = (i == j) ? 1.0f : 0.0f; for (int k = 0; k < 2; k++) I_KH[i][j] -= K[i][k] * H[k][j]; } } float P_temp[6][6] = {0}; for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { for (int k = 0; k < 6; k++) P_temp[i][j] += I_KH[i][k] * ekf->cov.P[k][j]; } } for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) ekf->cov.P[i][j] = P_temp[i][j]; } } // 更新步骤(气压计) void EKF3D_Update_Baro(EKF3D *ekf, Baro_Data *baro) { float H[1][6] = {0, 0, 1, 0, 0, 0}; float R = ekf->R_baro; float z = baro->z; float z_pred = ekf->state.z; float y = z - z_pred; // S = H*P*H^T + R float S = 0; for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { S += H[0][i] * ekf->cov.P[i][j] * H[0][j]; } } S += R; // 卡尔曼增益 K = P*H^T*S^{-1} float K[6]; for (int i = 0; i < 6; i++) { K[i] = 0; for (int j = 0; j < 6; j++) K[i] += ekf->cov.P[i][j] * H[0][j]; K[i] /= S; } // 更新状态 for (int i = 0; i < 6; i++) { if (i == 0) ekf->state.x += K[i] * y; else if (i == 1) ekf->state.y += K[i] * y; else if (i == 2) ekf->state.z += K[i] * y; else if (i == 3) ekf->state.vx += K[i] * y; else if (i == 4) ekf->state.vy += K[i] * y; else if (i == 5) ekf->state.vz += K[i] * y; } // 更新协方差 P = (I - K*H)*P float I_KH[6][6]; for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { I_KH[i][j] = (i == j) ? 1.0f : 0.0f; I_KH[i][j] -= K[i] * H[0][j]; } } float P_temp[6][6] = {0}; for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) { for (int k = 0; k < 6; k++) P_temp[i][j] += I_KH[i][k] * ekf->cov.P[k][j]; } } for (int i = 0; i < 6; i++) { for (int j = 0; j < 6; j++) ekf->cov.P[i][j] = P_temp[i][j]; } }

4. 集成到无人机悬停控制

EKF3D ekf3d; GPS_Data gps; Flow_Data flow; Baro_Data baro; void Hover_Control_Loop(void) { float dt = 0.01f; // 100Hz // 1. 读取传感器数据 read_gps(&gps); // GPS 位置 (m) read_optical_flow(&flow); // 光流位移 (m) read_baro(&baro); // 气压计高度 (m) // 2. 三维 EKF 预测 EKF3D_Predict(&ekf3d); // 3. 三维 EKF 更新 static uint32_t gps_update_count = 0; if (gps_update_count++ >= 100) { // 每秒一次GPS更新 EKF3D_Update_GPS(&ekf3d, &gps); gps_update_count = 0; } EKF3D_Update_Flow(&ekf3d, &flow); // 每次循环更新光流 EKF3D_Update_Baro(&ekf3d, &baro); // 每次循环更新气压计 // 4. 位置环 PID(使用 EKF 输出) pid_pos_x.setpoint = target_pos.x; pid_pos_x.feedback = ekf3d.state.x; PID_Update(&pid_pos_x, dt); pid_pos_y.setpoint = target_pos.y; pid_pos_y.feedback = ekf3d.state.y; PID_Update(&pid_pos_y, dt); pid_pos_z.setpoint = target_pos.z; pid_pos_z.feedback = ekf3d.state.z; PID_Update(&pid_pos_z, dt); // 5. 姿态环与电机控制 ... }

5. 参数调优建议

  • 过程噪声
    • Q_pos = 0.01:位置过程噪声较小,假设无人机运动平稳。
    • Q_vel = 0.1:速度过程噪声较大,允许速度变化。
  • 观测噪声
    • R_gps_pos = 4.0:GPS 噪声方差(2m)。
    • R_flow_vel = 0.01:光流速度噪声方差(0.1m/s)。
    • R_baro = 0.01:气压计噪声方差(0.1m)。

✅ 现在你有了一个统一的三维 EKF,代码简洁且融合紧密,能同时处理:

  • GPS + 光流(水平位置)
  • 气压计 + GPS(高度)

这样无人机悬停精度可以达到:

  • 水平位置:±0.5 米
  • 高度:±0.2 米
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/15 15:31:58

STM32飞控代码:EKF融合+位置姿态PID控制

目录 1. 代码结构总览 2. 完整代码示例&#xff08;STM32 HAL&#xff09; 3. 使用说明 完整可烧录到 STM32 飞控运行的一体化控制代码&#xff0c;包含&#xff1a; 三维 EKF 数据融合&#xff08;GPS 光流 气压计&#xff09;位置 PID 控制&#xff08;X、Y、Z 轴&…

作者头像 李华
网站建设 2026/3/21 21:03:10

YOLOv8 BYOL无需负样本的对比学习

YOLOv8 BYOL&#xff1a;无需负样本的自监督目标检测新范式 在工业质检车间&#xff0c;每天有数以万计的产品流过摄像头&#xff0c;但真正被标注用于训练的数据可能不足百张。标注一张缺陷图需要资深工程师反复确认&#xff0c;耗时几分钟&#xff1b;而采集图像几乎是零成本…

作者头像 李华
网站建设 2026/3/25 15:33:10

YOLOv8 WIoU权重IoU损失函数最新进展

YOLOv8 WIoU权重IoU损失函数最新进展 在工业质检线上&#xff0c;一个微小的焊点缺陷可能被传统检测模型轻易忽略&#xff1b;在高速行驶的自动驾驶场景中&#xff0c;远处行人框预测稍有偏差就可能导致严重后果。这些现实挑战不断推动目标检测技术向更高精度、更强鲁棒性的方向…

作者头像 李华
网站建设 2026/3/25 22:37:42

laravel意庐甜品蛋糕商城网站商城 博客laravel vue

目录具体实现截图项目介绍论文大纲核心代码部分展示可定制开发之亮点部门介绍结论源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作具体实现截图 本系统&#xff08;程序源码数据库调试部署讲解&#xff09;同时还支持Python(flask,django)、…

作者头像 李华
网站建设 2026/3/15 14:02:58

springboot私人口腔牙科医院信息管理系统vue

目录 具体实现截图项目介绍论文大纲核心代码部分展示可定制开发之亮点部门介绍结论源码获取详细视频演示 &#xff1a;文章底部获取博主联系方式&#xff01;同行可合作 具体实现截图 本系统&#xff08;程序源码数据库调试部署讲解&#xff09;同时还支持Python(flask,django…

作者头像 李华
网站建设 2026/3/10 7:50:40

YOLOv8 SNIP尺度归一化图像金字塔应用

YOLOv8 SNIP尺度归一化图像金字塔应用 在自动驾驶系统中&#xff0c;远处的行人可能仅占几个像素&#xff0c;而近处车辆却铺满整个视野&#xff1b;在无人机航拍场景下&#xff0c;同一画面里既有人群也有建筑群。这类极端尺度变化给目标检测带来了巨大挑战——传统模型往往顾…

作者头像 李华