卡尔曼滤波技术博客系列:第三篇 雷达目标跟踪:运动模型与坐标转换

摘要

在雷达目标跟踪系统中,选择合适的运动模型和正确处理坐标转换是确保跟踪精度的关键。雷达观测通常在极坐标系(球坐标系)下进行,而目标运动模型通常在笛卡尔坐标系(直角坐标系)中描述。这种坐标系的差异带来了复杂的非线性问题。本文将从雷达目标跟踪的基本原理出发,详细讲解常用运动模型(CV、CA、CTRV、CTRA)的数学推导,深入分析坐标转换的误差传播特性,并通过多个完整的Python示例演示如何在实际跟踪系统中应用这些技术。

目录

  1. 雷达目标跟踪系统概述

  2. 常用运动模型详解

  3. 坐标转换的数学原理

  4. 运动模型在卡尔曼滤波中的应用

  5. 交互多模型(IMM)滤波

  6. Demo 3-1:不同运动模型对比

  7. Demo 3-2:极坐标到笛卡尔坐标转换跟踪

  8. Demo 3-3:交互多模型滤波实现

  9. 总结与工程实践建议

1. 雷达目标跟踪系统概述

1.1 雷达目标跟踪的基本流程

雷达目标跟踪是一个典型的状态估计问题,其基本流程包括:数据采集、点迹提取、坐标转换、数据关联、状态估计和航迹管理等环节。

1.2 雷达观测的物理特性

现代雷达系统通常提供以下观测信息:

  1. 距离(Range):目标到雷达的直线距离

  2. 方位角(Azimuth):目标在水平面上的方向角

  3. 俯仰角(Elevation):目标在垂直面上的仰角

  4. 多普勒频率(Doppler):目标径向速度引起的频率偏移

  5. 信噪比(SNR):目标回波强度

在二维平面跟踪中,我们通常只考虑距离和方位角;在三维空间跟踪中,还需要考虑俯仰角。

1.3 目标跟踪的数学框架

目标跟踪问题可以形式化为一个状态估计问题:

系统模型

复制代码

2. 常用运动模型详解

运动模型描述了目标状态随时间的变化规律。选择合适的运动模型对跟踪性能至关重要。

2.1 匀速模型(Constant Velocity, CV)

匀速模型假设目标在采样间隔内保持匀速直线运动。这是最简单的运动模型,适用于匀速或近似匀速运动的目标。

2.1.1 二维CV模型

在二维平面中,CV模型的状态向量通常包含位置和速度:

2.1.2 三维CV模型

在三维空间中,CV模型的状态向量为:

2.2 匀加速模型(Constant Acceleration, CA)

匀加速模型假设目标在采样间隔内保持匀加速运动。适用于加速或减速运动的目标。

2.2.1 二维CA模型

在二维平面中,CA模型的状态向量包含位置、速度和加速度:

2.3 恒定转率和速度模型(Constant Turn Rate and Velocity, CTRV)

CTRV模型假设目标以恒定速度和恒定转率运动。适用于转弯机动的目标,是雷达目标跟踪中最常用的模型之一。

2.3.1 CTRV模型的状态向量

CTRV模型的状态向量为:

2.3.2 CTRV模型的状态转移

.4 恒定转率和加速度模型(Constant Turn Rate and Acceleration, CTRA)

CTRA模型是CTRV模型的扩展,增加了切向加速度。适用于同时进行转弯和加速/减速的目标。

2.4.1 CTRA模型的状态向量

CTRA模型的状态向量为:

2.4.2 CTRA模型的状态转移

2.5 运动模型对比

模型 状态维度 适用场景 优点 缺点
CV 4(2D)/6(3D) 匀速直线运动 计算简单,数值稳定 无法描述机动
CA 6(2D)/9(3D) 匀加速直线运动 可描述加速运动 对转弯运动描述差
CTRV 5(2D) 匀速转弯运动 准确描述转弯运动 不能描述加速
CTRA 6(2D) 加速转弯运动 最通用的模型 计算复杂

3. 坐标转换的数学原理

雷达观测通常在极坐标系下,而目标运动模型在笛卡尔坐标系中描述。坐标转换是雷达目标跟踪中的关键步骤。

3.1 二维坐标转换

3.1.1 极坐标到笛卡尔坐标
3.1.2 笛卡尔坐标到极坐标

从笛卡尔坐标到极坐标的逆转换

3.2 三维坐标转换

3.2.1 球坐标到笛卡尔坐标
3.2.2 笛卡尔坐标到球坐标

逆转换:

3.3 坐标转换的误差传播

坐标转换会引入非线性误差。考虑带有噪声的雷达观测:

3.3.1 二维情况下的误差传播

在二维情况下,转换后的笛卡尔坐标为:

3.3.2 误差椭球分析

转换后的位置误差在二维平面上形成一个误差椭球。误差椭球的主轴方向由协方差矩阵的特征向量决定,主轴长度由特征值决定。

3.4 去偏转换(Unbiased Conversion)

简单的三角转换会导致有偏估计。去偏转换通过修正偏差来提高估计精度。

3.4.1 二维去偏转换

对于二维极坐标到笛卡尔坐标的转换,去偏估计为:

4. 运动模型在卡尔曼滤波中的应用

运动模型是卡尔曼滤波器的核心组成部分。不同的运动模型对应不同的状态转移矩阵和过程噪声矩阵。

4.1 线性运动模型的卡尔曼滤波

对于线性运动模型(如CV、CA),可以直接使用标准卡尔曼滤波。

4.1.1 CV模型的卡尔曼滤波实现
python 复制代码
import numpy as np
from typing import Optional, Tuple

class CVKalmanFilter:
    """匀速模型卡尔曼滤波器"""
    
    def __init__(self, dt: float, q: float = 1.0):
        """
        初始化CV模型卡尔曼滤波器
        
        参数:
            dt: 采样间隔
            q: 过程噪声强度
        """
        self.dt = dt
        self.q = q
        
        # 状态维度: [x, y, vx, vy]
        self.state_dim = 4
        
        # 状态转移矩阵
        self.F = np.array([
            [1, 0, dt, 0],
            [0, 1, 0, dt],
            [0, 0, 1, 0],
            [0, 0, 0, 1]
        ])
        
        # 过程噪声协方差矩阵
        self.Q = np.array([
            [dt**4/4, 0, dt**3/2, 0],
            [0, dt**4/4, 0, dt**3/2],
            [dt**3/2, 0, dt**2, 0],
            [0, dt**3/2, 0, dt**2]
        ]) * q
        
        # 状态和协方差初始化
        self.x = np.zeros((self.state_dim, 1))
        self.P = np.eye(self.state_dim) * 1000
        
    def predict(self) -> Tuple[np.ndarray, np.ndarray]:
        """预测步骤"""
        self.x = self.F @ self.x
        self.P = self.F @ self.P @ self.F.T + self.Q
        return self.x.copy(), self.P.copy()
    
    def update(self, z: np.ndarray, R: np.ndarray, H: Optional[np.ndarray] = None) -> Tuple[np.ndarray, np.ndarray]:
        """
        更新步骤
        
        参数:
            z: 观测向量 (2×1) [x, y]
            R: 观测噪声协方差 (2×2)
            H: 观测矩阵 (如果为None,则使用默认值)
        """
        if H is None:
            H = np.array([
                [1, 0, 0, 0],
                [0, 1, 0, 0]
            ])
        
        # 计算卡尔曼增益
        S = H @ self.P @ H.T + R
        K = self.P @ H.T @ np.linalg.inv(S)
        
        # 状态更新
        y = z.reshape(-1, 1) - H @ self.x
        self.x = self.x + K @ y
        
        # 协方差更新
        I = np.eye(self.state_dim)
        self.P = (I - K @ H) @ self.P @ (I - K @ H).T + K @ R @ K.T
        
        return self.x.copy(), self.P.copy()

4.2 非线性运动模型的扩展卡尔曼滤波

对于非线性运动模型(如CTRV、CTRA),需要使用扩展卡尔曼滤波(EKF)或无迹卡尔曼滤波(UKF)。

4.2.1 CTRV模型的EKF实现
python 复制代码
import numpy as np
from typing import Optional, Tuple

class CTRVEKF:
    """CTRV模型扩展卡尔曼滤波器"""
    
    def __init__(self, dt: float, q_pos: float = 1.0, q_vel: float = 0.1, q_yawrate: float = 0.01):
        """
        初始化CTRV模型EKF
        
        参数:
            dt: 采样间隔
            q_pos: 位置过程噪声强度
            q_vel: 速度过程噪声强度
            q_yawrate: 转率过程噪声强度
        """
        self.dt = dt
        
        # 状态维度: [x, y, v, psi, psi_dot]
        self.state_dim = 5
        
        # 过程噪声强度
        self.q_pos = q_pos
        self.q_vel = q_vel
        self.q_yawrate = q_yawrate
        
        # 状态和协方差初始化
        self.x = np.zeros((self.state_dim, 1))
        self.P = np.eye(self.state_dim)
        self.P[0, 0] = self.P[1, 1] = 1000  # 位置初始不确定性大
        self.P[2, 2] = 100  # 速度初始不确定性
        self.P[3, 3] = np.pi  # 航向角初始不确定性
        self.P[4, 4] = np.pi/2  # 转率初始不确定性
        
    def predict(self) -> Tuple[np.ndarray, np.ndarray]:
        """预测步骤"""
        # 提取状态
        x, y, v, psi, psi_dot = self.x.flatten()
        
        # 状态转移
        if abs(psi_dot) < 1e-6:  # 直线运动
            x_pred = x + v * self.dt * np.cos(psi)
            y_pred = y + v * self.dt * np.sin(psi)
            v_pred = v
            psi_pred = psi
            psi_dot_pred = 0
        else:  # 转弯运动
            x_pred = x + (v/psi_dot) * (np.sin(psi + psi_dot*self.dt) - np.sin(psi))
            y_pred = y + (v/psi_dot) * (-np.cos(psi + psi_dot*self.dt) + np.cos(psi))
            v_pred = v
            psi_pred = psi + psi_dot * self.dt
            psi_dot_pred = psi_dot
        
        # 归一化航向角到[-π, π]
        psi_pred = (psi_pred + np.pi) % (2*np.pi) - np.pi
        
        self.x = np.array([[x_pred], [y_pred], [v_pred], [psi_pred], [psi_dot_pred]])
        
        # 计算雅可比矩阵
        F = self.compute_jacobian()
        
        # 过程噪声协方差
        Q = np.diag([self.q_pos, self.q_pos, self.q_vel, 0.1, self.q_yawrate])
        
        # 协方差预测
        self.P = F @ self.P @ F.T + Q
        
        return self.x.copy(), self.P.copy()
    
    def compute_jacobian(self) -> np.ndarray:
        """计算状态转移雅可比矩阵"""
        x, y, v, psi, psi_dot = self.x.flatten()
        
        F = np.eye(self.state_dim)
        
        if abs(psi_dot) < 1e-6:  # 直线运动
            F[0, 2] = self.dt * np.cos(psi)
            F[0, 3] = -v * self.dt * np.sin(psi)
            F[1, 2] = self.dt * np.sin(psi)
            F[1, 3] = v * self.dt * np.cos(psi)
        else:  # 转弯运动
            dt = self.dt
            F[0, 2] = (1/psi_dot) * (np.sin(psi + psi_dot*dt) - np.sin(psi))
            F[0, 3] = (v/psi_dot) * (np.cos(psi + psi_dot*dt) - np.cos(psi))
            F[0, 4] = (v/psi_dot**2) * (psi_dot*dt*np.cos(psi + psi_dot*dt) - np.sin(psi + psi_dot*dt) + np.sin(psi))
            
            F[1, 2] = (1/psi_dot) * (-np.cos(psi + psi_dot*dt) + np.cos(psi))
            F[1, 3] = (v/psi_dot) * (np.sin(psi + psi_dot*dt) - np.sin(psi))
            F[1, 4] = (v/psi_dot**2) * (psi_dot*dt*np.sin(psi + psi_dot*dt) + np.cos(psi + psi_dot*dt) - np.cos(psi))
            
            F[3, 4] = dt
        
        return F
    
    def update(self, z: np.ndarray, R: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
        """
        更新步骤(假设观测位置)
        
        参数:
            z: 观测向量 (2×1) [x, y]
            R: 观测噪声协方差 (2×2)
        """
        # 观测矩阵
        H = np.array([
            [1, 0, 0, 0, 0],
            [0, 1, 0, 0, 0]
        ])
        
        # 预测观测
        z_pred = H @ self.x
        
        # 计算卡尔曼增益
        S = H @ self.P @ H.T + R
        K = self.P @ H.T @ np.linalg.inv(S)
        
        # 状态更新
        y = z.reshape(-1, 1) - z_pred
        self.x = self.x + K @ y
        
        # 归一化航向角
        self.x[3, 0] = (self.x[3, 0] + np.pi) % (2*np.pi) - np.pi
        
        # 协方差更新
        I = np.eye(self.state_dim)
        self.P = (I - K @ H) @ self.P @ (I - K @ H).T + K @ R @ K.T
        
        return self.x.copy(), self.P.copy()

5. 交互多模型(IMM)滤波

单一运动模型往往难以准确描述复杂机动目标的运动。交互多模型(Interacting Multiple Model, IMM)滤波通过混合多个模型来适应目标的不同运动模式。

5.1 IMM滤波的基本原理

IMM滤波通过以下步骤实现:

  1. 交互/混合:根据模型概率混合上一时刻的模型条件估计

  2. 模型条件滤波:每个模型独立进行预测和更新

  3. 模型概率更新:根据模型似然更新模型概率

  4. 估计融合:混合各模型的估计得到最终估计

5.2 IMM滤波的数学描述

5.2.2 模型条件滤波

5.3 IMM滤波的Python实现

python 复制代码
import numpy as np
from typing import List, Tuple, Dict
from dataclasses import dataclass
from abc import ABC, abstractmethod

@dataclass
class ModelResult:
    """模型滤波结果"""
    x: np.ndarray  # 状态估计
    P: np.ndarray  # 协方差矩阵
    z_pred: np.ndarray  # 预测观测
    S: np.ndarray  # 新息协方差
    likelihood: float  # 模型似然

class BaseFilter(ABC):
    """滤波器基类"""
    
    def __init__(self, state_dim: int):
        self.state_dim = state_dim
        self.x = np.zeros((state_dim, 1))
        self.P = np.eye(state_dim)
    
    @abstractmethod
    def predict(self) -> Tuple[np.ndarray, np.ndarray]:
        """预测步骤"""
        pass
    
    @abstractmethod
    def update(self, z: np.ndarray, R: np.ndarray) -> ModelResult:
        """更新步骤"""
        pass

class IMMFilter:
    """交互多模型滤波器"""
    
    def __init__(self, filters: List[BaseFilter], 
                 model_prob: np.ndarray,
                 trans_prob: np.ndarray):
        """
        初始化IMM滤波器
        
        参数:
            filters: 滤波器列表
            model_prob: 初始模型概率
            trans_prob: 模型转移概率矩阵
        """
        self.filters = filters
        self.num_models = len(filters)
        self.mu = model_prob  # 模型概率
        self.pi = trans_prob  # 转移概率矩阵
        
        # 检查维度
        assert len(model_prob) == self.num_models, "模型概率维度不匹配"
        assert trans_prob.shape == (self.num_models, self.num_models), "转移概率矩阵维度不匹配"
        
        # 状态维度(假设所有滤波器状态维度相同)
        self.state_dim = filters[0].state_dim
        
        # 存储各模型的历史结果
        self.model_results = [None] * self.num_models
    
    def step(self, z: np.ndarray, R: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
        """
        执行一步IMM滤波
        
        参数:
            z: 观测向量
            R: 观测噪声协方差
            
        返回:
            x_fused: 融合状态估计
            P_fused: 融合协方差矩阵
        """
        # 1. 交互/混合
        mixed_states, mixed_covs = self._interaction()
        
        # 2. 模型条件滤波
        model_likelihoods = np.zeros(self.num_models)
        
        for j in range(self.num_models):
            # 设置混合后的状态和协方差
            self.filters[j].x = mixed_states[j]
            self.filters[j].P = mixed_covs[j]
            
            # 执行预测和更新
            self.filters[j].predict()
            result = self.filters[j].update(z, R)
            self.model_results[j] = result
            
            # 保存模型似然
            model_likelihoods[j] = result.likelihood
        
        # 3. 模型概率更新
        self._update_model_probabilities(model_likelihoods)
        
        # 4. 估计融合
        x_fused, P_fused = self._fusion()
        
        return x_fused, P_fused
    
    def _interaction(self) -> Tuple[List[np.ndarray], List[np.ndarray]]:
        """交互/混合步骤"""
        # 计算混合概率
        c_bar = np.zeros(self.num_models)
        for j in range(self.num_models):
            c_bar[j] = np.sum(self.pi[:, j] * self.mu)
        
        mu_mixed = np.zeros((self.num_models, self.num_models))
        for i in range(self.num_models):
            for j in range(self.num_models):
                mu_mixed[i, j] = self.pi[i, j] * self.mu[i] / c_bar[j]
        
        # 混合状态和协方差
        mixed_states = []
        mixed_covs = []
        
        for j in range(self.num_models):
            # 初始化混合状态
            x_mixed = np.zeros((self.state_dim, 1))
            for i in range(self.num_models):
                x_mixed += mu_mixed[i, j] * self.filters[i].x
            
            # 初始化混合协方差
            P_mixed = np.zeros((self.state_dim, self.state_dim))
            for i in range(self.num_models):
                dx = self.filters[i].x - x_mixed
                P_mixed += mu_mixed[i, j] * (self.filters[i].P + dx @ dx.T)
            
            mixed_states.append(x_mixed)
            mixed_covs.append(P_mixed)
        
        return mixed_states, mixed_covs
    
    def _update_model_probabilities(self, likelihoods: np.ndarray):
        """更新模型概率"""
        # 计算归一化常数
        c = np.sum(likelihoods * np.sum(self.pi * self.mu, axis=0))
        
        # 更新模型概率
        if c > 1e-10:  # 避免除以零
            self.mu = (likelihoods * np.sum(self.pi * self.mu, axis=0)) / c
        else:
            # 如果c太小,保持原概率
            pass
        
        # 确保概率和为1
        self.mu = self.mu / np.sum(self.mu)
    
    def _fusion(self) -> Tuple[np.ndarray, np.ndarray]:
        """估计融合"""
        # 初始化融合状态和协方差
        x_fused = np.zeros((self.state_dim, 1))
        for j in range(self.num_models):
            x_fused += self.mu[j] * self.model_results[j].x
        
        P_fused = np.zeros((self.state_dim, self.state_dim))
        for j in range(self.num_models):
            dx = self.model_results[j].x - x_fused
            P_fused += self.mu[j] * (self.model_results[j].P + dx @ dx.T)
        
        return x_fused, P_fused
    
    def get_model_probabilities(self) -> np.ndarray:
        """获取当前模型概率"""
        return self.mu.copy()

6. Demo 3-1:不同运动模型对比

这个Demo将比较CV、CA、CTRV、CTRA四种运动模型在不同机动场景下的跟踪性能。

python 复制代码
"""
demo_3_1_motion_models_comparison.py
不同运动模型对比分析
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# 运动模型实现
class CVFilter:
    """匀速模型卡尔曼滤波器"""
    
    def __init__(self, dt=1.0, q=0.1):
        self.dt = dt
        self.state_dim = 4  # [x, y, vx, vy]
        
        # 状态转移矩阵
        self.F = np.array([
            [1, 0, dt, 0],
            [0, 1, 0, dt],
            [0, 0, 1, 0],
            [0, 0, 0, 1]
        ])
        
        # 过程噪声协方差
        self.Q = np.array([
            [dt**4/4, 0, dt**3/2, 0],
            [0, dt**4/4, 0, dt**3/2],
            [dt**3/2, 0, dt**2, 0],
            [0, dt**3/2, 0, dt**2]
        ]) * q
        
        # 初始状态
        self.x = np.zeros((4, 1))
        self.P = np.eye(4) * 100
        
    def predict(self):
        self.x = self.F @ self.x
        self.P = self.F @ self.P @ self.F.T + self.Q
        return self.x.copy(), self.P.copy()
    
    def update(self, z, R):
        H = np.array([[1, 0, 0, 0], [0, 1, 0, 0]])
        
        # 卡尔曼增益
        S = H @ self.P @ H.T + R
        K = self.P @ H.T @ np.linalg.inv(S)
        
        # 状态更新
        y = z.reshape(-1, 1) - H @ self.x
        self.x = self.x + K @ y
        
        # 协方差更新
        I = np.eye(self.state_dim)
        self.P = (I - K @ H) @ self.P @ (I - K @ H).T + K @ R @ K.T
        
        return self.x.copy(), self.P.copy()

class CAFilter:
    """匀加速模型卡尔曼滤波器"""
    
    def __init__(self, dt=1.0, q=0.1):
        self.dt = dt
        self.state_dim = 6  # [x, y, vx, vy, ax, ay]
        
        # 状态转移矩阵
        self.F = np.array([
            [1, 0, dt, 0, 0.5*dt**2, 0],
            [0, 1, 0, dt, 0, 0.5*dt**2],
            [0, 0, 1, 0, dt, 0],
            [0, 0, 0, 1, 0, dt],
            [0, 0, 0, 0, 1, 0],
            [0, 0, 0, 0, 0, 1]
        ])
        
        # 过程噪声协方差
        Q_pos = dt**4/4
        Q_vel = dt**3/2
        Q_acc = dt**2
        
        self.Q = np.array([
            [Q_pos, 0, Q_vel, 0, 0.5*Q_acc, 0],
            [0, Q_pos, 0, Q_vel, 0, 0.5*Q_acc],
            [Q_vel, 0, Q_acc, 0, dt, 0],
            [0, Q_vel, 0, Q_acc, 0, dt],
            [0.5*Q_acc, 0, dt, 0, 1, 0],
            [0, 0.5*Q_acc, 0, dt, 0, 1]
        ]) * q
        
        # 初始状态
        self.x = np.zeros((6, 1))
        self.P = np.eye(6) * 100
        
    def predict(self):
        self.x = self.F @ self.x
        self.P = self.F @ self.P @ self.F.T + self.Q
        return self.x.copy(), self.P.copy()
    
    def update(self, z, R):
        H = np.array([[1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0]])
        
        # 卡尔曼增益
        S = H @ self.P @ H.T + R
        K = self.P @ H.T @ np.linalg.inv(S)
        
        # 状态更新
        y = z.reshape(-1, 1) - H @ self.x
        self.x = self.x + K @ y
        
        # 协方差更新
        I = np.eye(self.state_dim)
        self.P = (I - K @ H) @ self.P @ (I - K @ H).T + K @ R @ K.T
        
        return self.x.copy(), self.P.copy()

class CTRVFilter:
    """CTRV模型扩展卡尔曼滤波器"""
    
    def __init__(self, dt=1.0, q_pos=0.1, q_vel=0.01, q_yaw=0.1, q_yawrate=0.01):
        self.dt = dt
        self.state_dim = 5  # [x, y, v, psi, psi_dot]
        
        # 过程噪声参数
        self.q_pos = q_pos
        self.q_vel = q_vel
        self.q_yaw = q_yaw
        self.q_yawrate = q_yawrate
        
        # 初始状态
        self.x = np.zeros((5, 1))
        self.P = np.eye(5) * 100
        self.P[2, 2] = 10
        self.P[3, 3] = np.pi
        self.P[4, 4] = np.pi/2
        
    def predict(self):
        x, y, v, psi, psi_dot = self.x.flatten()
        dt = self.dt
        
        # 状态转移
        if abs(psi_dot) < 1e-6:
            x_pred = x + v * dt * np.cos(psi)
            y_pred = y + v * dt * np.sin(psi)
            v_pred = v
            psi_pred = psi
            psi_dot_pred = 0
        else:
            x_pred = x + (v/psi_dot) * (np.sin(psi + psi_dot*dt) - np.sin(psi))
            y_pred = y + (v/psi_dot) * (-np.cos(psi + psi_dot*dt) + np.cos(psi))
            v_pred = v
            psi_pred = psi + psi_dot * dt
            psi_dot_pred = psi_dot
        
        # 归一化航向角
        psi_pred = (psi_pred + np.pi) % (2*np.pi) - np.pi
        
        self.x = np.array([[x_pred], [y_pred], [v_pred], [psi_pred], [psi_dot_pred]])
        
        # 计算雅可比矩阵
        F = self._compute_jacobian()
        
        # 过程噪声协方差
        Q = np.diag([self.q_pos, self.q_pos, self.q_vel, self.q_yaw, self.q_yawrate])
        
        # 协方差预测
        self.P = F @ self.P @ F.T + Q
        
        return self.x.copy(), self.P.copy()
    
    def _compute_jacobian(self):
        x, y, v, psi, psi_dot = self.x.flatten()
        dt = self.dt
        
        F = np.eye(5)
        
        if abs(psi_dot) < 1e-6:
            F[0, 2] = dt * np.cos(psi)
            F[0, 3] = -v * dt * np.sin(psi)
            F[1, 2] = dt * np.sin(psi)
            F[1, 3] = v * dt * np.cos(psi)
        else:
            F[0, 2] = (1/psi_dot) * (np.sin(psi + psi_dot*dt) - np.sin(psi))
            F[0, 3] = (v/psi_dot) * (np.cos(psi + psi_dot*dt) - np.cos(psi))
            F[0, 4] = (v/psi_dot**2) * (psi_dot*dt*np.cos(psi + psi_dot*dt) - 
                                       np.sin(psi + psi_dot*dt) + np.sin(psi))
            
            F[1, 2] = (1/psi_dot) * (-np.cos(psi + psi_dot*dt) + np.cos(psi))
            F[1, 3] = (v/psi_dot) * (np.sin(psi + psi_dot*dt) - np.sin(psi))
            F[1, 4] = (v/psi_dot**2) * (psi_dot*dt*np.sin(psi + psi_dot*dt) + 
                                       np.cos(psi + psi_dot*dt) - np.cos(psi))
            
            F[3, 4] = dt
        
        return F
    
    def update(self, z, R):
        H = np.array([[1, 0, 0, 0, 0], [0, 1, 0, 0, 0]])
        
        # 卡尔曼增益
        S = H @ self.P @ H.T + R
        K = self.P @ H.T @ np.linalg.inv(S)
        
        # 状态更新
        y = z.reshape(-1, 1) - H @ self.x
        self.x = self.x + K @ y
        
        # 归一化航向角
        self.x[3, 0] = (self.x[3, 0] + np.pi) % (2*np.pi) - np.pi
        
        # 协方差更新
        I = np.eye(self.state_dim)
        self.P = (I - K @ H) @ self.P @ (I - K @ H).T + K @ R @ K.T
        
        return self.x.copy(), self.P.copy()

def generate_trajectory(scenario='straight', num_steps=200, dt=1.0):
    """
    生成不同场景的轨迹
    
    参数:
        scenario: 场景类型 ('straight', 'turn', 'acceleration', 'complex')
        num_steps: 时间步数
        dt: 时间间隔
    """
    true_states = []
    
    if scenario == 'straight':
        # 匀速直线运动
        x, y = 0, 0
        vx, vy = 10, 5
        
        for t in range(num_steps):
            true_states.append([x, y, vx, vy, 0, 0])
            x += vx * dt
            y += vy * dt
    
    elif scenario == 'turn':
        # 匀速圆周运动
        radius = 100
        omega = 0.05  # 角速度
        
        for t in range(num_steps):
            angle = omega * t * dt
            x = radius * np.cos(angle)
            y = radius * np.sin(angle)
            vx = -radius * omega * np.sin(angle)
            vy = radius * omega * np.cos(angle)
            ax = -radius * omega**2 * np.cos(angle)
            ay = -radius * omega**2 * np.sin(angle)
            true_states.append([x, y, vx, vy, ax, ay])
    
    elif scenario == 'acceleration':
        # 匀加速直线运动
        x, y = 0, 0
        vx, vy = 5, 3
        ax, ay = 0.1, 0.05
        
        for t in range(num_steps):
            true_states.append([x, y, vx, vy, ax, ay])
            x += vx * dt + 0.5 * ax * dt**2
            y += vy * dt + 0.5 * ay * dt**2
            vx += ax * dt
            vy += ay * dt
    
    elif scenario == 'complex':
        # 复杂机动:直线→转弯→加速
        x, y = 0, 0
        v, psi = 10, 0
        a = 0
        psi_dot = 0
        
        for t in range(num_steps):
            # 分段机动
            if t < 50:
                # 匀速直线
                psi_dot = 0
                a = 0
            elif t < 100:
                # 匀速转弯
                psi_dot = 0.05
                a = 0
            elif t < 150:
                # 加速直线
                psi_dot = 0
                a = 0.2
            else:
                # 减速转弯
                psi_dot = -0.03
                a = -0.1
            
            # 状态更新
            v += a * dt
            psi += psi_dot * dt
            
            # 归一化航向角
            psi = (psi + np.pi) % (2*np.pi) - np.pi
            
            # 计算位置
            x += v * dt * np.cos(psi)
            y += v * dt * np.sin(psi)
            
            # 计算速度和加速度
            vx = v * np.cos(psi)
            vy = v * np.sin(psi)
            ax = a * np.cos(psi) - v * psi_dot * np.sin(psi)
            ay = a * np.sin(psi) + v * psi_dot * np.cos(psi)
            
            true_states.append([x, y, vx, vy, ax, ay])
    
    true_states = np.array(true_states)
    return true_states

def add_measurement_noise(true_states, pos_noise_std=5.0):
    """添加测量噪声"""
    measurements = true_states[:, :2] + np.random.randn(len(true_states), 2) * pos_noise_std
    return measurements

def run_comparison(scenario='straight'):
    """运行运动模型对比"""
    np.random.seed(42)
    
    # 生成轨迹
    print(f"生成{scenario}场景轨迹...")
    true_states = generate_trajectory(scenario, num_steps=200, dt=1.0)
    
    # 添加测量噪声
    pos_noise_std = 5.0
    measurements = add_measurement_noise(true_states, pos_noise_std)
    R = np.eye(2) * pos_noise_std**2
    
    # 初始化滤波器
    filters = {
        'CV': CVFilter(dt=1.0, q=0.1),
        'CA': CAFilter(dt=1.0, q=0.1),
        'CTRV': CTRVFilter(dt=1.0, q_pos=0.1, q_vel=0.01, q_yaw=0.1, q_yawrate=0.01)
    }
    
    # 运行滤波
    estimates = {name: [] for name in filters}
    position_errors = {name: [] for name in filters}
    
    for name, filter_obj in filters.items():
        print(f"运行{name}滤波器...")
        
        # 初始化状态(使用第一个测量值)
        filter_obj.x[0, 0] = measurements[0, 0]
        filter_obj.x[1, 0] = measurements[0, 1]
        
        for t in range(len(measurements)):
            # 预测
            filter_obj.predict()
            
            # 更新
            z = measurements[t, :2]
            x_est, _ = filter_obj.update(z, R)
            estimates[name].append(x_est.flatten())
            
            # 计算位置误差
            if t < len(true_states):
                pos_error = np.sqrt((x_est[0, 0] - true_states[t, 0])**2 + 
                                   (x_est[1, 0] - true_states[t, 1])**2)
                position_errors[name].append(pos_error)
    
    # 转换估计结果为数组
    for name in estimates:
        estimates[name] = np.array(estimates[name])
    
    # 计算性能指标
    performance = {}
    for name in filters:
        errors = position_errors[name]
        performance[name] = {
            'rmse': np.sqrt(np.mean(np.array(errors)**2)),
            'mae': np.mean(np.abs(errors)),
            'max_error': np.max(np.abs(errors))
        }
    
    # 可视化结果
    fig, axes = plt.subplots(2, 3, figsize=(18, 12))
    
    # 1. 轨迹对比
    ax = axes[0, 0]
    ax.plot(true_states[:, 0], true_states[:, 1], 'k-', linewidth=3, 
            label='真实轨迹', alpha=0.8)
    ax.plot(measurements[:, 0], measurements[:, 1], 'y.', markersize=2, 
            alpha=0.3, label='观测值')
    
    colors = {'CV': 'blue', 'CA': 'green', 'CTRV': 'red'}
    for name in filters:
        ax.plot(estimates[name][:, 0], estimates[name][:, 1], 
                '-', linewidth=1.5, color=colors[name], label=name)
    
    ax.set_xlabel('X位置')
    ax.set_ylabel('Y位置')
    ax.set_title(f'{scenario}场景轨迹跟踪对比')
    ax.legend()
    ax.grid(True, alpha=0.3)
    ax.axis('equal')
    
    # 2. 位置误差对比
    ax = axes[0, 1]
    t = np.arange(len(position_errors['CV']))
    
    for name in filters:
        ax.plot(t, position_errors[name], 
                '-', linewidth=1, color=colors[name], 
                label=f'{name} (RMSE={performance[name]["rmse"]:.2f})')
    
    ax.set_xlabel('时间步')
    ax.set_ylabel('位置误差')
    ax.set_title('位置误差对比')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 3. 速度估计对比(X方向)
    ax = axes[0, 2]
    t = np.arange(len(true_states))
    
    ax.plot(t, true_states[:, 2], 'k-', linewidth=2, label='真实速度', alpha=0.8)
    
    for name in filters:
        if name == 'CTRV':
            # CTRV估计的是速度大小和方向,需要转换为vx, vy
            v_est = estimates[name][:, 2]
            psi_est = estimates[name][:, 3]
            vx_est = v_est * np.cos(psi_est)
        else:
            vx_est = estimates[name][:, 2]
        
        ax.plot(t[:len(vx_est)], vx_est, '-', linewidth=1, color=colors[name], label=f'{name}估计')
    
    ax.set_xlabel('时间步')
    ax.set_ylabel('X方向速度')
    ax.set_title('X方向速度估计对比')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 4. 累积误差分布
    ax = axes[1, 0]
    error_data = []
    labels = []
    
    for name in filters:
        error_data.append(position_errors[name])
        labels.append(name)
    
    bp = ax.boxplot(error_data, labels=labels, patch_artist=True)
    
    # 设置颜色
    for patch, color in zip(bp['boxes'], colors.values()):
        patch.set_facecolor(color)
        patch.set_alpha(0.7)
    
    ax.set_ylabel('位置误差')
    ax.set_title('位置误差分布')
    ax.grid(True, alpha=0.3, axis='y')
    
    # 5. 模型适应性分析
    ax = axes[1, 1]
    
    # 计算滑动窗口RMSE
    window_size = 20
    sliding_rmse = {name: [] for name in filters}
    
    for name in filters:
        errors = position_errors[name]
        for i in range(len(errors) - window_size + 1):
            window_errors = errors[i:i+window_size]
            sliding_rmse[name].append(np.sqrt(np.mean(np.array(window_errors)**2)))
    
    t_sliding = np.arange(len(sliding_rmse['CV']))
    
    for name in filters:
        ax.plot(t_sliding, sliding_rmse[name], 
                '-', linewidth=1.5, color=colors[name], label=name)
    
    ax.set_xlabel('时间窗口中心')
    ax.set_ylabel(f'滑动RMSE (窗口大小={window_size})')
    ax.set_title('模型适应性分析')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 6. 性能总结表
    ax = axes[1, 2]
    ax.axis('off')
    
    # 创建性能表格
    cell_text = []
    for name in filters:
        perf = performance[name]
        cell_text.append([name, 
                         f"{perf['rmse']:.2f}", 
                         f"{perf['mae']:.2f}", 
                         f"{perf['max_error']:.2f}"])
    
    columns = ['模型', 'RMSE', 'MAE', '最大误差']
    table = ax.table(cellText=cell_text, colLabels=columns, 
                     cellLoc='center', loc='center',
                     colColours=['#f0f0f0']*4)
    
    table.auto_set_font_size(False)
    table.set_fontsize(10)
    table.scale(1, 1.5)
    
    ax.set_title('性能指标对比')
    
    plt.suptitle(f'运动模型对比分析 - {scenario}场景', fontsize=16, y=1.02)
    plt.tight_layout()
    plt.savefig(f'motion_models_comparison_{scenario}.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    # 打印性能统计
    print(f"\n{scenario}场景性能对比:")
    print("-"*60)
    print(f"{'模型':<10} {'RMSE':<10} {'MAE':<10} {'最大误差':<10}")
    print("-"*60)
    
    for name in filters:
        perf = performance[name]
        print(f"{name:<10} {perf['rmse']:<10.2f} {perf['mae']:<10.2f} {perf['max_error']:<10.2f}")
    
    print("="*60)
    
    return true_states, measurements, estimates, performance

def analyze_all_scenarios():
    """分析所有场景"""
    scenarios = ['straight', 'turn', 'acceleration', 'complex']
    
    all_performance = {}
    
    for scenario in scenarios:
        print(f"\n{'='*60}")
        print(f"分析场景: {scenario}")
        print('='*60)
        
        true_states, measurements, estimates, performance = run_comparison(scenario)
        all_performance[scenario] = performance
    
    # 综合对比
    print(f"\n{'='*60}")
    print("综合性能对比")
    print('='*60)
    
    models = ['CV', 'CA', 'CTRV']
    
    for model in models:
        print(f"\n{model}模型在不同场景下的RMSE:")
        for scenario in scenarios:
            rmse = all_performance[scenario][model]['rmse']
            print(f"  {scenario:<15}: {rmse:.2f}")
    
    # 绘制综合对比图
    fig, axes = plt.subplots(1, 2, figsize=(14, 6))
    
    # 1. 各场景RMSE对比
    ax = axes[0]
    x = np.arange(len(scenarios))
    width = 0.25
    
    for i, model in enumerate(models):
        rmses = [all_performance[scenario][model]['rmse'] for scenario in scenarios]
        ax.bar(x + (i-1)*width, rmses, width, label=model, alpha=0.7)
    
    ax.set_xlabel('场景')
    ax.set_ylabel('RMSE')
    ax.set_title('各场景RMSE对比')
    ax.set_xticks(x)
    ax.set_xticklabels(scenarios, rotation=15)
    ax.legend()
    ax.grid(True, alpha=0.3, axis='y')
    
    # 2. 模型适应性评分
    ax = axes[1]
    
    # 计算适应性评分(RMSE越小,评分越高)
    adaptability_scores = {model: 0 for model in models}
    
    for scenario in scenarios:
        # 找到该场景下最佳模型
        best_rmse = min(all_performance[scenario][model]['rmse'] for model in models)
        
        for model in models:
            rmse = all_performance[scenario][model]['rmse']
            # 评分 = 最佳RMSE / 当前RMSE
            score = best_rmse / rmse if rmse > 0 else 1.0
            adaptability_scores[model] += score
    
    # 归一化评分
    total_score = sum(adaptability_scores.values())
    for model in models:
        adaptability_scores[model] = adaptability_scores[model] / total_score * 100
    
    # 绘制雷达图
    ax = axes[1]
    angles = np.linspace(0, 2*np.pi, len(scenarios), endpoint=False).tolist()
    angles += angles[:1]  # 闭合图形
    
    for model in models:
        scores = [all_performance[scenario][model]['rmse'] for scenario in scenarios]
        # 转换为适应性分数(RMSE越小越好)
        max_score = max(scores)
        adapted_scores = [1 - (score/max_score) for score in scores]
        adapted_scores += adapted_scores[:1]  # 闭合图形
        
        ax.plot(angles, adapted_scores, 'o-', linewidth=2, label=model)
        ax.fill(angles, adapted_scores, alpha=0.1)
    
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(scenarios)
    ax.set_ylim([0, 1])
    ax.set_title('模型适应性雷达图')
    ax.legend(loc='upper right')
    ax.grid(True)
    
    plt.suptitle('运动模型综合性能对比', fontsize=16, y=1.02)
    plt.tight_layout()
    plt.savefig('motion_models_overall_comparison.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    return all_performance

if __name__ == "__main__":
    print("="*60)
    print("运动模型对比分析")
    print("="*60)
    
    # 分析单个场景
    # true_states, measurements, estimates, performance = run_comparison('turn')
    
    # 分析所有场景
    all_performance = analyze_all_scenarios()
    
    print("\n分析完成!")

7. Demo 3-2:极坐标到笛卡尔坐标转换跟踪

这个Demo将展示在雷达目标跟踪中如何处理极坐标观测,包括简单的三角转换和去偏转换方法。

python 复制代码
"""
demo_3_2_radar_coordinate_conversion.py
极坐标到笛卡尔坐标转换跟踪
完整版本,包含三种坐标转换方法的比较
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from demo_3_1_motion_models_comparison import CVFilter, CAFilter, CTRVFilter

def polar_to_cartesian(range_, bearing, radar_pos=(0, 0)):
    """
    极坐标到笛卡尔坐标转换(简单三角转换)
    
    参数:
        range_: 距离
        bearing: 方位角(弧度)
        radar_pos: 雷达位置 (x, y)
        
    返回:
        x, y: 笛卡尔坐标
    """
    x = radar_pos[0] + range_ * np.cos(bearing)
    y = radar_pos[1] + range_ * np.sin(bearing)
    return x, y

def polar_to_cartesian_unbiased(range_, bearing, range_var, bearing_var, radar_pos=(0, 0)):
    """
    极坐标到笛卡尔坐标的去偏转换
    
    参数:
        range_: 距离
        bearing: 方位角(弧度)
        range_var: 距离方差
        bearing_var: 方位角方差
        radar_pos: 雷达位置 (x, y)
        
    返回:
        x, y: 去偏估计的笛卡尔坐标
        cov: 协方差矩阵
    """
    # 去偏转换
    bias_correction = np.exp(-bearing_var/2)
    x_unbiased = radar_pos[0] + range_ * np.cos(bearing) * bias_correction
    y_unbiased = radar_pos[1] + range_ * np.sin(bearing) * bias_correction
    
    # 计算协方差
    cos_theta = np.cos(bearing)
    sin_theta = np.sin(bearing)
    cosh_var = np.cosh(bearing_var)
    sinh_var = np.sinh(bearing_var)
    
    var_x = (range_**2 * np.exp(-bearing_var) * 
             (cos_theta**2 * (cosh_var - 1) + sin_theta**2 * sinh_var) +
             range_var * np.exp(-bearing_var) * cos_theta**2 * (cosh_var + sinh_var))
    
    var_y = (range_**2 * np.exp(-bearing_var) * 
             (sin_theta**2 * (cosh_var - 1) + cos_theta**2 * sinh_var) +
             range_var * np.exp(-bearing_var) * sin_theta**2 * (cosh_var + sinh_var))
    
    cov_xy = (sin_theta * cos_theta * np.exp(-bearing_var) * 
              ((range_**2 + range_var) * (1 - np.exp(-bearing_var)) - range_**2))
    
    cov = np.array([[var_x, cov_xy], [cov_xy, var_y]])
    
    return np.array([x_unbiased, y_unbiased]), cov

def polar_to_cartesian_linearized(range_, bearing, range_var, bearing_var, radar_pos=(0, 0)):
    """
    极坐标到笛卡尔坐标的线性化转换
    
    参数:
        range_: 距离
        bearing: 方位角(弧度)
        range_var: 距离方差
        bearing_var: 方位角方差
        radar_pos: 雷达位置 (x, y)
        
    返回:
        x, y: 线性化估计的笛卡尔坐标
        cov: 协方差矩阵
    """
    # 线性化转换
    x = radar_pos[0] + range_ * np.cos(bearing)
    y = radar_pos[1] + range_ * np.sin(bearing)
    
    # 计算雅可比矩阵
    cos_theta = np.cos(bearing)
    sin_theta = np.sin(bearing)
    
    J = np.array([
        [cos_theta, -range_ * sin_theta],
        [sin_theta, range_ * cos_theta]
    ])
    
    # 极坐标协方差矩阵
    R_polar = np.diag([range_var, bearing_var])
    
    # 通过误差传播计算笛卡尔坐标协方差
    cov = J @ R_polar @ J.T
    
    return np.array([x, y]), cov

def generate_radar_measurements(trajectory, radar_pos=(0, 0), 
                               range_noise_std=5.0, bearing_noise_std=0.01):
    """
    生成雷达测量值(极坐标)
    
    参数:
        trajectory: 真实轨迹,形状为 (n, 4) 或 (n, 2),包含位置
        radar_pos: 雷达位置
        range_noise_std: 距离噪声标准差
        bearing_noise_std: 方位角噪声标准差
        
    返回:
        measurements_polar: 极坐标测量值 (range, bearing)
        measurements_simple: 简单三角转换的笛卡尔坐标
        measurements_unbiased: 去偏转换的笛卡尔坐标
        measurements_linearized: 线性化转换的笛卡尔坐标
        covariances: 各转换方法的协方差矩阵
    """
    n_points = trajectory.shape[0]
    
    # 确保轨迹包含位置信息
    if trajectory.shape[1] >= 2:
        positions = trajectory[:, :2]
    else:
        raise ValueError("轨迹必须至少包含位置信息")
    
    # 初始化数组
    measurements_polar = np.zeros((n_points, 2))  # (range, bearing)
    measurements_simple = np.zeros((n_points, 2))  # (x, y)
    measurements_unbiased = np.zeros((n_points, 2))  # (x, y)
    measurements_linearized = np.zeros((n_points, 2))  # (x, y)
    
    # 协方差数组
    cov_simple = np.zeros((n_points, 2, 2))
    cov_unbiased = np.zeros((n_points, 2, 2))
    cov_linearized = np.zeros((n_points, 2, 2))
    
    # 噪声参数
    range_var = range_noise_std**2
    bearing_var = bearing_noise_std**2
    
    for i in range(n_points):
        # 计算相对于雷达的位置
        dx = positions[i, 0] - radar_pos[0]
        dy = positions[i, 1] - radar_pos[1]
        
        # 计算真实极坐标
        true_range = np.sqrt(dx**2 + dy**2)
        true_bearing = np.arctan2(dy, dx)
        
        # 添加噪声
        measured_range = true_range + np.random.randn() * range_noise_std
        measured_bearing = true_bearing + np.random.randn() * bearing_noise_std
        
        # 归一化方位角到 [-π, π]
        measured_bearing = (measured_bearing + np.pi) % (2 * np.pi) - np.pi
        
        measurements_polar[i] = [measured_range, measured_bearing]
        
        # 1. 简单三角转换
        x_simple, y_simple = polar_to_cartesian(measured_range, measured_bearing, radar_pos)
        measurements_simple[i] = [x_simple, y_simple]
        
        # 简单转换的协方差(近似)
        cos_theta = np.cos(measured_bearing)
        sin_theta = np.sin(measured_bearing)
        cov_simple[i, 0, 0] = cos_theta**2 * range_var + (measured_range*sin_theta)**2 * bearing_var
        cov_simple[i, 1, 1] = sin_theta**2 * range_var + (measured_range*cos_theta)**2 * bearing_var
        cov_simple[i, 0, 1] = cos_theta*sin_theta * range_var - measured_range**2 * cos_theta*sin_theta * bearing_var
        cov_simple[i, 1, 0] = cov_simple[i, 0, 1]
        
        # 2. 去偏转换
        unbiased_pos, cov_unb = polar_to_cartesian_unbiased(
            measured_range, measured_bearing, range_var, bearing_var, radar_pos
        )
        measurements_unbiased[i] = unbiased_pos
        cov_unbiased[i] = cov_unb
        
        # 3. 线性化转换
        linearized_pos, cov_lin = polar_to_cartesian_linearized(
            measured_range, measured_bearing, range_var, bearing_var, radar_pos
        )
        measurements_linearized[i] = linearized_pos
        cov_linearized[i] = cov_lin
    
    return (measurements_polar, measurements_simple, measurements_unbiased, 
            measurements_linearized, cov_simple, cov_unbiased, cov_linearized)

def compare_conversion_methods():
    """
    比较三种坐标转换方法的性能
    """
    np.random.seed(42)
    
    # 生成一个机动目标轨迹
    num_steps = 200
    dt = 1.0
    
    # 轨迹:开始直线,然后转弯,最后加速
    trajectory = np.zeros((num_steps, 4))  # [x, y, vx, vy]
    
    # 初始状态
    x, y = 1000, 500
    vx, vy = 20, 10
    
    for t in range(num_steps):
        # 分段机动
        if t < 50:
            # 匀速直线
            pass
        elif t < 100:
            # 转弯
            vx, vy = vx - 0.1*vy, vy + 0.1*vx
        elif t < 150:
            # 加速
            vx, vy = vx * 1.01, vy * 1.01
        else:
            # 减速
            vx, vy = vx * 0.99, vy * 0.99
        
        # 更新位置
        x += vx * dt
        y += vy * dt
        
        trajectory[t] = [x, y, vx, vy]
    
    # 雷达参数
    radar_pos = (0, 0)
    range_noise_std = 10.0
    bearing_noise_std = 0.005  # 约0.286度
    
    # 生成雷达测量
    print("生成雷达测量数据...")
    (measurements_polar, measurements_simple, measurements_unbiased,
     measurements_linearized, cov_simple, cov_unbiased, cov_linearized) = generate_radar_measurements(
        trajectory, radar_pos, range_noise_std, bearing_noise_std
    )
    
    # 初始化滤波器(使用CV模型)
    filters = {
        'simple': CVFilter(dt=dt, q=0.1),
        'unbiased': CVFilter(dt=dt, q=0.1),
        'linearized': CVFilter(dt=dt, q=0.1)
    }
    
    # 对应的测量和协方差
    measurement_sets = {
        'simple': measurements_simple,
        'unbiased': measurements_unbiased,
        'linearized': measurements_linearized
    }
    
    covariance_sets = {
        'simple': cov_simple,
        'unbiased': cov_unbiased,
        'linearized': cov_linearized
    }
    
    # 运行跟踪
    estimates = {name: [] for name in filters}
    position_errors = {name: [] for name in filters}
    
    for name, filter_obj in filters.items():
        print(f"运行{name}转换的跟踪...")
        
        # 获取对应的测量和协方差
        measurements = measurement_sets[name]
        covariances = covariance_sets[name]
        
        # 初始化状态
        filter_obj.x[0, 0] = measurements[0, 0]
        filter_obj.x[1, 0] = measurements[0, 1]
        
        for t in range(num_steps):
            # 预测
            filter_obj.predict()
            
            # 设置观测噪声协方差
            R = covariances[t]
            
            # 更新
            z = measurements[t, :2]
            x_est, _ = filter_obj.update(z, R)
            estimates[name].append(x_est.flatten())
            
            # 计算位置误差
            pos_error = np.sqrt((x_est[0, 0] - trajectory[t, 0])**2 + 
                               (x_est[1, 0] - trajectory[t, 1])**2)
            position_errors[name].append(pos_error)
    
    # 转换结果为数组
    for name in estimates:
        estimates[name] = np.array(estimates[name])
    
    # 计算性能指标
    performance = {}
    for name in filters:
        errors = position_errors[name]
        performance[name] = {
            'rmse': np.sqrt(np.mean(np.array(errors)**2)),
            'mae': np.mean(np.abs(errors)),
            'max_error': np.max(np.abs(errors)),
            'std_error': np.std(errors)
        }
    
    # 可视化结果
    fig, axes = plt.subplots(2, 3, figsize=(18, 12))
    
    # 1. 轨迹对比
    ax = axes[0, 0]
    ax.plot(trajectory[:, 0], trajectory[:, 1], 'k-', linewidth=3, 
            label='真实轨迹', alpha=0.8)
    
    colors = {'simple': 'blue', 'unbiased': 'red', 'linearized': 'green'}
    for name in filters:
        ax.plot(estimates[name][:, 0], estimates[name][:, 1], 
                '-', linewidth=1.5, color=colors[name], label=f'{name}跟踪')
    
    ax.plot(radar_pos[0], radar_pos[1], 'r*', markersize=15, label='雷达位置')
    ax.set_xlabel('X位置 (m)')
    ax.set_ylabel('Y位置 (m)')
    ax.set_title('轨迹跟踪对比')
    ax.legend(loc='upper right')
    ax.grid(True, alpha=0.3)
    ax.axis('equal')
    
    # 2. 位置误差对比
    ax = axes[0, 1]
    t = np.arange(num_steps)
    
    for name in filters:
        ax.plot(t, position_errors[name], '-', linewidth=1, color=colors[name], 
                label=f'{name} (RMSE={performance[name]["rmse"]:.2f}m)')
    
    ax.set_xlabel('时间步')
    ax.set_ylabel('位置误差 (m)')
    ax.set_title('位置误差对比')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 3. 累积误差分布
    ax = axes[0, 2]
    
    for name, color in colors.items():
        errors_sorted = np.sort(position_errors[name])
        cdf = np.arange(1, len(errors_sorted) + 1) / len(errors_sorted)
        ax.plot(errors_sorted, cdf, '-', linewidth=2, color=color, 
                label=f'{name}')
    
    ax.set_xlabel('位置误差 (m)')
    ax.set_ylabel('累积概率')
    ax.set_title('累积误差分布函数 (CDF)')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 4. 滑动窗口误差分析
    ax = axes[1, 0]
    
    window_size = 20
    for name, color in colors.items():
        errors = position_errors[name]
        sliding_rmse = []
        for i in range(len(errors) - window_size + 1):
            window_errors = errors[i:i+window_size]
            sliding_rmse.append(np.sqrt(np.mean(np.array(window_errors)**2)))
        
        t_sliding = np.arange(len(sliding_rmse))
        ax.plot(t_sliding, sliding_rmse, '-', linewidth=1.5, color=color, 
                label=f'{name}')
    
    ax.set_xlabel('时间窗口中心')
    ax.set_ylabel(f'滑动RMSE (窗口大小={window_size})')
    ax.set_title('滑动窗口误差分析')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 5. 误差分布直方图
    ax = axes[1, 1]
    
    max_error = max([max(position_errors[name]) for name in filters])
    bins = np.linspace(0, max_error, 30)
    
    for name, color in colors.items():
        ax.hist(position_errors[name], bins=bins, alpha=0.5, color=color, 
                density=True, label=name)
    
    ax.set_xlabel('位置误差 (m)')
    ax.set_ylabel('概率密度')
    ax.set_title('误差分布直方图')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 6. 性能对比表
    ax = axes[1, 2]
    ax.axis('off')
    
    # 创建表格
    cell_text = []
    for name in filters:
        perf = performance[name]
        cell_text.append([name, f"{perf['rmse']:.2f}", f"{perf['mae']:.2f}", 
                         f"{perf['std_error']:.2f}", f"{perf['max_error']:.2f}"])
    
    # 计算相对于简单转换的改善比例
    baseline_rmse = performance['simple']['rmse']
    for name in ['unbiased', 'linearized']:
        improvement = (baseline_rmse - performance[name]['rmse']) / baseline_rmse * 100
        cell_text.append([f'{name}改善', f"{improvement:.1f}%", "", "", ""])
    
    columns = ['方法', 'RMSE (m)', 'MAE (m)', 'Std (m)', 'Max (m)']
    table = ax.table(cellText=cell_text, colLabels=columns, 
                     cellLoc='center', loc='center',
                     colColours=['#f0f0f0']*5)
    
    table.auto_set_font_size(False)
    table.set_fontsize(10)
    table.scale(1, 1.5)
    
    # 设置改善行的颜色
    table[(len(filters)+1, 0)].set_facecolor('#e0ffe0')
    table[(len(filters)+2, 0)].set_facecolor('#e0ffe0')
    
    ax.set_title('性能指标对比')
    
    plt.suptitle('极坐标转换方法在跟踪中的性能对比', fontsize=16, y=1.02)
    plt.tight_layout()
    plt.savefig('tracking_conversion_comparison.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    # 打印性能统计
    print("\n" + "="*60)
    print("跟踪性能对比")
    print("="*60)
    print(f"{'方法':<15} {'RMSE (m)':<12} {'MAE (m)':<12} {'Std (m)':<12} {'Max (m)':<12}")
    print("-"*60)
    
    for name in filters:
        perf = performance[name]
        print(f"{name:<15} {perf['rmse']:<12.2f} {perf['mae']:<12.2f} "
              f"{perf['std_error']:<12.2f} {perf['max_error']:<12.2f}")
    
    print("="*60)
    
    return {
        'trajectory': trajectory,
        'measurements_polar': measurements_polar,
        'measurements_simple': measurements_simple,
        'measurements_unbiased': measurements_unbiased,
        'measurements_linearized': measurements_linearized,
        'estimates': estimates,
        'performance': performance
    }

def analyze_conversion_bias():
    """
    分析坐标转换的偏差特性
    """
    np.random.seed(42)
    
    # 固定距离,变化方位角
    ranges = np.array([100, 500, 1000, 2000])
    bearings = np.linspace(-np.pi, np.pi, 100)
    
    # 噪声参数
    range_noise_std = 10.0
    bearing_noise_std = 0.01
    range_var = range_noise_std**2
    bearing_var = bearing_noise_std**2
    
    # 雷达位置
    radar_pos = (0, 0)
    
    # 蒙特卡洛模拟
    num_trials = 1000
    
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    
    for idx, range_val in enumerate(ranges):
        ax = axes[idx // 2, idx % 2]
        
        simple_biases = []
        unbiased_biases = []
        linearized_biases = []
        
        for bearing in bearings:
            # 真实位置
            true_x = radar_pos[0] + range_val * np.cos(bearing)
            true_y = radar_pos[1] + range_val * np.sin(bearing)
            
            simple_errors = []
            unbiased_errors = []
            linearized_errors = []
            
            for _ in range(num_trials):
                # 生成带噪声的测量
                noisy_range = range_val + np.random.randn() * range_noise_std
                noisy_bearing = bearing + np.random.randn() * bearing_noise_std
                
                # 简单转换
                simple_x, simple_y = polar_to_cartesian(noisy_range, noisy_bearing, radar_pos)
                simple_error = np.sqrt((simple_x - true_x)**2 + (simple_y - true_y)**2)
                simple_errors.append(simple_error)
                
                # 去偏转换
                unbiased_pos, _ = polar_to_cartesian_unbiased(noisy_range, noisy_bearing, 
                                                             range_var, bearing_var, radar_pos)
                unbiased_error = np.sqrt((unbiased_pos[0] - true_x)**2 + (unbiased_pos[1] - true_y)**2)
                unbiased_errors.append(unbiased_error)
                
                # 线性化转换
                linearized_pos, _ = polar_to_cartesian_linearized(noisy_range, noisy_bearing, 
                                                                 range_var, bearing_var, radar_pos)
                linearized_error = np.sqrt((linearized_pos[0] - true_x)**2 + (linearized_pos[1] - true_y)**2)
                linearized_errors.append(linearized_error)
            
            # 计算平均偏差
            simple_biases.append(np.mean(simple_errors))
            unbiased_biases.append(np.mean(unbiased_errors))
            linearized_biases.append(np.mean(linearized_errors))
        
        # 绘制偏差随方位角的变化
        ax.plot(np.degrees(bearings), simple_biases, 'b-', linewidth=2, label='简单转换')
        ax.plot(np.degrees(bearings), unbiased_biases, 'r-', linewidth=2, label='去偏转换')
        ax.plot(np.degrees(bearings), linearized_biases, 'g-', linewidth=2, label='线性化转换')
        
        ax.set_xlabel('方位角 (度)')
        ax.set_ylabel('平均位置误差 (m)')
        ax.set_title(f'距离 = {range_val}m')
        ax.legend()
        ax.grid(True, alpha=0.3)
        
        # 添加理论偏差线
        if idx == 0:
            # 计算理论偏差(近似)
            theoretical_bias_simple = range_val * np.sqrt(2 * (1 - np.exp(-bearing_var/2)))
            ax.axhline(y=theoretical_bias_simple, color='b', linestyle='--', 
                      alpha=0.5, label='理论偏差(简单)')
            
            theoretical_bias_unbiased = 0
            ax.axhline(y=theoretical_bias_unbiased, color='r', linestyle='--', 
                      alpha=0.5, label='理论偏差(去偏)')
            
            theoretical_bias_linearized = 0
            ax.axhline(y=theoretical_bias_linearized, color='g', linestyle='--', 
                      alpha=0.5, label='理论偏差(线性化)')
            ax.legend()
    
    plt.suptitle('坐标转换偏差分析 (蒙特卡洛模拟)', fontsize=16, y=1.02)
    plt.tight_layout()
    plt.savefig('conversion_bias_analysis.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    # 计算总体统计
    print("\n" + "="*60)
    print("坐标转换偏差统计")
    print("="*60)
    
    for range_val in ranges:
        # 选择中间方位角的结果
        mid_idx = len(bearings) // 2
        bearing = bearings[mid_idx]
        
        # 真实位置
        true_x = radar_pos[0] + range_val * np.cos(bearing)
        true_y = radar_pos[1] + range_val * np.sin(bearing)
        
        simple_errors = []
        unbiased_errors = []
        linearized_errors = []
        
        for _ in range(num_trials):
            # 生成带噪声的测量
            noisy_range = range_val + np.random.randn() * range_noise_std
            noisy_bearing = bearing + np.random.randn() * bearing_noise_std
            
            # 简单转换
            simple_x, simple_y = polar_to_cartesian(noisy_range, noisy_bearing, radar_pos)
            simple_error = np.sqrt((simple_x - true_x)**2 + (simple_y - true_y)**2)
            simple_errors.append(simple_error)
            
            # 去偏转换
            unbiased_pos, _ = polar_to_cartesian_unbiased(noisy_range, noisy_bearing, 
                                                         range_var, bearing_var, radar_pos)
            unbiased_error = np.sqrt((unbiased_pos[0] - true_x)**2 + (unbiased_pos[1] - true_y)**2)
            unbiased_errors.append(unbiased_error)
            
            # 线性化转换
            linearized_pos, _ = polar_to_cartesian_linearized(noisy_range, noisy_bearing, 
                                                             range_var, bearing_var, radar_pos)
            linearized_error = np.sqrt((linearized_pos[0] - true_x)**2 + (linearized_pos[1] - true_y)**2)
            linearized_errors.append(linearized_error)
        
        simple_mean = np.mean(simple_errors)
        unbiased_mean = np.mean(unbiased_errors)
        linearized_mean = np.mean(linearized_errors)
        
        simple_std = np.std(simple_errors)
        unbiased_std = np.std(unbiased_errors)
        linearized_std = np.std(linearized_errors)
        
        print(f"距离 {range_val:4d}m:")
        print(f"  简单转换: 均值={simple_mean:6.2f}m, 标准差={simple_std:6.2f}m")
        print(f"  去偏转换: 均值={unbiased_mean:6.2f}m, 标准差={unbiased_std:6.2f}m, "
              f"改善={(simple_mean-unbiased_mean)/simple_mean*100:5.1f}%")
        print(f"  线性化转换: 均值={linearized_mean:6.2f}m, 标准差={linearized_std:6.2f}m, "
              f"改善={(simple_mean-linearized_mean)/simple_mean*100:5.1f}%")
        print()
    
    print("="*60)

def analyze_covariance_matching():
    """
    分析协方差匹配程度
    """
    np.random.seed(42)
    
    # 生成测试点
    num_points = 10000
    ranges = np.random.uniform(100, 2000, num_points)
    bearings = np.random.uniform(-np.pi, np.pi, num_points)
    
    # 噪声参数
    range_noise_std = 10.0
    bearing_noise_std = 0.01
    range_var = range_noise_std**2
    bearing_var = bearing_noise_std**2
    
    # 雷达位置
    radar_pos = (0, 0)
    
    # 存储结果
    simple_errors = []
    unbiased_errors = []
    linearized_errors = []
    
    simple_innovation_norm = []
    unbiased_innovation_norm = []
    linearized_innovation_norm = []
    
    for i in range(num_points):
        # 真实位置
        true_x = radar_pos[0] + ranges[i] * np.cos(bearings[i])
        true_y = radar_pos[1] + ranges[i] * np.sin(bearings[i])
        
        # 生成带噪声的测量
        noisy_range = ranges[i] + np.random.randn() * range_noise_std
        noisy_bearing = bearings[i] + np.random.randn() * bearing_noise_std
        
        # 各种转换
        # 简单转换
        simple_x, simple_y = polar_to_cartesian(noisy_range, noisy_bearing, radar_pos)
        
        # 简单转换的协方差
        cos_theta = np.cos(noisy_bearing)
        sin_theta = np.sin(noisy_bearing)
        cov_simple = np.array([
            [cos_theta**2 * range_var + (noisy_range*sin_theta)**2 * bearing_var,
             cos_theta*sin_theta * range_var - noisy_range**2 * cos_theta*sin_theta * bearing_var],
            [cos_theta*sin_theta * range_var - noisy_range**2 * cos_theta*sin_theta * bearing_var,
             sin_theta**2 * range_var + (noisy_range*cos_theta)**2 * bearing_var]
        ])
        
        # 去偏转换
        unbiased_pos, cov_unb = polar_to_cartesian_unbiased(
            noisy_range, noisy_bearing, range_var, bearing_var, radar_pos
        )
        
        # 线性化转换
        linearized_pos, cov_lin = polar_to_cartesian_linearized(
            noisy_range, noisy_bearing, range_var, bearing_var, radar_pos
        )
        
        # 计算误差
        simple_error = np.array([simple_x - true_x, simple_y - true_y])
        unbiased_error = np.array([unbiased_pos[0] - true_x, unbiased_pos[1] - true_y])
        linearized_error = np.array([linearized_pos[0] - true_x, linearized_pos[1] - true_y])
        
        simple_errors.append(simple_error)
        unbiased_errors.append(unbiased_error)
        linearized_errors.append(linearized_error)
        
        # 计算新息归一化平方
        if np.linalg.matrix_rank(cov_simple) == 2:
            simple_inn = simple_error @ np.linalg.inv(cov_simple) @ simple_error
            simple_innovation_norm.append(simple_inn)
        
        if np.linalg.matrix_rank(cov_unb) == 2:
            unbiased_inn = unbiased_error @ np.linalg.inv(cov_unb) @ unbiased_error
            unbiased_innovation_norm.append(unbiased_inn)
        
        if np.linalg.matrix_rank(cov_lin) == 2:
            linearized_inn = linearized_error @ np.linalg.inv(cov_lin) @ linearized_error
            linearized_innovation_norm.append(linearized_inn)
    
    simple_errors = np.array(simple_errors)
    unbiased_errors = np.array(unbiased_errors)
    linearized_errors = np.array(linearized_errors)
    
    # 计算样本协方差
    sample_cov_simple = np.cov(simple_errors.T)
    sample_cov_unbiased = np.cov(unbiased_errors.T)
    sample_cov_linearized = np.cov(linearized_errors.T)
    
    # 计算理论协方差(平均)
    avg_cov_simple = np.zeros((2, 2))
    avg_cov_unbiased = np.zeros((2, 2))
    avg_cov_linearized = np.zeros((2, 2))
    
    # 重新计算平均理论协方差
    for i in range(num_points):
        noisy_range = ranges[i] + np.random.randn() * range_noise_std
        noisy_bearing = bearings[i] + np.random.randn() * bearing_noise_std
        
        cos_theta = np.cos(noisy_bearing)
        sin_theta = np.sin(noisy_bearing)
        avg_cov_simple += np.array([
            [cos_theta**2 * range_var + (noisy_range*sin_theta)**2 * bearing_var,
             cos_theta*sin_theta * range_var - noisy_range**2 * cos_theta*sin_theta * bearing_var],
            [cos_theta*sin_theta * range_var - noisy_range**2 * cos_theta*sin_theta * bearing_var,
             sin_theta**2 * range_var + (noisy_range*cos_theta)**2 * bearing_var]
        ])
        
        unbiased_pos, cov_unb = polar_to_cartesian_unbiased(
            noisy_range, noisy_bearing, range_var, bearing_var, radar_pos
        )
        avg_cov_unbiased += cov_unb
        
        linearized_pos, cov_lin = polar_to_cartesian_linearized(
            noisy_range, noisy_bearing, range_var, bearing_var, radar_pos
        )
        avg_cov_linearized += cov_lin
    
    avg_cov_simple /= num_points
    avg_cov_unbiased /= num_points
    avg_cov_linearized /= num_points
    
    # 可视化
    fig, axes = plt.subplots(2, 3, figsize=(18, 12))
    
    methods = ['simple', 'unbiased', 'linearized']
    titles = ['简单转换', '去偏转换', '线性化转换']
    cov_samples = [sample_cov_simple, sample_cov_unbiased, sample_cov_linearized]
    cov_theory = [avg_cov_simple, avg_cov_unbiased, avg_cov_linearized]
    
    for i, (method, title) in enumerate(zip(methods, titles)):
        # 样本协方差热图
        ax = axes[0, i]
        im = ax.imshow(cov_samples[i], cmap='hot', interpolation='nearest')
        ax.set_xticks([0, 1])
        ax.set_yticks([0, 1])
        ax.set_xticklabels(['x', 'y'])
        ax.set_yticklabels(['x', 'y'])
        ax.set_title(f'{title} - 样本协方差')
        plt.colorbar(im, ax=ax)
        
        # 添加数值
        for j in range(2):
            for k in range(2):
                ax.text(k, j, f'{cov_samples[i][j, k]:.1f}', 
                       ha='center', va='center', color='white')
    
    for i, (method, title) in enumerate(zip(methods, titles)):
        # 理论协方差热图
        ax = axes[1, i]
        im = ax.imshow(cov_theory[i], cmap='hot', interpolation='nearest')
        ax.set_xticks([0, 1])
        ax.set_yticks([0, 1])
        ax.set_xticklabels(['x', 'y'])
        ax.set_yticklabels(['x', 'y'])
        ax.set_title(f'{title} - 平均理论协方差')
        plt.colorbar(im, ax=ax)
        
        # 添加数值
        for j in range(2):
            for k in range(2):
                ax.text(k, j, f'{cov_theory[i][j, k]:.1f}', 
                       ha='center', va='center', color='white')
    
    plt.suptitle('协方差匹配分析', fontsize=16, y=1.02)
    plt.tight_layout()
    plt.savefig('covariance_matching_analysis.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    # 绘制新息归一化平方的分布
    fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    
    inn_data = [simple_innovation_norm, unbiased_innovation_norm, linearized_innovation_norm]
    
    for i, (method, title, data) in enumerate(zip(methods, titles, inn_data)):
        ax = axes[i]
        if len(data) > 0:
            ax.hist(data, bins=50, density=True, alpha=0.7, color='blue', edgecolor='black')
            
            # 添加卡方分布(2自由度)参考线
            x = np.linspace(0, max(data), 100)
            y = stats.chi2.pdf(x, df=2)
            ax.plot(x, y, 'r-', linewidth=2, label='χ²(2)')
            
            # 计算统计量
            mean_inn = np.mean(data)
            std_inn = np.std(data)
            
            ax.set_xlabel('归一化新息平方')
            ax.set_ylabel('概率密度')
            ax.set_title(f'{title}\n均值={mean_inn:.3f}, 标准差={std_inn:.3f}')
            ax.legend()
            ax.grid(True, alpha=0.3)
        else:
            ax.text(0.5, 0.5, '数据不足', ha='center', va='center', transform=ax.transAxes)
    
    plt.suptitle('归一化新息平方分布 (应近似χ²(2))', fontsize=16, y=1.02)
    plt.tight_layout()
    plt.savefig('innovation_analysis.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    # 打印协方差匹配统计
    print("\n" + "="*60)
    print("协方差匹配统计")
    print("="*60)
    
    for i, (method, title) in enumerate(zip(methods, titles)):
        diff = np.linalg.norm(cov_samples[i] - cov_theory[i], 'fro')
        print(f"{title}:")
        print(f"  样本协方差与理论协方差的Frobenius范数差异: {diff:.4f}")
        print(f"  样本协方差迹: {np.trace(cov_samples[i]):.2f}")
        print(f"  理论协方差迹: {np.trace(cov_theory[i]):.2f}")
        print(f"  迹差异: {abs(np.trace(cov_samples[i]) - np.trace(cov_theory[i])):.2f}")
        print()

if __name__ == "__main__":
    print("="*60)
    print("极坐标到笛卡尔坐标转换跟踪演示")
    print("="*60)
    
    np.random.seed(42)
    
    # 1. 比较坐标转换方法
    print("\n1. 比较坐标转换方法在跟踪中的性能...")
    tracking_results = compare_conversion_methods()
    
    # 2. 分析转换偏差
    print("\n2. 分析坐标转换偏差...")
    analyze_conversion_bias()
    
    # 3. 分析协方差匹配
    print("\n3. 分析协方差匹配...")
    analyze_covariance_matching()
    
    print("\n演示完成!")
    
    # 总结
    print("\n" + "="*60)
    print("主要结论")
    print("="*60)
    print("1. 简单三角转换: 计算简单,但有偏差,特别是在方位角噪声较大时")
    print("2. 去偏转换: 修正了偏差,提高估计精度,但计算较复杂")
    print("3. 线性化转换: 基于误差传播,能提供较好的协方差估计")
    print("4. 在跟踪任务中,正确的协方差估计对滤波性能至关重要")
    print("5. 实际应用中应根据精度要求和计算资源选择合适的转换方法")
    print("="*60)

8. Demo 3-3:交互多模型滤波实现

这个Demo将展示交互多模型(IMM)滤波的实现,并比较IMM与单一模型在机动目标跟踪中的性能。

python 复制代码
"""
demo_3_3_interacting_multiple_model.py
交互多模型滤波实现
"""

import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from demo_3_1_motion_models_comparison import CVFilter, CAFilter, CTRVFilter

class ModelResult:
    """模型滤波结果容器"""
    
    def __init__(self, x, P, z_pred, S, likelihood):
        self.x = x  # 状态估计
        self.P = P  # 协方差矩阵
        self.z_pred = z_pred  # 预测观测
        self.S = S  # 新息协方差
        self.likelihood = likelihood  # 模型似然

class IMMFilter:
    """
    交互多模型滤波器
    支持CV、CA、CTRV三种模型的混合
    """
    
    def __init__(self, models, model_prob, trans_prob, dt=1.0):
        """
        初始化IMM滤波器
        
        参数:
            models: 模型名称列表,如 ['CV', 'CA', 'CTRV']
            model_prob: 初始模型概率
            trans_prob: 模型转移概率矩阵
            dt: 时间间隔
        """
        self.models = models
        self.num_models = len(models)
        self.mu = np.array(model_prob)  # 模型概率
        self.pi = np.array(trans_prob)  # 转移概率矩阵
        self.dt = dt
        
        # 检查维度
        assert len(model_prob) == self.num_models, "模型概率维度不匹配"
        assert trans_prob.shape == (self.num_models, self.num_models), "转移概率矩阵维度不匹配"
        
        # 初始化滤波器
        self.filters = []
        for model in models:
            if model == 'CV':
                self.filters.append(CVFilter(dt=dt, q=0.1))
            elif model == 'CA':
                self.filters.append(CAFilter(dt=dt, q=0.1))
            elif model == 'CTRV':
                self.filters.append(CTRVFilter(dt=dt, q_pos=0.1, q_vel=0.01, q_yaw=0.1, q_yawrate=0.01))
            else:
                raise ValueError(f"未知模型: {model}")
        
        # 状态维度(假设所有滤波器状态维度相同)
        self.state_dim = self.filters[0].state_dim
        
        # 存储各模型的结果
        self.model_results = [None] * self.num_models
        
        # 历史记录
        self.history = {
            'x_fused': [],
            'P_fused': [],
            'model_prob': [],
            'model_estimates': [],
            'model_covariances': []
        }
    
    def init_state(self, x0, P0=None):
        """初始化状态"""
        if P0 is None:
            P0 = np.eye(self.state_dim) * 100
        
        for filter_obj in self.filters:
            filter_obj.x = x0.copy().reshape(-1, 1)
            filter_obj.P = P0.copy()
        
        # 初始化历史记录
        self.history['x_fused'] = [x0.copy()]
        self.history['P_fused'] = [P0.copy()]
        self.history['model_prob'] = [self.mu.copy()]
        
        # 初始化各模型估计
        model_estimates = []
        model_covariances = []
        for filter_obj in self.filters:
            model_estimates.append(filter_obj.x.copy())
            model_covariances.append(filter_obj.P.copy())
        
        self.history['model_estimates'].append(model_estimates)
        self.history['model_covariances'].append(model_covariances)
    
    def step(self, z, R):
        """
        执行一步IMM滤波
        
        参数:
            z: 观测向量
            R: 观测噪声协方差
            
        返回:
            x_fused: 融合状态估计
            P_fused: 融合协方差矩阵
        """
        # 1. 交互/混合
        mixed_states, mixed_covs = self._interaction()
        
        # 2. 模型条件滤波
        model_likelihoods = np.zeros(self.num_models)
        
        for j in range(self.num_models):
            # 设置混合后的状态和协方差
            self.filters[j].x = mixed_states[j]
            self.filters[j].P = mixed_covs[j]
            
            # 执行预测
            x_pred, P_pred = self.filters[j].predict()
            
            # 计算预测观测
            H = self._get_observation_matrix(j)
            z_pred = H @ x_pred
            
            # 计算新息协方差
            S = H @ P_pred @ H.T + R
            
            # 计算模型似然
            innovation = z.reshape(-1, 1) - z_pred
            likelihood = self._compute_likelihood(innovation, S)
            model_likelihoods[j] = likelihood
            
            # 更新步骤
            x_est, P_est = self.filters[j].update(z, R)
            
            # 保存结果
            self.model_results[j] = ModelResult(x_est, P_est, z_pred, S, likelihood)
        
        # 3. 模型概率更新
        self._update_model_probabilities(model_likelihoods)
        
        # 4. 估计融合
        x_fused, P_fused = self._fusion()
        
        # 保存历史
        self.history['x_fused'].append(x_fused.copy())
        self.history['P_fused'].append(P_fused.copy())
        self.history['model_prob'].append(self.mu.copy())
        
        # 保存各模型估计
        model_estimates = []
        model_covariances = []
        for result in self.model_results:
            model_estimates.append(result.x.copy())
            model_covariances.append(result.P.copy())
        
        self.history['model_estimates'].append(model_estimates)
        self.history['model_covariances'].append(model_covariances)
        
        return x_fused, P_fused
    
    def _interaction(self):
        """交互/混合步骤"""
        # 计算混合概率
        c_bar = np.zeros(self.num_models)
        for j in range(self.num_models):
            c_bar[j] = np.sum(self.pi[:, j] * self.mu)
        
        mu_mixed = np.zeros((self.num_models, self.num_models))
        for i in range(self.num_models):
            for j in range(self.num_models):
                mu_mixed[i, j] = self.pi[i, j] * self.mu[i] / c_bar[j]
        
        # 混合状态和协方差
        mixed_states = []
        mixed_covs = []
        
        for j in range(self.num_models):
            # 初始化混合状态
            x_mixed = np.zeros((self.state_dim, 1))
            for i in range(self.num_models):
                x_mixed += mu_mixed[i, j] * self.filters[i].x
            
            # 初始化混合协方差
            P_mixed = np.zeros((self.state_dim, self.state_dim))
            for i in range(self.num_models):
                dx = self.filters[i].x - x_mixed
                P_mixed += mu_mixed[i, j] * (self.filters[i].P + dx @ dx.T)
            
            mixed_states.append(x_mixed)
            mixed_covs.append(P_mixed)
        
        return mixed_states, mixed_covs
    
    def _update_model_probabilities(self, likelihoods):
        """更新模型概率"""
        # 计算归一化常数
        c = np.sum(likelihoods * np.sum(self.pi * self.mu, axis=0))
        
        # 更新模型概率
        if c > 1e-10:  # 避免除以零
            self.mu = (likelihoods * np.sum(self.pi * self.mu, axis=0)) / c
        else:
            # 如果c太小,保持原概率
            pass
        
        # 确保概率和为1
        self.mu = self.mu / np.sum(self.mu)
    
    def _fusion(self):
        """估计融合"""
        # 初始化融合状态和协方差
        x_fused = np.zeros((self.state_dim, 1))
        for j in range(self.num_models):
            x_fused += self.mu[j] * self.model_results[j].x
        
        P_fused = np.zeros((self.state_dim, self.state_dim))
        for j in range(self.num_models):
            dx = self.model_results[j].x - x_fused
            P_fused += self.mu[j] * (self.model_results[j].P + dx @ dx.T)
        
        return x_fused, P_fused
    
    def _get_observation_matrix(self, model_idx):
        """获取观测矩阵"""
        if self.models[model_idx] in ['CV', 'CA']:
            # CV和CA模型观测位置
            if self.models[model_idx] == 'CV':
                return np.array([[1, 0, 0, 0], [0, 1, 0, 0]])
            else:  # CA
                return np.array([[1, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0]])
        else:  # CTRV
            return np.array([[1, 0, 0, 0, 0], [0, 1, 0, 0, 0]])
    
    def _compute_likelihood(self, innovation, S):
        """计算模型似然"""
        try:
            # 计算多元高斯概率密度
            n = innovation.shape[0]
            det_S = np.linalg.det(S)
            
            if det_S <= 0:
                return 1e-10
            
            likelihood = (1.0 / ((2*np.pi)**(n/2) * np.sqrt(det_S))) * \
                        np.exp(-0.5 * innovation.T @ np.linalg.inv(S) @ innovation)
            
            return likelihood.item()
        except:
            return 1e-10
    
    def get_history(self):
        """获取历史记录"""
        return self.history
    
    def get_model_probabilities(self):
        """获取当前模型概率"""
        return self.mu.copy()

def generate_maneuvering_trajectory(num_steps=300, dt=1.0):
    """
    生成机动目标轨迹
    
    轨迹包含:匀速直线 → 转弯 → 加速直线 → 减速转弯
    """
    trajectory = np.zeros((num_steps, 4))  # [x, y, vx, vy]
    
    # 初始状态
    x, y = 0, 0
    v, psi = 20, 0  # 初始速度20m/s,航向0度(向东)
    
    for t in range(num_steps):
        # 分段机动
        if t < 50:
            # 阶段1: 匀速直线
            a = 0
            psi_dot = 0
        elif t < 100:
            # 阶段2: 匀速转弯 (左转)
            a = 0
            psi_dot = 0.05  # 约2.86度/秒
        elif t < 150:
            # 阶段3: 加速直线
            a = 0.5
            psi_dot = 0
        elif t < 200:
            # 阶段4: 匀速直线
            a = 0
            psi_dot = 0
        elif t < 250:
            # 阶段5: 减速转弯 (右转)
            a = -0.3
            psi_dot = -0.03
        else:
            # 阶段6: 匀速直线
            a = 0
            psi_dot = 0
        
        # 更新状态
        v += a * dt
        psi += psi_dot * dt
        
        # 归一化航向角
        psi = (psi + np.pi) % (2*np.pi) - np.pi
        
        # 计算位置
        x += v * dt * np.cos(psi)
        y += v * dt * np.sin(psi)
        
        # 计算速度分量
        vx = v * np.cos(psi)
        vy = v * np.sin(psi)
        
        trajectory[t] = [x, y, vx, vy]
    
    return trajectory

def compare_imm_vs_single_models():
    """比较IMM与单一模型的性能"""
    np.random.seed(42)
    
    # 生成机动轨迹
    print("生成机动目标轨迹...")
    trajectory = generate_maneuvering_trajectory(num_steps=300, dt=1.0)
    
    # 添加观测噪声
    pos_noise_std = 5.0
    measurements = trajectory[:, :2] + np.random.randn(len(trajectory), 2) * pos_noise_std
    R = np.eye(2) * pos_noise_std**2
    
    # 初始化滤波器
    models = ['CV', 'CA', 'CTRV']
    
    # IMM滤波器
    model_prob = [0.33, 0.33, 0.34]  # 初始模型概率
    
    # 转移概率矩阵
    trans_prob = np.array([
        [0.95, 0.025, 0.025],  # CV -> CV, CA, CTRV
        [0.025, 0.95, 0.025],  # CA -> CV, CA, CTRV
        [0.025, 0.025, 0.95]   # CTRV -> CV, CA, CTRV
    ])
    
    imm = IMMFilter(models, model_prob, trans_prob, dt=1.0)
    
    # 单一模型滤波器
    single_filters = {}
    for model in models:
        if model == 'CV':
            single_filters[model] = CVFilter(dt=1.0, q=0.1)
        elif model == 'CA':
            single_filters[model] = CAFilter(dt=1.0, q=0.1)
        else:  # CTRV
            single_filters[model] = CTRVFilter(dt=1.0, q_pos=0.1, q_vel=0.01, q_yaw=0.1, q_yawrate=0.01)
    
    # 运行滤波
    print("运行IMM滤波...")
    imm.init_state(measurements[0].reshape(-1, 1))
    
    imm_estimates = []
    imm_position_errors = []
    imm_model_probs = []
    
    for t in range(len(measurements)):
        z = measurements[t, :2]
        x_est, _ = imm.step(z, R)
        imm_estimates.append(x_est.flatten())
        
        # 计算位置误差
        pos_error = np.sqrt((x_est[0, 0] - trajectory[t, 0])**2 + 
                           (x_est[1, 0] - trajectory[t, 1])**2)
        imm_position_errors.append(pos_error)
        
        # 保存模型概率
        imm_model_probs.append(imm.get_model_probabilities().copy())
    
    imm_estimates = np.array(imm_estimates)
    imm_model_probs = np.array(imm_model_probs)
    
    # 运行单一模型滤波
    print("运行单一模型滤波...")
    single_estimates = {model: [] for model in models}
    single_position_errors = {model: [] for model in models}
    
    for model, filter_obj in single_filters.items():
        print(f"  运行{model}滤波...")
        # 初始化状态
        filter_obj.x[0, 0] = measurements[0, 0]
        filter_obj.x[1, 0] = measurements[0, 1]
        
        for t in range(len(measurements)):
            # 预测
            filter_obj.predict()
            
            # 更新
            z = measurements[t, :2]
            x_est, _ = filter_obj.update(z, R)
            single_estimates[model].append(x_est.flatten())
            
            # 计算位置误差
            pos_error = np.sqrt((x_est[0, 0] - trajectory[t, 0])**2 + 
                               (x_est[1, 0] - trajectory[t, 1])**2)
            single_position_errors[model].append(pos_error)
    
    for model in models:
        single_estimates[model] = np.array(single_estimates[model])
    
    # 计算性能指标
    performance = {}
    
    # IMM性能
    imm_rmse = np.sqrt(np.mean(np.array(imm_position_errors)**2))
    imm_mae = np.mean(np.abs(imm_position_errors))
    imm_max_error = np.max(np.abs(imm_position_errors))
    
    performance['IMM'] = {
        'rmse': imm_rmse,
        'mae': imm_mae,
        'max_error': imm_max_error
    }
    
    # 单一模型性能
    for model in models:
        errors = single_position_errors[model]
        performance[model] = {
            'rmse': np.sqrt(np.mean(np.array(errors)**2)),
            'mae': np.mean(np.abs(errors)),
            'max_error': np.max(np.abs(errors))
        }
    
    # 可视化结果
    fig, axes = plt.subplots(2, 3, figsize=(18, 12))
    
    # 1. 轨迹对比
    ax = axes[0, 0]
    ax.plot(trajectory[:, 0], trajectory[:, 1], 'k-', linewidth=3, 
            label='真实轨迹', alpha=0.8)
    ax.plot(measurements[:, 0], measurements[:, 1], 'y.', markersize=2, 
            alpha=0.3, label='观测值')
    
    colors = {'CV': 'blue', 'CA': 'green', 'CTRV': 'red', 'IMM': 'purple'}
    
    # 绘制单一模型轨迹
    for model in models:
        ax.plot(single_estimates[model][:, 0], single_estimates[model][:, 1], 
                '-', linewidth=1, color=colors[model], alpha=0.7, label=f'{model}')
    
    # 绘制IMM轨迹
    ax.plot(imm_estimates[:, 0], imm_estimates[:, 1], '-', linewidth=2, 
            color=colors['IMM'], label='IMM')
    
    ax.set_xlabel('X位置 (m)')
    ax.set_ylabel('Y位置 (m)')
    ax.set_title('轨迹跟踪对比')
    ax.legend(loc='upper right')
    ax.grid(True, alpha=0.3)
    ax.axis('equal')
    
    # 2. 位置误差对比
    ax = axes[0, 1]
    t = np.arange(len(trajectory))
    
    for model in models:
        ax.plot(t, single_position_errors[model], '-', linewidth=0.5, 
                color=colors[model], alpha=0.7, label=f'{model}')
    
    ax.plot(t, imm_position_errors, '-', linewidth=1.5, 
            color=colors['IMM'], label='IMM')
    
    ax.set_xlabel('时间步')
    ax.set_ylabel('位置误差 (m)')
    ax.set_title('位置误差对比')
    ax.legend()
    ax.grid(True, alpha=0.3)
    
    # 3. IMM模型概率变化
    ax = axes[0, 2]
    
    for i, model in enumerate(models):
        ax.plot(t, imm_model_probs[:, i], '-', linewidth=2, 
                color=colors[model], label=model)
    
    # 添加机动阶段背景
    ax.axvspan(0, 50, alpha=0.1, color='gray', label='阶段1: 匀速直线')
    ax.axvspan(50, 100, alpha=0.2, color='gray', label='阶段2: 转弯')
    ax.axvspan(100, 150, alpha=0.3, color='gray', label='阶段3: 加速直线')
    ax.axvspan(150, 200, alpha=0.1, color='gray', label='阶段4: 匀速直线')
    ax.axvspan(200, 250, alpha=0.2, color='gray', label='阶段5: 减速转弯')
    ax.axvspan(250, 300, alpha=0.1, color='gray', label='阶段6: 匀速直线')
    
    ax.set_xlabel('时间步')
    ax.set_ylabel('模型概率')
    ax.set_title('IMM模型概率变化')
    ax.legend(loc='upper right')
    ax.grid(True, alpha=0.3)
    ax.set_ylim([0, 1])
    
    # 4. 各阶段误差分析
    ax = axes[1, 0]
    
    # 定义阶段
    stages = [
        (0, 50, '阶段1\n匀速直线'),
        (50, 100, '阶段2\n转弯'),
        (100, 150, '阶段3\n加速直线'),
        (150, 200, '阶段4\n匀速直线'),
        (200, 250, '阶段5\n减速转弯'),
        (250, 300, '阶段6\n匀速直线')
    ]
    
    x_pos = np.arange(len(stages))
    width = 0.15
    
    for stage_idx, (start, end, stage_name) in enumerate(stages):
        # 计算各模型在该阶段的平均误差
        for i, model in enumerate(models + ['IMM']):
            if model == 'IMM':
                errors = imm_position_errors[start:end]
            else:
                errors = single_position_errors[model][start:end]
            
            stage_mae = np.mean(errors)
            
            ax.bar(stage_idx + (i-1.5)*width, stage_mae, width, 
                   color=colors[model], alpha=0.7, label=model if stage_idx==0 else "")
    
    ax.set_xlabel('运动阶段')
    ax.set_ylabel('平均位置误差 (m)')
    ax.set_title('各阶段误差分析')
    ax.set_xticks(x_pos)
    ax.set_xticklabels([stage[2] for stage in stages], rotation=15)
    ax.legend()
    ax.grid(True, alpha=0.3, axis='y')
    
    # 5. 误差分布箱线图
    ax = axes[1, 1]
    
    error_data = []
    labels = []
    
    for model in models:
        error_data.append(single_position_errors[model])
        labels.append(model)
    
    error_data.append(imm_position_errors)
    labels.append('IMM')
    
    bp = ax.boxplot(error_data, labels=labels, patch_artist=True)
    
    # 设置颜色
    for patch, label in zip(bp['boxes'], labels):
        patch.set_facecolor(colors[label])
        patch.set_alpha(0.7)
    
    ax.set_ylabel('位置误差 (m)')
    ax.set_title('误差分布箱线图')
    ax.grid(True, alpha=0.3, axis='y')
    
    # 6. 性能对比表
    ax = axes[1, 2]
    ax.axis('off')
    
    # 创建表格
    cell_text = []
    for model in models + ['IMM']:
        perf = performance[model]
        cell_text.append([model, f"{perf['rmse']:.2f}", 
                         f"{perf['mae']:.2f}", f"{perf['max_error']:.2f}"])
    
    # 计算相对于最佳单一模型的改善比例
    best_single_rmse = min([performance[model]['rmse'] for model in models])
    imm_improvement = (best_single_rmse - performance['IMM']['rmse']) / best_single_rmse * 100
    
    cell_text.append(['IMM改善', f"{imm_improvement:.1f}%", "", ""])
    
    columns = ['模型', 'RMSE (m)', 'MAE (m)', '最大误差 (m)']
    table = ax.table(cellText=cell_text, colLabels=columns, 
                     cellLoc='center', loc='center',
                     colColours=['#f0f0f0']*4)
    
    table.auto_set_font_size(False)
    table.set_fontsize(10)
    table.scale(1, 1.5)
    
    # 设置改善行的颜色
    table[(len(models)+1, 0)].set_facecolor('#e0ffe0')
    table[(len(models)+1, 1)].set_facecolor('#e0ffe0')
    table[(len(models)+1, 2)].set_facecolor('#e0ffe0')
    table[(len(models)+1, 3)].set_facecolor('#e0ffe0')
    
    ax.set_title('性能指标对比')
    
    plt.suptitle('IMM与单一模型滤波性能对比', fontsize=16, y=1.02)
    plt.tight_layout()
    plt.savefig('imm_vs_single_models.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    # 打印性能统计
    print("\n" + "="*60)
    print("滤波性能对比")
    print("="*60)
    print(f"{'模型':<10} {'RMSE (m)':<12} {'MAE (m)':<12} {'最大误差 (m)':<12}")
    print("-"*60)
    
    for model in models + ['IMM']:
        perf = performance[model]
        print(f"{model:<10} {perf['rmse']:<12.2f} {perf['mae']:<12.2f} {perf['max_error']:<12.2f}")
    
    print(f"\nIMM相对于最佳单一模型的RMSE改善: {imm_improvement:.1f}%")
    print("="*60)
    
    return {
        'trajectory': trajectory,
        'measurements': measurements,
        'imm_estimates': imm_estimates,
        'single_estimates': single_estimates,
        'imm_model_probs': imm_model_probs,
        'performance': performance
    }

def analyze_imm_parameters():
    """分析IMM参数对性能的影响"""
    np.random.seed(42)
    
    # 生成测试轨迹
    trajectory = generate_maneuvering_trajectory(num_steps=200, dt=1.0)
    
    # 添加观测噪声
    pos_noise_std = 5.0
    measurements = trajectory[:, :2] + np.random.randn(len(trajectory), 2) * pos_noise_std
    R = np.eye(2) * pos_noise_std**2
    
    # 测试不同的转移概率矩阵
    trans_prob_configs = {
        '保守': np.array([
            [0.98, 0.01, 0.01],
            [0.01, 0.98, 0.01],
            [0.01, 0.01, 0.98]
        ]),
        '平衡': np.array([
            [0.95, 0.025, 0.025],
            [0.025, 0.95, 0.025],
            [0.025, 0.025, 0.95]
        ]),
        '激进': np.array([
            [0.90, 0.05, 0.05],
            [0.05, 0.90, 0.05],
            [0.05, 0.05, 0.90]
        ])
    }
    
    # 测试不同的初始模型概率
    init_prob_configs = {
        '均匀': [0.33, 0.33, 0.34],
        '偏向CV': [0.6, 0.2, 0.2],
        '偏向CTRV': [0.2, 0.2, 0.6]
    }
    
    # 存储结果
    results = []
    
    for trans_name, trans_prob in trans_prob_configs.items():
        for init_name, init_prob in init_prob_configs.items():
            print(f"测试参数: 转移矩阵={trans_name}, 初始概率={init_name}")
            
            # 创建IMM滤波器
            models = ['CV', 'CA', 'CTRV']
            imm = IMMFilter(models, init_prob, trans_prob, dt=1.0)
            
            # 初始化状态
            imm.init_state(measurements[0].reshape(-1, 1))
            
            # 运行滤波
            position_errors = []
            for t in range(len(measurements)):
                z = measurements[t, :2]
                x_est, _ = imm.step(z, R)
                
                # 计算位置误差
                pos_error = np.sqrt((x_est[0, 0] - trajectory[t, 0])**2 + 
                                   (x_est[1, 0] - trajectory[t, 1])**2)
                position_errors.append(pos_error)
            
            # 计算性能指标
            rmse = np.sqrt(np.mean(np.array(position_errors)**2))
            mae = np.mean(np.abs(position_errors))
            max_error = np.max(np.abs(position_errors))
            
            results.append({
                'trans_name': trans_name,
                'init_name': init_name,
                'rmse': rmse,
                'mae': mae,
                'max_error': max_error
            })
    
    # 可视化参数分析结果
    fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    
    # 1. RMSE热图
    ax = axes[0]
    
    # 创建数据矩阵
    trans_names = list(trans_prob_configs.keys())
    init_names = list(init_prob_configs.keys())
    rmse_matrix = np.zeros((len(init_names), len(trans_names)))
    
    for i, init_name in enumerate(init_names):
        for j, trans_name in enumerate(trans_names):
            for result in results:
                if result['init_name'] == init_name and result['trans_name'] == trans_name:
                    rmse_matrix[i, j] = result['rmse']
                    break
    
    im = ax.imshow(rmse_matrix, cmap='YlOrRd', aspect='auto')
    ax.set_xticks(np.arange(len(trans_names)))
    ax.set_yticks(np.arange(len(init_names)))
    ax.set_xticklabels(trans_names)
    ax.set_yticklabels(init_names)
    ax.set_xlabel('转移矩阵')
    ax.set_ylabel('初始概率')
    ax.set_title('RMSE热图 (越小越好)')
    
    # 添加数值标签
    for i in range(len(init_names)):
        for j in range(len(trans_names)):
            ax.text(j, i, f'{rmse_matrix[i, j]:.2f}', 
                   ha='center', va='center', color='black')
    
    plt.colorbar(im, ax=ax)
    
    # 2. 参数对MAE的影响
    ax = axes[1]
    
    x = np.arange(len(trans_names))
    width = 0.25
    
    for idx, init_name in enumerate(init_names):
        mae_values = []
        for trans_name in trans_names:
            for result in results:
                if result['init_name'] == init_name and result['trans_name'] == trans_name:
                    mae_values.append(result['mae'])
                    break
        
        ax.bar(x + (idx-1)*width, mae_values, width, 
               label=f'初始概率={init_name}', alpha=0.7)
    
    ax.set_xlabel('转移矩阵')
    ax.set_ylabel('MAE (m)')
    ax.set_title('参数对MAE的影响')
    ax.set_xticks(x)
    ax.set_xticklabels(trans_names)
    ax.legend()
    ax.grid(True, alpha=0.3, axis='y')
    
    # 3. 参数对最大误差的影响
    ax = axes[2]
    
    for idx, init_name in enumerate(init_names):
        max_errors = []
        for trans_name in trans_names:
            for result in results:
                if result['init_name'] == init_name and result['trans_name'] == trans_name:
                    max_errors.append(result['max_error'])
                    break
        
        ax.bar(x + (idx-1)*width, max_errors, width, 
               label=f'初始概率={init_name}', alpha=0.7)
    
    ax.set_xlabel('转移矩阵')
    ax.set_ylabel('最大误差 (m)')
    ax.set_title('参数对最大误差的影响')
    ax.set_xticks(x)
    ax.set_xticklabels(trans_names)
    ax.legend()
    ax.grid(True, alpha=0.3, axis='y')
    
    plt.suptitle('IMM参数敏感性分析', fontsize=16, y=1.02)
    plt.tight_layout()
    plt.savefig('imm_parameter_analysis.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    # 打印参数分析结果
    print("\n" + "="*60)
    print("IMM参数敏感性分析结果")
    print("="*60)
    print(f"{'初始概率':<10} {'转移矩阵':<10} {'RMSE':<10} {'MAE':<10} {'最大误差':<10}")
    print("-"*60)
    
    for result in results:
        print(f"{result['init_name']:<10} {result['trans_name']:<10} "
              f"{result['rmse']:<10.2f} {result['mae']:<10.2f} {result['max_error']:<10.2f}")
    
    print("="*60)
    
    return results

def analyze_model_switching():
    """分析模型切换特性"""
    np.random.seed(42)
    
    # 生成包含明确机动阶段的轨迹
    num_steps = 300
    dt = 1.0
    
    trajectory = np.zeros((num_steps, 4))
    true_model = np.zeros(num_steps, dtype=int)  # 0: CV, 1: CA, 2: CTRV
    
    # 初始状态
    x, y = 0, 0
    v, psi = 20, 0
    
    for t in range(num_steps):
        # 定义机动阶段
        if t < 100:
            # 阶段1: 匀速直线 (CV)
            a = 0
            psi_dot = 0
            true_model[t] = 0
        elif t < 150:
            # 阶段2: 加速直线 (CA)
            a = 0.5
            psi_dot = 0
            true_model[t] = 1
        elif t < 200:
            # 阶段3: 匀速转弯 (CTRV)
            a = 0
            psi_dot = 0.05
            true_model[t] = 2
        elif t < 250:
            # 阶段4: 减速转弯 (CTRV)
            a = -0.3
            psi_dot = -0.03
            true_model[t] = 2
        else:
            # 阶段5: 匀速直线 (CV)
            a = 0
            psi_dot = 0
            true_model[t] = 0
        
        # 更新状态
        v += a * dt
        psi += psi_dot * dt
        psi = (psi + np.pi) % (2*np.pi) - np.pi
        
        x += v * dt * np.cos(psi)
        y += v * dt * np.sin(psi)
        
        vx = v * np.cos(psi)
        vy = v * np.sin(psi)
        
        trajectory[t] = [x, y, vx, vy]
    
    # 添加观测噪声
    pos_noise_std = 5.0
    measurements = trajectory[:, :2] + np.random.randn(len(trajectory), 2) * pos_noise_std
    R = np.eye(2) * pos_noise_std**2
    
    # 运行IMM滤波
    models = ['CV', 'CA', 'CTRV']
    model_prob = [0.33, 0.33, 0.34]
    trans_prob = np.array([
        [0.95, 0.025, 0.025],
        [0.025, 0.95, 0.025],
        [0.025, 0.025, 0.95]
    ])
    
    imm = IMMFilter(models, model_prob, trans_prob, dt=1.0)
    imm.init_state(measurements[0].reshape(-1, 1))
    
    imm_estimates = []
    imm_model_probs = []
    position_errors = []
    
    for t in range(len(measurements)):
        z = measurements[t, :2]
        x_est, _ = imm.step(z, R)
        imm_estimates.append(x_est.flatten())
        imm_model_probs.append(imm.get_model_probabilities().copy())
        
        pos_error = np.sqrt((x_est[0, 0] - trajectory[t, 0])**2 + 
                           (x_est[1, 0] - trajectory[t, 1])**2)
        position_errors.append(pos_error)
    
    imm_estimates = np.array(imm_estimates)
    imm_model_probs = np.array(imm_model_probs)
    
    # 计算模型切换延迟
    model_switch_delay = []
    
    for i in range(1, len(true_model)):
        if true_model[i] != true_model[i-1]:
            # 找到模型切换点
            switch_point = i
            
            # 查找IMM识别切换的时间
            for j in range(i, min(i+20, len(imm_model_probs))):
                if np.argmax(imm_model_probs[j]) == true_model[i]:
                    delay = j - i
                    model_switch_delay.append(delay)
                    break
    
    # 可视化模型切换分析
    fig, axes = plt.subplots(2, 3, figsize=(18, 12))
    
    # 1. 轨迹与模型概率
    ax = axes[0, 0]
    ax.plot(trajectory[:, 0], trajectory[:, 1], 'k-', linewidth=2, 
            label='真实轨迹', alpha=0.8)
    
    # 用颜色表示真实模型
    colors = ['blue', 'green', 'red']
    model_names = ['CV', 'CA', 'CTRV']
    
    for i in range(len(true_model)-1):
        if true_model[i] == true_model[i+1]:
            ax.plot(trajectory[i:i+2, 0], trajectory[i:i+2, 1], 
                    color=colors[true_model[i]], linewidth=3, alpha=0.5)
    
    ax.set_xlabel('X位置 (m)')
    ax.set_ylabel('Y位置 (m)')
    ax.set_title('轨迹与真实运动模型')
    ax.legend()
    ax.grid(True, alpha=0.3)
    ax.axis('equal')
    
    # 添加模型图例
    for i, (color, name) in enumerate(zip(colors, model_names)):
        ax.plot([], [], color=color, linewidth=3, label=name)
    ax.legend(loc='upper right')
    
    # 2. 真实模型与估计模型概率
    ax = axes[0, 1]
    t = np.arange(num_steps)
    
    # 绘制真实模型(背景)
    for i in range(num_steps-1):
        if true_model[i] == true_model[i+1]:
            ax.axvspan(i, i+1, alpha=0.3, color=colors[true_model[i]])
    
    # 绘制估计模型概率
    for i, (color, name) in enumerate(zip(colors, model_names)):
        ax.plot(t, imm_model_probs[:, i], '-', linewidth=2, 
                color=color, label=name)
    
    ax.set_xlabel('时间步')
    ax.set_ylabel('模型概率')
    ax.set_title('真实模型与估计模型概率')
    ax.legend()
    ax.grid(True, alpha=0.3)
    ax.set_ylim([0, 1])
    
    # 3. 模型切换延迟
    ax = axes[0, 2]
    
    if model_switch_delay:
        ax.hist(model_switch_delay, bins=np.arange(0, max(model_switch_delay)+2)-0.5, 
                edgecolor='black', alpha=0.7, color='blue')
        
        avg_delay = np.mean(model_switch_delay)
        ax.axvline(x=avg_delay, color='red', linestyle='--', 
                  label=f'平均延迟={avg_delay:.1f}步')
        
        ax.set_xlabel('切换延迟 (步数)')
        ax.set_ylabel('频数')
        ax.set_title('模型切换延迟分布')
        ax.legend()
        ax.grid(True, alpha=0.3)
    else:
        ax.text(0.5, 0.5, '无模型切换', ha='center', va='center', 
                transform=ax.transAxes, fontsize=12)
        ax.set_title('模型切换延迟')
    
    # 4. 模型概率收敛速度
    ax = axes[1, 0]
    
    # 计算模型概率的收敛度量
    prob_entropy = -np.sum(imm_model_probs * np.log(imm_model_probs + 1e-10), axis=1)
    
    ax.plot(t, prob_entropy, 'b-', linewidth=2)
    ax.set_xlabel('时间步')
    ax.set_ylabel('模型概率熵')
    ax.set_title('模型不确定性度量 (熵)')
    ax.grid(True, alpha=0.3)
    
    # 5. 各模型在对应阶段的概率
    ax = axes[1, 1]
    
    # 定义阶段
    stages = [
        (0, 100, 'CV阶段'),
        (100, 150, 'CA阶段'),
        (150, 250, 'CTRV阶段'),
        (250, 300, 'CV阶段')
    ]
    
    stage_data = {model: [] for model in model_names}
    
    for start, end, stage_name in stages:
        for i, model in enumerate(model_names):
            # 计算该阶段该模型概率的平均值
            stage_prob = np.mean(imm_model_probs[start:end, i])
            stage_data[model].append(stage_prob)
    
    x = np.arange(len(stages))
    width = 0.25
    
    for i, (model, color) in enumerate(zip(model_names, colors)):
        ax.bar(x + (i-1)*width, stage_data[model], width, 
               color=color, alpha=0.7, label=model)
    
    ax.set_xlabel('运动阶段')
    ax.set_ylabel('平均模型概率')
    ax.set_title('各模型在不同阶段的平均概率')
    ax.set_xticks(x)
    ax.set_xticklabels([stage[2] for stage in stages], rotation=15)
    ax.legend()
    ax.grid(True, alpha=0.3, axis='y')
    ax.set_ylim([0, 1])
    
    # 6. 模型切换决策质量
    ax = axes[1, 2]
    
    # 计算每个时间步的决策质量
    decision_quality = []
    for t in range(num_steps):
        estimated_model = np.argmax(imm_model_probs[t])
        if estimated_model == true_model[t]:
            decision_quality.append(1)  # 正确
        else:
            decision_quality.append(0)  # 错误
    
    # 滑动窗口计算准确率
    window_size = 20
    accuracy_sliding = []
    for i in range(num_steps - window_size + 1):
        window_quality = decision_quality[i:i+window_size]
        accuracy_sliding.append(np.mean(window_quality))
    
    t_sliding = np.arange(len(accuracy_sliding))
    ax.plot(t_sliding, accuracy_sliding, 'b-', linewidth=2)
    ax.axhline(y=0.5, color='r', linestyle='--', alpha=0.5, label='随机猜测')
    ax.set_xlabel('时间窗口中心')
    ax.set_ylabel(f'模型决策准确率 (窗口大小={window_size})')
    ax.set_title('模型切换决策质量')
    ax.legend()
    ax.grid(True, alpha=0.3)
    ax.set_ylim([0, 1])
    
    plt.suptitle('IMM模型切换特性分析', fontsize=16, y=1.02)
    plt.tight_layout()
    plt.savefig('imm_model_switching_analysis.png', dpi=300, bbox_inches='tight')
    plt.show()
    
    # 打印模型切换统计
    print("\n" + "="*60)
    print("模型切换特性分析")
    print("="*60)
    
    if model_switch_delay:
        print(f"模型切换次数: {len(model_switch_delay)}")
        print(f"平均切换延迟: {np.mean(model_switch_delay):.2f} 步")
        print(f"最大切换延迟: {np.max(model_switch_delay):.2f} 步")
        print(f"最小切换延迟: {np.min(model_switch_delay):.2f} 步")
    else:
        print("无模型切换发生")
    
    # 计算总体决策准确率
    overall_accuracy = np.mean(decision_quality) * 100
    print(f"总体模型决策准确率: {overall_accuracy:.1f}%")
    
    # 分阶段计算准确率
    print("\n分阶段模型决策准确率:")
    for start, end, stage_name in stages:
        stage_quality = decision_quality[start:end]
        stage_accuracy = np.mean(stage_quality) * 100
        print(f"  {stage_name}: {stage_accuracy:.1f}%")
    
    print("="*60)
    
    return {
        'trajectory': trajectory,
        'true_model': true_model,
        'imm_model_probs': imm_model_probs,
        'decision_quality': decision_quality,
        'switch_delays': model_switch_delay
    }

if __name__ == "__main__":
    print("="*60)
    print("交互多模型滤波实现与分析")
    print("="*60)
    
    np.random.seed(42)
    
    # 1. 比较IMM与单一模型
    print("\n1. 比较IMM与单一模型性能...")
    comparison_results = compare_imm_vs_single_models()
    
    # 2. 分析IMM参数敏感性
    print("\n2. 分析IMM参数敏感性...")
    parameter_results = analyze_imm_parameters()
    
    # 3. 分析模型切换特性
    print("\n3. 分析模型切换特性...")
    switching_results = analyze_model_switching()
    
    print("\n演示完成!")
    
    # 总结
    print("\n" + "="*60)
    print("IMM滤波关键结论")
    print("="*60)
    print("1. IMM通过混合多个模型来适应目标的不同运动模式")
    print("2. 在机动目标跟踪中,IMM通常优于任何单一模型")
    print("3. 模型概率的合理设置对IMM性能至关重要")
    print("4. 转移概率矩阵影响模型切换的灵敏度和稳定性")
    print("5. IMM能够有效识别目标的运动模式变化")
    print("6. 模型切换通常有1-3步的延迟,取决于参数设置")
    print("7. 在实际应用中,IMM参数需要根据具体场景调整")
    print("="*60)

9. 总结与工程实践建议

通过本文的三个Demo,我们全面探讨了雷达目标跟踪中的运动模型和坐标转换问题。以下是关键结论和工程实践建议:

9.1 运动模型选择指南

9.2 坐标转换实践建议

  1. 精度要求不高的场景:使用简单三角转换

  2. 高精度跟踪场景:使用去偏转换或考虑二阶矩

  3. 非线性滤波场景:在滤波器中直接使用极坐标观测

  4. 计算资源受限场景:使用线性化转换

  5. 实时性要求高的场景:预计算转换表或使用近似公式

9.3 IMM滤波工程实践

  1. 模型集合选择

    • 通常选择2-3个互补的模型

    • 常用组合:CV + CA + CTRV

    • 根据目标特性调整模型集合

  2. 参数初始化

    • 初始模型概率:根据先验知识设置,无先验时用均匀分布

    • 转移概率矩阵:对角线元素通常设为0.9-0.99

    • 过程噪声:根据目标机动性设置

  3. 性能调优

    • 监控模型概率变化

    • 调整转移概率以适应目标机动频率

    • 使用NEES/NIS进行一致性检验

  4. 计算优化

    • 并行计算各模型滤波

    • 使用平方根形式提高数值稳定性

    • 实现快速矩阵运算

9.4 实际部署考虑

  1. 实时性要求

    • 优化计算复杂度

    • 考虑固定点运算

    • 并行化处理

  2. 内存限制

    • 限制历史数据长度

    • 使用滑动窗口

    • 压缩存储中间结果

  3. 鲁棒性设计

    • 异常值检测与处理

    • 数值稳定性保障

    • 模型有效性检验

  4. 可维护性

    • 模块化设计

    • 参数配置文件化

    • 完善的日志记录

9.5 未来研究方向

  1. 自适应IMM:在线学习转移概率和噪声参数

  2. 深度学习辅助:使用神经网络识别运动模式

  3. 多传感器融合:雷达+光电+红外多源信息融合

  4. 分布式跟踪:多雷达组网协同跟踪

  5. 抗干扰技术:电子战环境下的鲁棒跟踪

9.6 代码资源总结

本文实现的完整代码包括:

  1. 基础运动模型

    • CV模型:CVFilter

    • CA模型:CAFilter

    • CTRV模型:CTRVFilter

  2. 坐标转换方法

    • 简单三角转换:polar_to_cartesian

    • 去偏转换:polar_to_cartesian_unbiased

    • 线性化转换:polar_to_cartesian_linearized

  3. IMM滤波实现

    • 完整IMM框架:IMMFilter

    • 模型结果容器:ModelResult

  4. 三个完整Demo

    • Demo 3-1:不同运动模型对比

    • Demo 3-2:坐标转换方法对比

    • Demo 3-3:IMM滤波实现与分析

9.7 下一篇预告

在下一篇博客中,我们将深入探讨多目标跟踪的核心问题:

  1. 数据关联算法:最近邻、概率数据关联、联合概率数据关联

  2. 航迹起始与终结:逻辑法、序列概率比检验

  3. 密集环境下的跟踪:航迹分叉与合并处理

  4. 多雷达多目标跟踪:时空配准与航迹融合

  5. 实际工程挑战:计算复杂度、实时性、鲁棒性

相关推荐
soragui2 小时前
【Python】第 1 章:Python 解释器原理
开发语言·python
nimadan122 小时前
生成剧本杀软件2025推荐,创新剧情设计工具引领潮流
人工智能·python
极光代码工作室2 小时前
基于深度学习的智能垃圾分类系统
python·深度学习·神经网络·机器学习·ai
MediaTea2 小时前
Pandas 操作指南(二):数据选取与条件筛选
人工智能·python·机器学习·数据挖掘·pandas
小陈工2 小时前
Python Web开发入门(十二):使用Flask-RESTful构建API——让后端开发更优雅
开发语言·前端·python·安全·oracle·flask·restful
无心水2 小时前
20、Spring陷阱:Feign AOP切面为何失效?配置优先级如何“劫持”你的设置?
java·开发语言·后端·python·spring·java.time·java时间处理
夜雨飘零12 小时前
零门槛!用 AI 生成 HTML 并一键部署到云端桌面
人工智能·python·html
qZ6bgMe433 小时前
使用Mixin类简单重构配置模块
网络·python·重构
巧妹儿3 小时前
AI Agent 实战:MySQL 监控指标查询 Skill|华为云 + 腾讯云双兼容可直接复用
python·mysql·ai·大模型·华为云·腾讯云