EKF-SLAM在MATLAB上的仿真实现

一、EKF-SLAM算法原理

1.1 基本概念

EKF-SLAM(扩展卡尔曼滤波-同时定位与建图)是一种基于概率的算法,用于机器人在未知环境中同时估计自身位姿和构建环境地图。

1.2 状态向量表示

复制代码
状态向量 x = [机器人位姿; 地标位置]
x = [x_r, y_r, θ_r, x_l1, y_l1, x_l2, y_l2, ...]^T

1.3 算法流程

  1. 预测步骤:根据运动模型预测机器人位姿
  2. 更新步骤:根据观测数据更新状态估计
  3. 数据关联:将观测与已有地标匹配
  4. 新地标初始化:将未匹配的观测初始化为新地标

二、MATLAB仿真实现

2.1 完整仿真代码

matlab 复制代码
%% EKF-SLAM MATLAB仿真
% 功能:实现基本的EKF-SLAM算法仿真

clear all; close all; clc;

%% 1. 仿真参数设置
fprintf('=== EKF-SLAM仿真开始 ===\n');

% 时间参数
dt = 0.1;           % 时间步长(s)
T = 100;            % 总仿真时间(s)
steps = T/dt;       % 总步数

% 机器人参数
wheel_base = 0.5;   % 轮距(m)
wheel_radius = 0.1; % 轮半径(m)

% 噪声参数
Q = diag([0.1, 0.1, 0.05].^2);     % 过程噪声协方差
R = diag([0.1, 0.05].^2);          % 观测噪声协方差

% 地图参数
num_landmarks = 10;                 % 地标数量
map_size = 20;                      % 地图尺寸(m)

% EKF参数
state_dim = 3;                      % 机器人状态维度
landmark_dim = 2;                   % 每个地标维度

%% 2. 环境初始化
fprintf('初始化环境...\n');

% 生成随机地标
true_landmarks = map_size * (rand(2, num_landmarks) - 0.5);

% 机器人真实轨迹(圆形运动)
true_trajectory = zeros(3, steps);
estimated_trajectory = zeros(3, steps);

% 初始状态
true_pose = [0; 0; 0];              % [x, y, theta]
estimated_pose = true_pose + sqrt(Q)*randn(3,1); % 加入初始误差

% EKF状态向量和协方差初始化
state = [estimated_pose; zeros(2*num_landmarks, 1)]; % 初始时地标位置未知
P = zeros(3 + 2*num_landmarks);
P(1:3, 1:3) = 0.1 * eye(3);        % 机器人位姿初始不确定性
for i = 1:num_landmarks
    idx = 3 + 2*(i-1) + (1:2);
    P(idx, idx) = 1000 * eye(2);   % 地标位置初始不确定性(大值表示未知)
end

% 数据关联矩阵(记录哪些地标已被观测)
observed_landmarks = false(1, num_landmarks);

%% 3. 主仿真循环
fprintf('开始仿真循环...\n');

% 创建图形窗口
figure('Position', [100, 100, 1200, 500]);

for k = 1:steps
    %% 3.1 生成控制输入(速度模型)
    v = 1.0 + 0.1*randn;           % 线速度(m/s)
    w = 0.2 + 0.05*randn;          % 角速度(rad/s)
    
    %% 3.2 真实运动(用于生成真值)
    true_pose = motion_model(true_pose, [v; w], dt, zeros(3,1));
    true_trajectory(:, k) = true_pose;
    
    %% 3.3 EKF预测步骤
    % 提取机器人状态
    robot_state = state(1:3);
    
    % 计算雅可比矩阵
    F_x = [1, 0, -v*dt*sin(robot_state(3));
           0, 1,  v*dt*cos(robot_state(3));
           0, 0,  1];
    
    % 状态转移矩阵
    F = eye(length(state));
    F(1:3, 1:3) = F_x;
    
    % 过程噪声雅可比
    G = [dt*cos(robot_state(3)), 0;
         dt*sin(robot_state(3)), 0;
         0, dt];
    
    % 预测状态
    state(1:3) = motion_model(robot_state, [v; w], dt, zeros(3,1));
    
    % 预测协方差
    P = F * P * F' + Q_k;
    
    %% 3.4 生成观测
    measurements = [];
    landmark_ids = [];
    
    % 传感器参数
    max_range = 10;     % 最大观测距离(m)
    fov = 120;          % 视场角(度)
    
    for i = 1:num_landmarks
        % 计算相对位置
        dx = true_landmarks(1, i) - true_pose(1);
        dy = true_landmarks(2, i) - true_pose(2);
        
        % 计算距离和角度
        distance = sqrt(dx^2 + dy^2);
        bearing = atan2(dy, dx) - true_pose(3);
        bearing = wrapToPi(bearing); % 归一化到[-pi, pi]
        
        % 检查是否在传感器范围内
        if distance <= max_range && abs(bearing) <= deg2rad(fov/2)
            % 添加观测噪声
            z_noisy = [distance; bearing] + sqrt(R)*randn(2,1);
            
            measurements = [measurements, z_noisy];
            landmark_ids = [landmark_ids, i];
        end
    end
    
    %% 3.5 EKF更新步骤
    if ~isempty(measurements)
        num_measurements = size(measurements, 2);
        
        for m = 1:num_measurements
            landmark_id = landmark_ids(m);
            z = measurements(:, m);
            
            % 检查地标是否已初始化
            if observed_landmarks(landmark_id)
                % 已观测地标:数据关联
                idx = 3 + 2*(landmark_id-1) + (1:2);
                landmark_state = state(idx);
                
                % 计算预测观测
                dx = landmark_state(1) - state(1);
                dy = landmark_state(2) - state(2);
                q = dx^2 + dy^2;
                
                z_pred = [sqrt(q);
                         atan2(dy, dx) - state(3)];
                z_pred(2) = wrapToPi(z_pred(2));
                
                % 计算观测雅可比
                H = zeros(2, length(state));
                H(:, 1:3) = [-dx/sqrt(q), -dy/sqrt(q), 0;
                             dy/q, -dx/q, -1];
                H(:, idx) = [dx/sqrt(q), dy/sqrt(q);
                            -dy/q, dx/q];
                
                % 卡尔曼增益
                S = H * P * H' + R;
                K = P * H' / S;
                
                % 状态更新
                innovation = z - z_pred;
                innovation(2) = wrapToPi(innovation(2)); % 角度归一化
                
                state = state + K * innovation;
                P = (eye(length(state)) - K * H) * P;
                
            else
                % 新地标:初始化
                fprintf('初始化新地标 %d\n', landmark_id);
                
                % 计算地标全局坐标
                global_x = state(1) + z(1)*cos(state(3) + z(2));
                global_y = state(2) + z(1)*sin(state(3) + z(2));
                
                % 更新状态向量
                idx = 3 + 2*(landmark_id-1) + (1:2);
                state(idx) = [global_x; global_y];
                
                % 更新协方差(简化处理)
                observed_landmarks(landmark_id) = true;
            end
        end
    end
    
    %% 3.6 存储估计轨迹
    estimated_trajectory(:, k) = state(1:3);
    
    %% 3.7 实时可视化
    if mod(k, 10) == 0 || k == steps
        % 子图1:轨迹和地图
        subplot(1, 2, 1);
        cla;
        hold on;
        grid on;
        
        % 绘制真实地标
        plot(true_landmarks(1, :), true_landmarks(2, :), 'g^', ...
             'MarkerSize', 10, 'LineWidth', 2, 'DisplayName', '真实地标');
        
        % 绘制估计地标
        for i = 1:num_landmarks
            if observed_landmarks(i)
                idx = 3 + 2*(i-1) + (1:2);
                plot(state(idx(1)), state(idx(2)), 'b*', ...
                     'MarkerSize', 8, 'LineWidth', 1.5);
                
                % 绘制不确定性椭圆
                covariance = P(idx, idx);
                error_ellipse(covariance, state(idx), 'b', 0.5);
            end
        end
        
        % 绘制真实轨迹
        plot(true_trajectory(1, 1:k), true_trajectory(2, 1:k), 'r-', ...
             'LineWidth', 1.5, 'DisplayName', '真实轨迹');
        
        % 绘制估计轨迹
        plot(estimated_trajectory(1, 1:k), estimated_trajectory(2, 1:k), 'b--', ...
             'LineWidth', 1.5, 'DisplayName', '估计轨迹');
        
        % 绘制当前位姿
        plot_robot(true_pose, 'r', 0.5);  % 真实位姿
        plot_robot(state(1:3), 'b', 0.5); % 估计位姿
        
        % 绘制观测
        if ~isempty(measurements)
            for m = 1:size(measurements, 2)
                z = measurements(:, m);
                end_x = true_pose(1) + z(1)*cos(true_pose(3) + z(2));
                end_y = true_pose(2) + z(1)*sin(true_pose(3) + z(2));
                plot([true_pose(1), end_x], [true_pose(2), end_y], 'm:', 'LineWidth', 1);
            end
        end
        
        xlabel('X (m)'); ylabel('Y (m)');
        title(sprintf('EKF-SLAM仿真 (t=%.1fs, 步数=%d)', k*dt, k));
        legend('Location', 'best');
        axis equal;
        xlim([-map_size/2, map_size/2]);
        ylim([-map_size/2, map_size/2]);
        
        % 子图2:误差分析
        subplot(1, 2, 2);
        cla;
        hold on;
        grid on;
        
        % 计算位置误差
        pos_error = sqrt((true_pose(1) - state(1))^2 + ...
                         (true_pose(2) - state(2))^2);
        
        % 计算角度误差
        angle_error = abs(wrapToPi(true_pose(3) - state(3)));
        
        % 绘制误差历史
        if k > 1
            time_history = (1:k)*dt;
            pos_errors = sqrt(sum((true_trajectory(1:2, 1:k) - ...
                                  estimated_trajectory(1:2, 1:k)).^2, 1));
            
            plot(time_history, pos_errors, 'b-', 'LineWidth', 1.5);
            plot(k*dt, pos_error, 'ro', 'MarkerSize', 8, 'LineWidth', 2);
        end
        
        xlabel('时间 (s)'); ylabel('位置误差 (m)');
        title(sprintf('位置误差: %.3fm, 角度误差: %.3frad', pos_error, angle_error));
        legend('误差历史', '当前误差', 'Location', 'best');
        
        drawnow;
    end
    
    %% 3.8 进度显示
    if mod(k, 100) == 0
        fprintf('进度: %.1f%%\n', k/steps*100);
    end
end

%% 4. 结果分析
fprintf('\n=== 仿真完成 ===\n');

% 计算最终误差
final_pos_error = norm(true_trajectory(1:2, end) - estimated_trajectory(1:2, end));
final_angle_error = abs(wrapToPi(true_trajectory(3, end) - estimated_trajectory(3, end)));

fprintf('最终位置误差: %.3f m\n', final_pos_error);
fprintf('最终角度误差: %.3f rad\n', final_angle_error);
fprintf('已观测地标数: %d/%d\n', sum(observed_landmarks), num_landmarks);

% 绘制最终结果对比图
figure('Position', [100, 100, 800, 600]);

% 子图1:完整轨迹对比
subplot(2, 2, 1);
hold on; grid on;
plot(true_trajectory(1, :), true_trajectory(2, :), 'r-', 'LineWidth', 2, 'DisplayName', '真实轨迹');
plot(estimated_trajectory(1, :), estimated_trajectory(2, :), 'b--', 'LineWidth', 2, 'DisplayName', '估计轨迹');
plot(true_landmarks(1, :), true_landmarks(2, :), 'g^', 'MarkerSize', 10, 'LineWidth', 2, 'DisplayName', '真实地标');

% 绘制估计地标
for i = 1:num_landmarks
    if observed_landmarks(i)
        idx = 3 + 2*(i-1) + (1:2);
        plot(state(idx(1)), state(idx(2)), 'b*', 'MarkerSize', 8, 'LineWidth', 1.5);
    end
end

xlabel('X (m)'); ylabel('Y (m)');
title('轨迹与地图对比');
legend('Location', 'best');
axis equal;

% 子图2:位置误差随时间变化
subplot(2, 2, 2);
time = (1:steps)*dt;
pos_errors = sqrt(sum((true_trajectory(1:2, :) - estimated_trajectory(1:2, :)).^2, 1));
plot(time, pos_errors, 'b-', 'LineWidth', 1.5);
grid on;
xlabel('时间 (s)'); ylabel('位置误差 (m)');
title('位置误差变化');
ylim([0, max(pos_errors)*1.1]);

% 子图3:角度误差随时间变化
subplot(2, 2, 3);
angle_errors = abs(wrapToPi(true_trajectory(3, :) - estimated_trajectory(3, :)));
plot(time, angle_errors, 'r-', 'LineWidth', 1.5);
grid on;
xlabel('时间 (s)'); ylabel('角度误差 (rad)');
title('角度误差变化');
ylim([0, max(angle_errors)*1.1]);

% 子图4:协方差迹(不确定性度量)
subplot(2, 2, 4);
cov_trace = zeros(1, steps);
for k = 1:steps
    % 这里简化处理,实际应该存储每一步的协方差迹
    cov_trace(k) = trace(P(1:3, 1:3));
end
plot(time, cov_trace, 'g-', 'LineWidth', 1.5);
grid on;
xlabel('时间 (s)'); ylabel('协方差迹');
title('机器人位姿不确定性');

%% 辅助函数定义

function new_pose = motion_model(pose, u, dt, noise)
    % 运动模型:速度模型
    % pose: [x, y, theta]
    % u: [v, w] 线速度和角速度
    % dt: 时间步长
    % noise: 过程噪声
    
    v = u(1);
    w = u(2);
    theta = pose(3);
    
    if abs(w) < 1e-6
        % 直线运动
        new_pose = pose + [v*dt*cos(theta);
                          v*dt*sin(theta);
                          0] + noise;
    else
        % 曲线运动
        new_pose = pose + [v/w*(sin(theta + w*dt) - sin(theta));
                          v/w*(-cos(theta + w*dt) + cos(theta));
                          w*dt] + noise;
    end
    
    % 角度归一化
    new_pose(3) = wrapToPi(new_pose(3));
end

function plot_robot(pose, color, scale)
    % 绘制机器人
    % pose: [x, y, theta]
    % color: 颜色
    % scale: 缩放因子
    
    x = pose(1);
    y = pose(2);
    theta = pose(3);
    
    % 机器人尺寸
    robot_length = 0.6 * scale;
    robot_width = 0.4 * scale;
    
    % 机器人轮廓(矩形)
    corners = [-robot_length/2, -robot_width/2;
                robot_length/2, -robot_width/2;
                robot_length/2,  robot_width/2;
               -robot_length/2,  robot_width/2];
    
    % 旋转矩阵
    R = [cos(theta), -sin(theta);
         sin(theta),  cos(theta)];
    
    % 旋转和平移
    corners = (R * corners')' + [x, y];
    
    % 绘制机器人主体
    fill(corners(:,1), corners(:,2), color, 'FaceAlpha', 0.3, 'EdgeColor', color, 'LineWidth', 1.5);
    
    % 绘制方向指示器
    front_x = x + robot_length/2 * cos(theta);
    front_y = y + robot_length/2 * sin(theta);
    plot([x, front_x], [y, front_y], color, 'LineWidth', 2);
    
    % 绘制机器人中心点
    plot(x, y, [color, 'o'], 'MarkerSize', 6, 'LineWidth', 2);
end

function h = error_ellipse(covariance, center, color, scale)
    % 绘制误差椭圆
    % covariance: 2x2协方差矩阵
    % center: 椭圆中心 [x, y]
    % color: 颜色
    % scale: 缩放因子(置信度)
    
    [V, D] = eig(covariance);
    
    % 计算椭圆参数
    a = scale * sqrt(D(1,1));  % 半长轴
    b = scale * sqrt(D(2,2));  % 半短轴
    angle = atan2(V(2,1), V(1,1));  % 旋转角度
    
    % 生成椭圆点
    theta = linspace(0, 2*pi, 100);
    ellipse = [a * cos(theta); b * sin(theta)];
    
    % 旋转
    R = [cos(angle), -sin(angle); sin(angle), cos(angle)];
    ellipse = R * ellipse;
    
    % 平移
    ellipse(1,:) = ellipse(1,:) + center(1);
    ellipse(2,:) = ellipse(2,:) + center(2);
    
    % 绘制椭圆
    h = plot(ellipse(1,:), ellipse(2,:), color, 'LineWidth', 1);
end

function angle = wrapToPi(angle)
    % 将角度归一化到[-pi, pi]
    angle = mod(angle + pi, 2*pi) - pi;
end

2.2 简化版本(快速入门)

matlab 复制代码
%% 简化版EKF-SLAM仿真
% 适用于快速理解和测试

clear; close all; clc;

% 参数设置
dt = 0.1;
steps = 200;
num_landmarks = 5;

% 初始化
true_landmarks = 10 * rand(2, num_landmarks) - 5;
true_pose = [0; 0; 0];
state = [true_pose; zeros(2*num_landmarks, 1)];
P = diag([0.1, 0.1, 0.05, 1000*ones(1, 2*num_landmarks)]);

% 噪声
Q = diag([0.05, 0.05, 0.02].^2);
R = diag([0.1, 0.05].^2);

% 存储
trajectory = zeros(3, steps);
estimates = zeros(3, steps);

% 主循环
for k = 1:steps
    % 控制输入
    v = 1.0;
    w = 0.1 * sin(0.1*k);
    
    % 真实运动
    true_pose = true_pose + [v*dt*cos(true_pose(3));
                            v*dt*sin(true_pose(3));
                            w*dt];
    trajectory(:, k) = true_pose;
    
    % EKF预测
    state(1:3) = state(1:3) + [v*dt*cos(state(3));
                              v*dt*sin(state(3));
                              w*dt];
    
    % 雅可比
    F = eye(length(state));
    F(1:3, 1:3) = [1, 0, -v*dt*sin(state(3));
                   0, 1,  v*dt*cos(state(3));
                   0, 0,  1];
    
    % 预测协方差
    P = F * P * F' + blkdiag(Q, zeros(2*num_landmarks));
    
    % 生成观测
    for i = 1:num_landmarks
        dx = true_landmarks(1,i) - true_pose(1);
        dy = true_landmarks(2,i) - true_pose(2);
        dist = norm([dx, dy]);
        
        if dist < 5  % 观测范围
            % 观测模型
            z_true = [dist;
                     atan2(dy, dx) - true_pose(3)];
            z = z_true + sqrt(R)*randn(2,1);
            
            % 数据关联(简化:已知对应关系)
            idx = 3 + 2*(i-1) + (1:2);
            
            if norm(state(idx)) < 1e-6  % 新地标
                state(idx) = [state(1) + z(1)*cos(state(3)+z(2));
                             state(2) + z(1)*sin(state(3)+z(2))];
            else  % 已存在地标
                % 预测观测
                dx_est = state(idx(1)) - state(1);
                dy_est = state(idx(2)) - state(2);
                q = dx_est^2 + dy_est^2;
                z_pred = [sqrt(q);
                         atan2(dy_est, dx_est) - state(3)];
                
                % 雅可比
                H = zeros(2, length(state));
                H(:, 1:3) = [-dx_est/sqrt(q), -dy_est/sqrt(q), 0;
                            dy_est/q, -dx_est/q, -1];
                H(:, idx) = [dx_est/sqrt(q), dy_est/sqrt(q);
                           -dy_est/q, dx_est/q];
                
                % 卡尔曼更新
                S = H * P * H' + R;
                K = P * H' / S;
                state = state + K * (z - z_pred);
                P = (eye(length(state)) - K * H) * P;
            end
        end
    end
    
    estimates(:, k) = state(1:3);
    
    % 可视化
    if mod(k, 10) == 0
        clf;
        subplot(1,2,1);
        hold on; grid on;
        plot(trajectory(1,1:k), trajectory(2,1:k), 'r-');
        plot(estimates(1,1:k), estimates(2,1:k), 'b--');
        plot(true_landmarks(1,:), true_landmarks(2,:), 'g^');
        for i = 1:num_landmarks
            idx = 3 + 2*(i-1) + (1:2);
            if norm(state(idx)) > 0
                plot(state(idx(1)), state(idx(2)), 'b*');
            end
        end
        xlabel('X'); ylabel('Y'); title('EKF-SLAM');
        legend('真实轨迹', '估计轨迹', '真实地标', '估计地标');
        axis equal;
        
        subplot(1,2,2);
        error = vecnorm(trajectory(1:2,1:k) - estimates(1:2,1:k));
        plot(1:k, error, 'b-');
        xlabel('步数'); ylabel('位置误差'); title('误差变化');
        grid on;
        drawnow;
    end
end

fprintf('仿真完成!\n');

三、MATLAB工具箱实现

3.1 使用Robotics System Toolbox

matlab 复制代码
%% 使用MATLAB Robotics System Toolbox实现EKF-SLAM

% 创建EKF-SLAM对象
ekfSLAM = robotics.EKFSLAM('StateTransitionFcn', @stateTransitionFcn, ...
                           'MeasurementFcn', @measurementFcn, ...
                           'StateCovariance', eye(3), ...
                           'ProcessNoise', diag([0.1, 0.1, 0.05]), ...
                           'MeasurementNoise', diag([0.1, 0.05]));

% 初始化状态
initialState = [0; 0; 0];  % [x, y, theta]
initialize(ekfSLAM, initialState);

% 仿真循环
for k = 1:100
    % 控制输入
    u = [1.0; 0.1];  % [v; w]
    
    % 预测步骤
    predict(ekfSLAM, u);
    
    % 生成观测
    measurements = generateMeasurements(truePose, trueLandmarks);
    
    % 更新步骤
    if ~isempty(measurements)
        correct(ekfSLAM, measurements);
    end
    
    % 获取估计状态
    [estimatedState, stateCovariance] = getState(ekfSLAM);
    
    % 可视化
    plotSLAM(ekfSLAM, truePose, trueLandmarks);
end

3.2 自定义函数

matlab 复制代码
function statePred = stateTransitionFcn(state, u)
    % 状态转移函数
    dt = 0.1;
    v = u(1);
    w = u(2);
    theta = state(3);
    
    if abs(w) < 1e-6
        statePred = state + [v*dt*cos(theta);
                            v*dt*sin(theta);
                            0];
    else
        statePred = state + [v/w*(sin(theta+w*dt)-sin(theta));
                            v/w*(-cos(theta+w*dt)+cos(theta));
                            w*dt];
    end
    statePred(3) = wrapToPi(statePred(3));
end

function measurements = measurementFcn(state, landmarks)
    % 观测函数
    measurements = [];
    for i = 1:size(landmarks, 2)
        dx = landmarks(1,i) - state(1);
        dy = landmarks(2,i) - state(2);
        dist = norm([dx, dy]);
        bearing = atan2(dy, dx) - state(3);
        measurements = [measurements; dist; wrapToPi(bearing)];
    end
end

四、性能优化技巧

4.1 稀疏矩阵优化

matlab 复制代码
% 利用EKF-SLAM的稀疏性
function P = updateSparseCovariance(P, H, R, z, z_pred)
    % 稀疏更新协方差矩阵
    n = size(P, 1);
    m = size(z, 1);
    
    % 只更新相关部分
    idx = find(any(H ~= 0, 1));  % 找到非零列
    H_reduced = H(:, idx);
    P_reduced = P(idx, idx);
    
    % 计算卡尔曼增益(小矩阵运算)
    S = H_reduced * P_reduced * H_reduced' + R;
    K_reduced = P_reduced * H_reduced' / S;
    
    % 更新状态和协方差
    innovation = z - z_pred;
    P(idx, idx) = P_reduced - K_reduced * H_reduced * P_reduced;
    
    % 返回更新后的协方差
end

4.2 并行化处理

matlab 复制代码
% 使用parfor并行处理观测
if ~isempty(measurements)
    num_meas = size(measurements, 2);
    innovations = zeros(2, num_meas);
    H_cells = cell(1, num_meas);
    
    parfor m = 1:num_meas
        % 并行计算每个观测的创新向量和雅可比
        [innovations(:, m), H_cells{m}] = ...
            computeInnovation(state, P, measurements(:, m), R);
    end
    
    % 合并更新
    for m = 1:num_meas
        H = H_cells{m};
        S = H * P * H' + R;
        K = P * H' / S;
        state = state + K * innovations(:, m);
        P = (eye(size(P)) - K * H) * P;
    end
end

五、扩展功能

5.1 数据关联(最近邻算法)

matlab 复制代码
function [matched_idx, new_landmarks] = dataAssociation(state, measurements, max_distance)
    % 数据关联:最近邻算法
    num_landmarks = (length(state) - 3) / 2;
    matched_idx = zeros(1, size(measurements, 2));
    new_landmarks = [];
    
    for m = 1:size(measurements, 2)
        z = measurements(:, m);
        min_dist = inf;
        best_idx = 0;
        
        % 计算与所有已存在地标的马氏距离
        for i = 1:num_landmarks
            idx = 3 + 2*(i-1) + (1:2);
            if norm(state(idx)) > 0  % 地标已初始化
                % 计算预测观测
                dx = state(idx(1)) - state(1);
                dy = state(idx(2)) - state(2);
                z_pred = [norm([dx, dy]);
                         atan2(dy, dx) - state(3)];
                
                % 计算马氏距离
                H = computeJacobian(state, i);
                S = H * P * H' + R;
                d = (z - z_pred)' / S * (z - z_pred);
                
                if d < min_dist && d < max_distance
                    min_dist = d;
                    best_idx = i;
                end
            end
        end
        
        if best_idx > 0
            matched_idx(m) = best_idx;
        else
            % 新地标
            new_landmarks = [new_landmarks, m];
        end
    end
end

5.2 一致性检验(NEES测试)

matlab 复制代码
function [passed, nees] = consistencyTest(true_state, est_state, est_cov)
    % NEES(归一化估计误差平方)测试
    error = true_state(1:3) - est_state(1:3);
    nees = error' / est_cov(1:3, 1:3) * error;
    
    % 95%置信区间(卡方分布,自由度为3)
    chi2_lower = chi2inv(0.025, 3);
    chi2_upper = chi2inv(0.975, 3);
    
    passed = (nees >= chi2_lower) && (nees <= chi2_upper);
    
    if ~passed
        fprintf('NEES测试失败: %.3f (应在[%.3f, %.3f]之间)\n', ...
                nees, chi2_lower, chi2_upper);
    end
end

参考代码 EKF-SLAM在matlab上实现仿真 www.youwefan.com/contentcsu/59426.html

六、仿真结果分析

6.1 性能指标计算

matlab 复制代码
% 计算RMSE
function [rmse_position, rmse_orientation] = computeRMSE(true_traj, est_traj)
    position_errors = sqrt(sum((true_traj(1:2,:) - est_traj(1:2,:)).^2, 1));
    orientation_errors = abs(wrapToPi(true_traj(3,:) - est_traj(3,:)));
    
    rmse_position = sqrt(mean(position_errors.^2));
    rmse_orientation = sqrt(mean(orientation_errors.^2));
    
    fprintf('位置RMSE: %.4f m\n', rmse_position);
    fprintf('角度RMSE: %.4f rad\n', rmse_orientation);
end

% 计算地图精度
function map_accuracy = computeMapAccuracy(true_landmarks, est_landmarks)
    % 使用匈牙利算法进行地标匹配
    num_true = size(true_landmarks, 2);
    num_est = size(est_landmarks, 2);
    
    % 计算距离矩阵
    dist_matrix = zeros(num_true, num_est);
    for i = 1:num_true
        for j = 1:num_est
            dist_matrix(i,j) = norm(true_landmarks(:,i) - est_landmarks(:,j));
        end
    end
    
    % 简单最近邻匹配
    [~, assignment] = min(dist_matrix, [], 2);
    
    % 计算平均误差
    errors = zeros(num_true, 1);
    for i = 1:num_true
        errors(i) = dist_matrix(i, assignment(i));
    end
    
    map_accuracy = mean(errors);
    fprintf('地图精度: %.4f m\n', map_accuracy);
end

七、实用建议

7.1 调试技巧

  1. 逐步验证:先验证运动模型,再验证观测模型
  2. 可视化调试:实时绘制协方差椭圆和不确定性
  3. 单元测试:为每个函数编写测试用例
  4. 数值稳定性 :使用chol分解代替直接求逆

7.2 参数调优

matlab 复制代码
% 参数敏感性分析
function analyzeParameterSensitivity()
    parameters = {'过程噪声', '观测噪声', '地标数量', '传感器范围'};
    errors = zeros(length(parameters), 10);
    
    for p = 1:length(parameters)
        for trial = 1:10
            % 调整参数并运行仿真
            % 记录误差
        end
    end
    
    % 绘制敏感性分析图
    figure;
    boxplot(errors', 'Labels', parameters);
    xlabel('参数'); ylabel('位置误差(m)');
    title('参数敏感性分析');
end

7.3 实时性能优化

  1. 降低状态维度:使用子图分割或滑动窗口
  2. 近似更新:使用信息滤波器或稀疏EKF
  3. 硬件加速:使用MATLAB Coder生成C代码
  4. 异步处理:将预测和更新步骤分离

这个完整的EKF-SLAM MATLAB仿真实现包含了从基础到高级的所有内容,你可以根据自己的需求进行修改和扩展。建议先从简化版本开始理解算法流程,再逐步尝试完整版本和高级功能。

相关推荐
广州山泉婚姻2 小时前
C语言三种基本程序结构详解
c语言·开发语言
上弦月-编程2 小时前
【C语言】函数栈帧的创建与销毁(底层原理)
c语言·开发语言
MATLAB代码顾问2 小时前
混合粒子群-模拟退火算法(HPSO-SA)求解作业车间调度问题——附MATLAB代码
算法·matlab·模拟退火算法
eqwaak02 小时前
PyTorch张量操作全攻略:从入门到精通
开发语言·人工智能·pytorch·python
辞旧 lekkk2 小时前
【Qt】初识(上)
开发语言·数据库·qt·学习·萌新
格林威2 小时前
线阵工业相机:如何计算线阵相机的行频(Line Rate)?公式+实例
开发语言·人工智能·数码相机·算法·计算机视觉·工业相机·线阵相机
Chasing Aurora2 小时前
python 安装依赖和导入模块 详解
开发语言·python·虚拟环境·import·pyenv·requirements
近津薪荼2 小时前
C++ vector容器底层深度剖析与模拟实现
开发语言·c++
木易 士心2 小时前
为什么 Promise 比 setTimeout 先执行?——JavaScript 事件循环与异步顺序完全指南
开发语言·javascript·ecmascript