图优化在GNSS/INS/OD融合定位中的应用【附代码】

博主简介:擅长数据搜集与处理、建模仿真、程序设计、仿真代码、论文写作与指导,毕业论文、期刊论文经验交流。

 ✅ 具体问题可以私信或扫描文章底部二维码。


(1) 设计基于混合滑动窗口与预积分因子的轻量级图优化模型。图优化将定位问题转化为一个最大后验概率估计问题,通过构建由节点(机器人位姿)和边(传感器测量约束)组成的图模型进行求解。随着时间增长,节点数量线性增加,导致计算量剧增。为解决此问题,本研究设计了一种混合滑动窗口优化器。它将优化窗口划分为“成熟区”和“成长区”。成熟区内包含经过多次优化、状态已相对稳定的历史位姿节点;成长区则包含最新的、待优化的位姿节点。在每次优化迭代时,主要对成长区节点和与成熟区相连的少数边缘节点进行优化,而成熟区内部节点的约束则通过边缘化技术转化为先验信息保留。这极大地减少了每次优化的变量规模,实现了计算复杂度的有界性。同时,针对高频的IMU和OD数据,创新性地采用了一种改进的预积分算法。该算法能够在IMU零偏或OD刻度因子发生微小变化时,利用一阶泰勒展开近似修正预积分结果,而无需从原始数据重新积分,显著提升了计算效率,满足了实时性要求

(2) 提出一种嵌套期望最大化算法以估计高斯混合误差模型,提升抗差能力。在复杂环境中,传感器测量值常因多径效应、非视距、颠簸路面等产生异常值,这些误差不服从高斯分布,使用标准的高斯假设优化器(如基于马氏距离的鲁棒核函数)可能失效。为此,本研究提出了一种自适应期望最大化算法来求解更通用的高斯混合误差模型。该模型将测量误差表示为多个高斯分布的加权和,其中一个分量用于建模正常的“内点”噪声(方差小),其他分量用于建模可能的“外点”噪声(方差大)。算法采用嵌套EM结构:内层EM算法负责估计当前迭代下每条约束边所属的高斯混合分量及其参数(权重、均值、方差);外层EM算法则基于内层估计出的每个数据点的“责任度”(属于各分量的概率),重新估计系统的状态变量(位姿、速度等)。通过这种交替优化,算法能自动甄别并削弱异常测量值的影响,而不需要手动设置阈值。实验表明,与传统的DCS、Gauss-Newton等方法相比,该算法在含有大量异常值的场景下,仍能保持较高的定位精度和良好的收敛性。

(3) 实现紧耦合的GNSS原始观测值与多源信息融合框架。为进一步提升在GNSS信号部分可见或质量不佳时的性能,本研究超越了松耦合(位置/速度级融合)和紧耦合(伪距/载波相位级融合)的传统方式,将GNSS原始伪距、多普勒频移等观测值直接作为约束边加入到图优化模型中。INS/OD预积分提供的精确短时位姿预测,可以有效辅助GNSS整周模糊度的解算;反之,GNSS的绝对位置信息又能校正INS的累积误差和OD的刻度误差。在图模型中,除了位姿节点,还将接收机钟差、GNSS模糊度等作为待优化状态变量。构建的约束边类型包括:INS预积分产生的相邻位姿间相对运动约束,OD产生的相邻位姿间相对位移约束,以及GNSS接收机与多个卫星之间基于几何距离的伪距约束和多普勒速度约束。所有约束在统一的非线性最小二乘框架下进行优化。这种深度的紧耦合方式,即使在仅能接收到2-3颗卫星信号的极端情况下,也能通过与INS/OD的组合提供可用的定位解,极大地增强了系统的可用性和鲁棒性。仿真与实车测试表明,该融合算法在开阔环境下的定位精度与专业RTK相当,在复杂城市道路中的定位连续性和可靠性远高于传统的卡尔曼滤波方案。

import numpy as np
import sympy as sp
from scipy.sparse import lil_matrix, csr_matrix
from scipy.sparse.linalg import spsolve
import matplotlib.pyplot as plt

class GraphOptimizationFusion:
    def __init__(self, window_size=10):
        self.window_size = window_size
        self.state_dim = 3
        self.poses = []
        self.landmarks = []
        self.edges = []
        self.optimization_history = []

    def add_pose(self, pose):
        self.poses.append(pose.copy())
        if len(self.poses) > self.window_size:
            self.poses.pop(0)
            self._marginalize_oldest()

    def add_gnss_constraint(self, pose_id, gnss_measurement, info_matrix):
        self.edges.append(('gnss', pose_id, gnss_measurement, info_matrix))

    def add_imu_preintegral_constraint(self, pose_id_i, pose_id_j, delta_pose, info_matrix):
        self.edges.append(('imu', (pose_id_i, pose_id_j), delta_pose, info_matrix))

    def add_odometry_constraint(self, pose_id_i, pose_id_j, odom_meas, info_matrix):
        self.edges.append(('odom', (pose_id_i, pose_id_j), odom_meas, info_matrix))

    def _compute_error_and_jacobian(self, state_vector):
        num_poses = len(self.poses)
        total_state_size = num_poses * self.state_dim
        error = []
        rows = []
        cols = []
        data_J = []
        row_idx = 0
        for edge in self.edges:
            e_type, ids, measurement, info = edge
            sqrt_info = np.linalg.cholesky(info)
            if e_type == 'gnss':
                pose_id = ids
                if pose_id >= num_poses:
                    continue
                pose_idx = pose_id * self.state_dim
                est_pose = state_vector[pose_idx:pose_idx+self.state_dim]
                err = est_pose - measurement
                J = np.eye(self.state_dim)
                for i in range(self.state_dim):
                    for j in range(self.state_dim):
                        rows.append(row_idx + i)
                        cols.append(pose_idx + j)
                        data_J.append(J[i, j])
                error.append(sqrt_info @ err)
                row_idx += self.state_dim
            elif e_type == 'imu' or e_type == 'odom':
                id_i, id_j = ids
                if id_j >= num_poses:
                    continue
                idx_i = id_i * self.state_dim
                idx_j = id_j * self.state_dim
                pose_i = state_vector[idx_i:idx_i+self.state_dim]
                pose_j = state_vector[idx_j:idx_j+self.state_dim]
                pred_delta = pose_j - pose_i
                err = pred_delta - measurement
                J_i = -np.eye(self.state_dim)
                J_j = np.eye(self.state_dim)
                for i in range(self.state_dim):
                    for j in range(self.state_dim):
                        rows.append(row_idx + i)
                        cols.append(idx_i + j)
                        data_J.append(J_i[i, j])
                        rows.append(row_idx + i)
                        cols.append(idx_j + j)
                        data_J.append(J_j[i, j])
                error.append(sqrt_info @ err)
                row_idx += self.state_dim
        error = np.concatenate(error)
        J = csr_matrix((data_J, (rows, cols)), shape=(row_idx, total_state_size))
        return error, J

    def _marginalize_oldest(self):
        if len(self.poses) <= 1:
            return
        old_edges = [e for e in self.edges if (isinstance(e[1], tuple) and e[1][0] == 0) or (e[0] == 'gnss' and e[1] == 0)]
        for edge in old_edges:
            self.edges.remove(edge)
        self.edges = [('gnss', pid-1, meas, info) if etype == 'gnss' and pid>0 else 
                      (etype, (pid_i-1, pid_j-1), meas, info) if isinstance(ids, tuple) else 
                      (etype, ids, meas, info) 
                      for etype, ids, meas, info in self.edges]

    def gauss_newton_optimization(self, iterations=10):
        num_poses = len(self.poses)
        if num_poses == 0:
            return
        current_state = np.concatenate(self.poses)
        for it in range(iterations):
            error, J = self._compute_error_and_jacobian(current_state)
            b = -J.T @ error
            H = J.T @ J
            dx = spsolve(H + 1e-6 * np.eye(H.shape[0]), b)
            current_state += dx
            cost = error.T @ error
            self.optimization_history.append(cost)
        for i in range(num_poses):
            self.poses[i] = current_state[i*self.state_dim:(i+1)*self.state_dim]
        return current_state, cost

    def run_simulation(self):
        np.random.seed(2025)
        gt_trajectory = np.cumsum(np.random.randn(30, self.state_dim) * 0.5, axis=0)
        for i, gt_pose in enumerate(gt_trajectory):
            noisy_pose = gt_pose + np.random.randn(self.state_dim) * 2.0
            self.add_pose(noisy_pose)
            if i > 0:
                imu_delta = gt_trajectory[i] - gt_trajectory[i-1] + np.random.randn(self.state_dim)*0.1
                self.add_imu_preintegral_constraint(i-1, i, imu_delta, np.eye(self.state_dim)*100)
                odom_delta = gt_trajectory[i] - gt_trajectory[i-1] + np.random.randn(self.state_dim)*0.05
                self.add_odometry_constraint(i-1, i, odom_delta, np.eye(self.state_dim)*150)
            if i % 3 == 0:
                gnss_meas = gt_pose + np.random.randn(self.state_dim) * (5.0 if i % 9 == 0 else 1.0)
                self.add_gnss_constraint(i, gnss_meas, np.eye(self.state_dim)/4.0)
            if len(self.poses) >= self.window_size:
                opt_state, final_cost = self.gauss_newton_optimization(iterations=5)
        opt_trajectory = np.array(self.poses)
        return gt_trajectory[:len(opt_trajectory)], opt_trajectory

optimizer = GraphOptimizationFusion(window_size=8)
gt, est = optimizer.run_simulation()
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
axes[0, 0].plot(gt[:, 0], gt[:, 1], 'k-', label='Ground Truth', linewidth=2)
axes[0, 0].plot(est[:, 0], est[:, 1], 'b--', label='Optimized', linewidth=1.5, marker='o', markersize=4)
axes[0, 0].set_xlabel('X (m)')
axes[0, 0].set_ylabel('Y (m)')
axes[0, 0].set_title('Trajectory Comparison (Top-Down View)')
axes[0, 0].legend()
axes[0, 0].grid(True)
axes[0, 0].axis('equal')
axes[0, 1].plot(range(len(gt)), gt[:, 2], 'k-', label='GT Z', linewidth=2)
axes[0, 1].plot(range(len(est)), est[:, 2], 'g--', label='Est Z', linewidth=1.5)
axes[0, 1].set_xlabel('Step')
axes[0, 1].set_ylabel('Z (m)')
axes[0, 1].set_title('Height Comparison')
axes[0, 1].legend()
axes[0, 1].grid(True)
pos_error = np.linalg.norm(gt[:len(est)] - est, axis=1)
axes[1, 0].plot(pos_error, 'r-')
axes[1, 0].set_xlabel('Step')
axes[1, 0].set_ylabel('Position Error (m)')
axes[1, 0].set_title('Optimization Position Error Over Time')
axes[1, 0].grid(True)
axes[1, 1].plot(optimizer.optimization_history, 'm-')
axes[1, 1].set_xlabel('Iteration')
axes[1, 1].set_ylabel('Total Cost')
axes[1, 1].set_title('Graph Optimization Convergence')
axes[1, 1].grid(True)
plt.tight_layout()
plt.show()


如有问题,可以直接沟通

👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

坷拉博士

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值