本项目基于:
Ubuntu2204
Eigen3
因为电赛备赛需要,所以抽空研究了一下卡尔曼滤波。
文件目录如下:
.
├── CMakeLists.txt
├── CMakePresets.jso
├── config
│ └── kalman_config.yaml
├── include
│ ├── ballsensor.hpp
│ └── kalman.hpp
└── src
├── ballsensor.cpp
├── kalman.cpp
├── kalman_filter_results.png
├── main.cpp
└── show.py首先,因为我这边还没有场地所以模拟了一个ballsensor.cpp 作为输入xy,还会随机输入噪声
#ifndef BALL_SENSOR_H
#define BALL_SENSOR_H
#include <vector>
#include <random>
#include <Eigen/Dense>
#include <string>
class BallSensor {
private:
// 真实状态变量
double true_x, true_y; // 位置
double true_vx, true_vy; // 速度
double true_ax, true_ay; // 加速度
// 随机数生成器
std::default_random_engine generator;
std::normal_distribution<double> noise_distribution;
// 测量噪声标准差
double measurement_noise_std;
// 时间步长
double dt;
// 存储轨迹和测量值
std::vector<Eigen::Vector2d> true_positions;
std::vector<Eigen::Vector2d> measurements;
// 当前时间
double current_time;
public:
// 构造函数
BallSensor(double noise_std = 0.1, double time_step = 0.1);
// 从配置文件初始化
void initFromConfig(const std::string& config_file);
// 初始化球的状态
void initialize(double x0, double y0, double vx0, double vy0, double ax0, double ay0);
// 更新球的真实位置
void updateTrueState();
// 获取带有噪声的测量值
Eigen::Vector2d getMeasurement();
// 获取当前时间
double getCurrentTime() const { return current_time; }
// 获取真实状态
Eigen::Vector2d getTruePosition() const;
Eigen::Vector2d getTrueVelocity() const;
Eigen::Vector2d getTrueAcceleration() const;
// 获取历史数据
const std::vector<Eigen::Vector2d>& getTruePositions() const { return true_positions; }
const std::vector<Eigen::Vector2d>& getMeasurements() const { return measurements; }
};
#endif // BALL_SENSOR_H#include "../include/ballsensor.hpp"
#include <yaml-cpp/yaml.h>
#include <chrono>
#include <iostream>
#include <fstream>
BallSensor::BallSensor(double noise_std, double time_step)
: measurement_noise_std(noise_std), dt(time_step), current_time(0.0) {
// 初始化随机数生成器
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
generator = std::default_random_engine(seed);
// 配置高斯分布噪声
noise_distribution = std::normal_distribution<double>(0.0, measurement_noise_std);
}
void BallSensor::initialize(double x0, double y0, double vx0, double vy0, double ax0, double ay0) {
true_x = x0;
true_y = y0;
true_vx = vx0;
true_vy = vy0;
true_ax = ax0;
true_ay = ay0;
current_time = 0.0;
// 清除历史数据
true_positions.clear();
measurements.clear();
// 记录初始位置
Eigen::Vector2d initial_position(true_x, true_y);
true_positions.push_back(initial_position);
// 记录初始带噪声测量值
measurements.push_back(getMeasurement());
}
void BallSensor::updateTrueState() {
// 运动学方程:更新位置和速度
true_x += true_vx * dt + 0.5 * true_ax * dt * dt;
true_y += true_vy * dt + 0.5 * true_ay * dt * dt;
true_vx += true_ax * dt;
true_vy += true_ay * dt;
// 小概率随机改变加速度,模拟不平整的倾斜平面
std::uniform_real_distribution<double> random_change(0.0, 1.0);
if (random_change(generator) < 0.05) { // 5%的概率改变加速度
std::normal_distribution<double> acc_change(0.0, 0.05);
true_ax += acc_change(generator);
true_ay += acc_change(generator);
// 限制加速度范围,防止过大
if (std::abs(true_ax) > 0.5) true_ax *= 0.8;
if (std::abs(true_ay) > 0.5) true_ay *= 0.8;
}
// 增加时间
current_time += dt;
// 存储真实位置
true_positions.push_back(Eigen::Vector2d(true_x, true_y));
}
Eigen::Vector2d BallSensor::getMeasurement() {
// 生成带有噪声的测量值
double measured_x = true_x + noise_distribution(generator);
double measured_y = true_y + noise_distribution(generator);
Eigen::Vector2d measurement(measured_x, measured_y);
measurements.push_back(measurement);
return measurement;
}
Eigen::Vector2d BallSensor::getTruePosition() const {
return Eigen::Vector2d(true_x, true_y);
}
Eigen::Vector2d BallSensor::getTrueVelocity() const {
return Eigen::Vector2d(true_vx, true_vy);
}
Eigen::Vector2d BallSensor::getTrueAcceleration() const {
return Eigen::Vector2d(true_ax, true_ay);
}
void BallSensor::initFromConfig(const std::string& config_file) {
try {
// 加载YAML文件
YAML::Node config = YAML::LoadFile(config_file);
// 读取传感器设置
measurement_noise_std = config["sensor"]["measurement_noise_std"].as<double>();
dt = config["sensor"]["time_step"].as<double>();
// 重新配置噪声分布
noise_distribution = std::normal_distribution<double>(0.0, measurement_noise_std);
// 初始化随机数生成器
unsigned seed = std::chrono::system_clock::now().time_since_epoch().count();
generator = std::default_random_engine(seed);
// 初始化球的状态
double x0 = config["initial_state"]["x"].as<double>();
double y0 = config["initial_state"]["y"].as<double>();
double vx0 = config["initial_state"]["vx"].as<double>();
double vy0 = config["initial_state"]["vy"].as<double>();
double ax0 = config["initial_state"]["ax"].as<double>();
double ay0 = config["initial_state"]["ay"].as<double>();
// 初始化球的状态
initialize(x0, y0, vx0, vy0, ax0, ay0);
std::cout << "传感器模拟器已从配置文件成功初始化: " << config_file << std::endl;
} catch (const YAML::Exception& e) {
std::cerr << "读取配置文件时出错: " << e.what() << std::endl;
throw;
} catch (const std::exception& e) {
std::cerr << "初始化传感器模拟器时出错: " << e.what() << std::endl;
throw;
}
}关键的卡尔曼滤波器在这里:
因为调参的时候可能经常要改参数,我就把它写成个个yaml,方便我随时改参而不用重新build
# 卡尔曼滤波器配置文件
# 基本设置
time_step: 0.1 # 时间步长 (秒)
# 状态初始化
initial_state:
x: 0.0 # 初始x位置
y: 0.0 # 初始y位置
vx: 1.0 # 初始x方向速度 (每秒1个单位)
vy: 1.5 # 初始y方向速度 (每秒1.5个单位)
ax: 0.2 # 初始x方向加速度 (每秒每秒0.2个单位)
ay: -0.1 # 初始y方向加速度 (每秒每秒-0.1个单位,负值表示减速)
# 初始协方差矩阵对角元素
initial_covariance:
x: 0.1 # x位置的初始不确定性
y: 0.1 # y位置的初始不确定性
vx: 1.0 # x方向速度的初始不确定性
vy: 1.0 # y方向速度的初始不确定性
ax: 2.0 # x方向加速度的初始不确定性
ay: 2.0 # y方向加速度的初始不确定性
# 噪声设置
noise:
process:
default: 0.01 # 默认过程噪声
position:
x: 0.01 # x位置的过程噪声
y: 0.01 # y位置的过程噪声
velocity:
x: 0.01 # x速度的过程噪声
y: 0.01 # y速度的过程噪声
acceleration:
x: 0.1 # x加速度的过程噪声
y: 0.1 # y加速度的过程噪声
measurement:
position:
x: 0.1 # x位置的测量噪声
y: 0.1 # y位置的测量噪声
# 传感器模拟器设置
sensor:
measurement_noise_std: 0.1 # 传感器噪声标准差
time_step: 0.1 # 传感器采样时间步长
我把他写成了一个类
#ifndef KALMAN_FILTER_H
#define KALMAN_FILTER_H
#include <Eigen/Dense>
#include <string>
class KalmanFilter {
private:
// 状态向量: [x, y, vx, vy, ax, ay]^T
Eigen::VectorXd x;
// 状态协方差矩阵
Eigen::MatrixXd P;
// 状态转移矩阵
Eigen::MatrixXd F;
// 测量矩阵
Eigen::MatrixXd H;
// 过程噪声协方差
Eigen::MatrixXd Q;
// 测量噪声协方差
Eigen::MatrixXd R;
// 时间步长
double dt;
public:
// 构造函数
KalmanFilter();
// 从配置文件初始化滤波器
void initFromConfig(const std::string& config_file);
// 手动初始化滤波器
void init(double dt, const Eigen::VectorXd& initial_state, const Eigen::MatrixXd& initial_covariance);
// 预测步骤
void predict();
// 更新步骤
void update(const Eigen::VectorXd& measurement);
// 获取当前状态
const Eigen::VectorXd& getState() const { return x; }
// 获取当前协方差
const Eigen::MatrixXd& getCovariance() const { return P; }
};
#endif // KALMAN_FILTER_H#include "../include/kalman.hpp"
#include <yaml-cpp/yaml.h>
#include <iostream>
#include <fstream>
KalmanFilter::KalmanFilter() {
// 默认构造函数
}
void KalmanFilter::initFromConfig(const std::string& config_file) {
try {
// 加载YAML文件
YAML::Node config = YAML::LoadFile(config_file);
// 读取时间步长
dt = config["time_step"].as<double>();
// 设置状态维度为6:[x, y, vx, vy, ax, ay]
int state_dim = 6;
// 设置测量维度为2:[x, y]
int meas_dim = 2;
// 初始化状态向量
x = Eigen::VectorXd(state_dim);
x(0) = config["initial_state"]["x"].as<double>();
x(1) = config["initial_state"]["y"].as<double>();
x(2) = config["initial_state"]["vx"].as<double>();
x(3) = config["initial_state"]["vy"].as<double>();
x(4) = config["initial_state"]["ax"].as<double>();
x(5) = config["initial_state"]["ay"].as<double>();
// 初始化状态协方差矩阵
P = Eigen::MatrixXd::Zero(state_dim, state_dim);
P(0, 0) = config["initial_covariance"]["x"].as<double>();
P(1, 1) = config["initial_covariance"]["y"].as<double>();
P(2, 2) = config["initial_covariance"]["vx"].as<double>();
P(3, 3) = config["initial_covariance"]["vy"].as<double>();
P(4, 4) = config["initial_covariance"]["ax"].as<double>();
P(5, 5) = config["initial_covariance"]["ay"].as<double>();
// 构建状态转移矩阵 F
F = Eigen::MatrixXd::Identity(state_dim, state_dim);
F(0, 2) = dt; // x与vx的关系
F(0, 4) = 0.5*dt*dt; // x与ax的关系
F(1, 3) = dt; // y与vy的关系
F(1, 5) = 0.5*dt*dt; // y与ay的关系
F(2, 4) = dt; // vx与ax的关系
F(3, 5) = dt; // vy与ay的关系
// 构建测量矩阵 H
H = Eigen::MatrixXd::Zero(meas_dim, state_dim);
H(0, 0) = 1.0; // 测量x
H(1, 1) = 1.0; // 测量y
// 设置过程噪声协方差矩阵 Q
double process_noise = config["noise"]["process"]["default"].as<double>();
Q = Eigen::MatrixXd::Identity(state_dim, state_dim) * process_noise;
// 设置特定状态变量的过程噪声
Q(0, 0) = config["noise"]["process"]["position"]["x"].as<double>();
Q(1, 1) = config["noise"]["process"]["position"]["y"].as<double>();
Q(2, 2) = config["noise"]["process"]["velocity"]["x"].as<double>();
Q(3, 3) = config["noise"]["process"]["velocity"]["y"].as<double>();
Q(4, 4) = config["noise"]["process"]["acceleration"]["x"].as<double>();
Q(5, 5) = config["noise"]["process"]["acceleration"]["y"].as<double>();
// 设置测量噪声协方差矩阵 R
R = Eigen::MatrixXd::Zero(meas_dim, meas_dim);
R(0, 0) = config["noise"]["measurement"]["position"]["x"].as<double>();
R(1, 1) = config["noise"]["measurement"]["position"]["y"].as<double>();
std::cout << "卡尔曼滤波器已从配置文件成功初始化: " << config_file << std::endl;
} catch (const YAML::Exception& e) {
std::cerr << "读取配置文件时出错: " << e.what() << std::endl;
throw;
} catch (const std::exception& e) {
std::cerr << "初始化卡尔曼滤波器时出错: " << e.what() << std::endl;
throw;
}
}
void KalmanFilter::init(double time_step, const Eigen::VectorXd& initial_state, const Eigen::MatrixXd& initial_covariance) {
dt = time_step;
// 设置状态维度为6:[x, y, vx, vy, ax, ay]
int state_dim = 6;
// 设置测量维度为2:[x, y]
int meas_dim = 2;
// 初始化状态向量
x = initial_state;
// 初始化状态协方差矩阵
P = initial_covariance;
// 构建状态转移矩阵 F
// 运动方程:
// x(t+dt) = x(t) + vx(t)*dt + 0.5*ax(t)*dt^2
// y(t+dt) = y(t) + vy(t)*dt + 0.5*ay(t)*dt^2
// vx(t+dt) = vx(t) + ax(t)*dt
// vy(t+dt) = vy(t) + ay(t)*dt
// ax(t+dt) = ax(t) (假设加速度保持不变)
// ay(t+dt) = ay(t) (假设加速度保持不变)
F = Eigen::MatrixXd::Identity(state_dim, state_dim);
F(0, 2) = dt; // x与vx的关系
F(0, 4) = 0.5*dt*dt; // x与ax的关系
F(1, 3) = dt; // y与vy的关系
F(1, 5) = 0.5*dt*dt; // y与ay的关系
F(2, 4) = dt; // vx与ax的关系
F(3, 5) = dt; // vy与ay的关系
// 构建测量矩阵 H
// 我们只测量位置 [x, y]
H = Eigen::MatrixXd::Zero(meas_dim, state_dim);
H(0, 0) = 1.0; // 测量x
H(1, 1) = 1.0; // 测量y
// 设置过程噪声协方差矩阵 Q
double process_noise = 0.01;
Q = Eigen::MatrixXd::Identity(state_dim, state_dim) * process_noise;
// 加速度的过程噪声可能更大,因为它代表了我们不确定的倾斜平面带来的加速度
Q(4, 4) = 0.1; // ax的过程噪声
Q(5, 5) = 0.1; // ay的过程噪声
// 设置测量噪声协方差矩阵 R
double measurement_noise = 0.1; // 传感器噪声
R = Eigen::MatrixXd::Identity(meas_dim, meas_dim) * measurement_noise;
}
void KalmanFilter::predict() {
// 预测步骤
// 预测状态: x = F * x
x = F * x;
// 预测协方差: P = F * P * F^T + Q
P = F * P * F.transpose() + Q;
}
void KalmanFilter::update(const Eigen::VectorXd& measurement) {
// 更新步骤
// 计算卡尔曼增益: K = P * H^T * (H * P * H^T + R)^-1
Eigen::MatrixXd PHt = P * H.transpose();
Eigen::MatrixXd S = H * PHt + R;
Eigen::MatrixXd K = PHt * S.inverse();
// 计算新的状态: x = x + K * (z - H * x)
// z是测量值,H * x是预测的测量值
Eigen::VectorXd y = measurement - H * x;
x = x + K * y;
// 更新状态协方差: P = (I - K * H) * P
int state_dim = x.size();
Eigen::MatrixXd I = Eigen::MatrixXd::Identity(state_dim, state_dim);
P = (I - K * H) * P;
}最终的main:
#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <iomanip> // 用于设置输出精度
#include "../include/ballsensor.hpp"
#include "../include/kalman.hpp"
int main() {
std::string config_file = "../config/kalman_config.yaml";
try {
// 创建传感器模拟器并从配置文件初始化
BallSensor sensor;
sensor.initFromConfig(config_file);
// 创建卡尔曼滤波器并从配置文件初始化
KalmanFilter kf;
kf.initFromConfig(config_file);
// 存储估计结果
std::vector<Eigen::Vector2d> estimated_positions;
std::vector<Eigen::Vector2d> estimated_velocities;
std::vector<Eigen::Vector2d> estimated_accelerations;
// 模拟100步
int num_steps = 500;
for (int i = 0; i < num_steps; ++i) {
// 更新传感器的真实状态
sensor.updateTrueState();
// 获取当前测量值
Eigen::Vector2d measurement = sensor.getMeasurement();
// 卡尔曼滤波预测步骤
kf.predict();
// 卡尔曼滤波更新步骤
kf.update(measurement);
// 获取滤波后的状态
Eigen::VectorXd state = kf.getState();
// 存储估计结果
estimated_positions.push_back(Eigen::Vector2d(state(0), state(1)));
estimated_velocities.push_back(Eigen::Vector2d(state(2), state(3)));
estimated_accelerations.push_back(Eigen::Vector2d(state(4), state(5)));
// 打印当前状态(每10步打印一次)
if (i % 10 == 0) {
std::cout << "步骤 " << i << ":" << std::endl;
std::cout << " 真实位置: ("
<< sensor.getTruePosition()[0] << ", "
<< sensor.getTruePosition()[1] << ")" << std::endl;
std::cout << " 测量位置: ("
<< measurement[0] << ", "
<< measurement[1] << ")" << std::endl;
std::cout << " 估计位置: ("
<< state(0) << ", "
<< state(1) << ")" << std::endl;
std::cout << " 估计速度: ("
<< state(2) << ", "
<< state(3) << ")" << std::endl;
std::cout << " 估计加速度: ("
<< state(4) << ", "
<< state(5) << ")" << std::endl;
std::cout << std::endl;
}
}
// 将结果保存到CSV文件中,用于后续可视化
std::ofstream result_file("kalman_filter_results.csv");
if (!result_file.is_open()) {
std::cerr << "无法创建结果文件" << std::endl;
return 1;
}
// 写入CSV文件头
result_file << "时间步,";
result_file << "真实位置X,真实位置Y,";
result_file << "测量位置X,测量位置Y,";
result_file << "估计位置X,估计位置Y,";
result_file << "真实速度X,真实速度Y,";
result_file << "估计速度X,估计速度Y,";
result_file << "真实加速度X,真实加速度Y,";
result_file << "估计加速度X,估计加速度Y" << std::endl;
// 写入所有数据
auto true_positions = sensor.getTruePositions();
auto measurements = sensor.getMeasurements();
for (int i = 0; i < num_steps; ++i) {
result_file << i << ",";
// 真实位置
result_file << true_positions[i][0] << "," << true_positions[i][1] << ",";
// 测量位置
result_file << measurements[i][0] << "," << measurements[i][1] << ",";
// 估计位置
result_file << estimated_positions[i][0] << "," << estimated_positions[i][1] << ",";
// 真实速度 (只有最后一步的)
if (i == num_steps - 1) {
Eigen::Vector2d true_vel = sensor.getTrueVelocity();
result_file << true_vel[0] << "," << true_vel[1] << ",";
} else {
// 可以通过位置差分来估计真实速度
double dt = sensor.getCurrentTime() / num_steps;
result_file << (true_positions[i+1][0] - true_positions[i][0]) / dt << ",";
result_file << (true_positions[i+1][1] - true_positions[i][1]) / dt << ",";
}
// 估计速度
result_file << estimated_velocities[i][0] << "," << estimated_velocities[i][1] << ",";
// 真实加速度 (最后一步的)
if (i == num_steps - 1) {
Eigen::Vector2d true_acc = sensor.getTrueAcceleration();
result_file << true_acc[0] << "," << true_acc[1] << ",";
} else if (i == num_steps - 2) {
// 最后第二步,简单差分
double dt = sensor.getCurrentTime() / num_steps;
result_file << "0,0,"; // 简化处理
} else {
// 可以通过速度差分来估计加速度
double dt = sensor.getCurrentTime() / num_steps;
double v1x = (true_positions[i+1][0] - true_positions[i][0]) / dt;
double v2x = (true_positions[i+2][0] - true_positions[i+1][0]) / dt;
double v1y = (true_positions[i+1][1] - true_positions[i][1]) / dt;
double v2y = (true_positions[i+2][1] - true_positions[i+1][1]) / dt;
result_file << (v2x - v1x) / dt << ",";
result_file << (v2y - v1y) / dt << ",";
}
// 估计加速度
result_file << estimated_accelerations[i][0] << "," << estimated_accelerations[i][1];
result_file << std::endl;
}
result_file.close();
std::cout << "模拟成功完成! 结果已保存到 kalman_filter_results.csv" << std::endl;
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}
return 0;
}
CMakeLists.txt如下
cmake_minimum_required(VERSION 3.10)
project(BallKalmanFilter)
# 设置C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 查找Eigen库
find_package(Eigen3 REQUIRED)
include_directories(${EIGEN3_INCLUDE_DIR})
# 查找yaml-cpp库
find_package(yaml-cpp REQUIRED)
# 包含头文件目录
include_directories(include)
# 添加源文件
set(SOURCES
src/main.cpp
src/ballsensor.cpp
src/kalman.cpp
)
# 创建可执行文件
add_executable(ball_filter ${SOURCES})
# 链接库
target_link_libraries(ball_filter ${EIGEN3_LIBRARIES} yaml-cpp)
# 设置配置文件目录
file(COPY config DESTINATION ${CMAKE_BINARY_DIR})
最后写了个py来可视化图像
import zhplot
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib
import os
# 读取数据
df = pd.read_csv('build/kalman_filter_results.csv')
# 创建保存图形的目录
save_dir = 'build'
if not os.path.exists(save_dir):
os.makedirs(save_dir)
# 图1:位置轨迹对比
plt.figure(figsize=(12, 10))
plt.plot(df['真实位置X'], df['真实位置Y'], 'b-', linewidth=2, label='真实轨迹')
plt.plot(df['测量位置X'], df['测量位置Y'], 'r.', markersize=4, label='测量值')
plt.plot(df['估计位置X'], df['估计位置Y'], 'g-', linewidth=2, label='卡尔曼滤波')
plt.grid(True)
plt.legend(fontsize=14)
plt.title('位置轨迹对比', fontsize=18)
plt.xlabel('X坐标', fontsize=14)
plt.ylabel('Y坐标', fontsize=14)
plt.tick_params(axis='both', which='major', labelsize=12)
plt.tight_layout()
plt.savefig(os.path.join(save_dir, '1_位置轨迹对比.png'), dpi=300)
# 图2:X位置随时间变化
plt.figure(figsize=(12, 10))
plt.plot(df['时间步'], df['真实位置X'], 'b-', linewidth=2, label='真实X')
plt.plot(df['时间步'], df['测量位置X'], 'r.', markersize=4, label='测量X')
plt.plot(df['时间步'], df['估计位置X'], 'g-', linewidth=2, label='估计X')
plt.grid(True)
plt.legend(fontsize=14)
plt.title('X位置随时间变化', fontsize=18)
plt.xlabel('时间步', fontsize=14)
plt.ylabel('X坐标', fontsize=14)
plt.tick_params(axis='both', which='major', labelsize=12)
plt.tight_layout()
plt.savefig(os.path.join(save_dir, '2_X位置随时间变化.png'), dpi=300)
# 图3:速度随时间变化
plt.figure(figsize=(12, 10))
plt.plot(df['时间步'], df['真实速度X'], 'b-', linewidth=2, label='真实Vx')
plt.plot(df['时间步'], df['估计速度X'], 'g-', linewidth=2, label='估计Vx')
plt.plot(df['时间步'], df['真实速度Y'], 'b--', linewidth=2, label='真实Vy')
plt.plot(df['时间步'], df['估计速度Y'], 'g--', linewidth=2, label='估计Vy')
plt.grid(True)
plt.legend(fontsize=14)
plt.title('速度随时间变化', fontsize=18)
plt.xlabel('时间步', fontsize=14)
plt.ylabel('速度', fontsize=14)
plt.tick_params(axis='both', which='major', labelsize=12)
plt.tight_layout()
plt.savefig(os.path.join(save_dir, '3_速度随时间变化.png'), dpi=300)
# 图4:加速度随时间变化
plt.figure(figsize=(12, 10))
plt.plot(df['时间步'], df['真实加速度X'], 'b-', linewidth=2, label='真实Ax')
plt.plot(df['时间步'], df['估计加速度X'], 'g-', linewidth=2, label='估计Ax')
plt.plot(df['时间步'], df['真实加速度Y'], 'b--', linewidth=2, label='真实Ay')
plt.plot(df['时间步'], df['估计加速度Y'], 'g--', linewidth=2, label='估计Ay')
plt.grid(True)
plt.legend(fontsize=14)
plt.title('加速度随时间变化', fontsize=18)
plt.xlabel('时间步', fontsize=14)
plt.ylabel('加速度', fontsize=14)
plt.tick_params(axis='both', which='major', labelsize=12)
plt.tight_layout()
plt.savefig(os.path.join(save_dir, '4_加速度随时间变化.png'), dpi=300)
# 显示所有图形
plt.show()最终效果如下



