自动驾驶车辆运动学模型 Python 实现:从 3 个坐标系到单车模型仿真

📅 2026/7/10 18:59:47
自动驾驶车辆运动学模型 Python 实现:从 3 个坐标系到单车模型仿真
自动驾驶车辆运动学模型 Python 实现从 3 个坐标系到单车模型仿真在自动驾驶技术快速发展的今天理解车辆运动学模型已成为算法工程师和控制工程师的必备技能。不同于复杂的动力学模型运动学模型从几何关系出发为初学者提供了一个直观理解车辆运动的窗口。本文将带领读者从零开始用Python实现一个完整的单车运动学模型涵盖大地坐标系、车身坐标系和Frenet坐标系之间的转换最终构建一个可交互的仿真环境。1. 坐标系基础与Python表示1.1 三大坐标系精要自动驾驶系统中常用的三个坐标系各有其独特作用大地坐标系全局坐标系固定不变的参考系通常采用WGS84或UTM坐标系车身坐标系原点在车辆质心x轴指向车辆前方y轴指向左侧Frenet坐标系沿参考路径建立将车辆位置分解为纵向和横向分量class CoordinateSystem: 坐标系基类 def __init__(self, origin(0,0), rotation0): self.origin np.array(origin) # 原点坐标 self.rotation rotation # 旋转角度(弧度)1.2 坐标系转换数学原理坐标系间的转换本质上是矩阵运算。以车身到大地坐标系的转换为例$$ \begin{bmatrix} X_{global} \ Y_{global} \end{bmatrix}\begin{bmatrix} \cos\varphi -\sin\varphi \ \sin\varphi \cos\varphi \end{bmatrix} \begin{bmatrix} x_{body} \ y_{body} \end{bmatrix} \begin{bmatrix} X_{origin} \ Y_{origin} \end{bmatrix} $$Python实现时我们使用NumPy进行高效矩阵运算def body_to_global(body_point, vehicle_pose): 车身坐标系转大地坐标系 :param body_point: 车身坐标系下的点(x,y) :param vehicle_pose: 车辆位姿(x,y,heading) :return: 全局坐标系下的点 x, y, heading vehicle_pose rotation_matrix np.array([ [np.cos(heading), -np.sin(heading)], [np.sin(heading), np.cos(heading)] ]) return rotation_matrix body_point np.array([x, y])2. 单车运动学模型实现2.1 模型假设与参数定义单车模型基于以下关键假设前后轮分别简化为单轮低速行驶忽略轮胎侧偏仅考虑平面运动X,Y,偏航角后轮转向角为零class BicycleModel: def __init__(self, wheelbase2.7, max_steernp.radians(30)): :param wheelbase: 轴距(m) :param max_steer: 最大前轮转角(rad) self.wheelbase wheelbase self.max_steer max_steer self.state np.zeros(3) # [x, y, heading]2.2 运动学方程离散化实现基于运动学微分方程我们采用欧拉方法进行离散化$$ \begin{cases} x_{k1} x_k v \cdot \cos(\varphi_k) \cdot \Delta t \ y_{k1} y_k v \cdot \sin(\varphi_k) \cdot \Delta t \ \varphi_{k1} \varphi_k \frac{v}{L} \tan(\delta_f) \cdot \Delta t \end{cases} $$Python实现如下def update(self, velocity, steer_angle, dt): 更新车辆状态 :param velocity: 车速(m/s) :param steer_angle: 前轮转角(rad) :param dt: 时间步长(s) steer_angle np.clip(steer_angle, -self.max_steer, self.max_steer) x, y, heading self.state self.state[0] x velocity * np.cos(heading) * dt self.state[1] y velocity * np.sin(heading) * dt self.state[2] heading velocity * np.tan(steer_angle) / self.wheelbase * dt # 归一化角度到[-π, π] self.state[2] (self.state[2] np.pi) % (2 * np.pi) - np.pi return self.state3. Frenet坐标系转换实践3.1 参考路径表示Frenet坐标系依赖于参考路径我们使用三次样条曲线表示路径from scipy.interpolate import CubicSpline class ReferencePath: def __init__(self, waypoints): :param waypoints: 路径点列表[(x0,y0), (x1,y1), ...] waypoints np.array(waypoints) self.cumulative_distances np.cumsum(np.sqrt(np.sum(np.diff(waypoints, axis0)**2, axis1))) self.cumulative_distances np.insert(self.cumulative_distances, 0, 0) self.spline_x CubicSpline(self.cumulative_distances, waypoints[:,0]) self.spline_y CubicSpline(self.cumulative_distances, waypoints[:,1])3.2 全局坐标与Frenet坐标互转实现全局坐标系到Frenet坐标系的转换def global_to_frenet(self, point): 全局坐标转Frenet坐标 :param point: 全局坐标(x,y) :return: (s, d) s为纵向位移d为横向偏移 # 找到最近点 def distance_to_spline(s): spline_point np.array([self.spline_x(s), self.spline_y(s)]) return np.linalg.norm(spline_point - point) from scipy.optimize import minimize res minimize(distance_to_spline, x00, bounds[(0, self.cumulative_distances[-1])]) s res.x[0] # 计算横向偏移 tangent_vector np.array([ self.spline_x(s, 1), # x的一阶导数 self.spline_y(s, 1) # y的一阶导数 ]) normal_vector np.array([-tangent_vector[1], tangent_vector[0]]) normal_vector normal_vector / np.linalg.norm(normal_vector) spline_point np.array([self.spline_x(s), self.spline_y(s)]) d np.dot(point - spline_point, normal_vector) return s, d4. 完整仿真系统实现4.1 可视化与交互设计使用Matplotlib构建交互式仿真界面import matplotlib.pyplot as plt from matplotlib.widgets import Slider class BicycleSimulator: def __init__(self, model, path): self.model model self.path path self.fig, self.ax plt.subplots(figsize(10, 8)) plt.subplots_adjust(bottom0.25) # 添加控制滑块 ax_vel plt.axes([0.2, 0.1, 0.6, 0.03]) ax_steer plt.axes([0.2, 0.05, 0.6, 0.03]) self.vel_slider Slider(ax_vel, Velocity, -5.0, 5.0, valinit0) self.steer_slider Slider(ax_steer, Steer, -np.pi/4, np.pi/4, valinit0) # 初始化绘图元素 self.vehicle_plot, self.ax.plot([], [], bo-) self.path_plot, self.ax.plot([], [], g-) self.trajectory_plot, self.ax.plot([], [], r--) self.update_plot() def update(self, eventNone): v self.vel_slider.val steer self.steer_slider.val self.model.update(v, steer, 0.1) self.update_plot() def update_plot(self): # 更新路径显示 s np.linspace(0, self.path.cumulative_distances[-1], 100) path_points np.column_stack((self.path.spline_x(s), self.path.spline_y(s))) self.path_plot.set_data(path_points[:,0], path_points[:,1]) # 更新车辆显示 x, y, heading self.model.state vehicle_shape np.array([ [x 2*np.cos(heading), y 2*np.sin(heading)], [x - 1*np.cos(heading) - 0.5*np.sin(heading), y - 1*np.sin(heading) 0.5*np.cos(heading)], [x, y], [x - 1*np.cos(heading) 0.5*np.sin(heading), y - 1*np.sin(heading) - 0.5*np.cos(heading)] ]) self.vehicle_plot.set_data(vehicle_shape[:,0], vehicle_shape[:,1]) # 更新轨迹 if not hasattr(self, trajectory): self.trajectory [] self.trajectory.append([x, y]) self.trajectory_plot.set_data(np.array(self.trajectory).T) self.ax.relim() self.ax.autoscale_view() self.fig.canvas.draw_idle()4.2 仿真案例与参数调优通过调整模型参数观察车辆行为变化参数典型值范围影响效果轴距(L)2.5-3.0m影响转向灵敏度最大转向角25-35度决定最小转弯半径速度范围-5~5 m/s负值表示倒车时间步长(dt)0.01-0.1s影响仿真精度和计算效率# 创建仿真实例 waypoints [(0,0), (10,5), (20,-5), (30,10), (40,0)] ref_path ReferencePath(waypoints) model BicycleModel(wheelbase2.7, max_steernp.radians(30)) simulator BicycleSimulator(model, ref_path) # 连接滑块事件 simulator.vel_slider.on_changed(simulator.update) simulator.steer_slider.on_changed(simulator.update) plt.show()5. 模型验证与误差分析5.1 理论验证方法验证运动学模型的准确性可通过以下方法圆周运动测试恒定转向角和速度下车辆应做匀速圆周运动直线运动测试零转向角时车辆应保持直线行驶路径跟踪测试比较仿真轨迹与理论轨迹的偏差def test_circular_motion(): 验证圆周运动 model BicycleModel(wheelbase2.7) states [] for _ in range(100): states.append(model.update(2.0, np.radians(10), 0.1).copy()) # 计算实际转弯半径 positions np.array(states)[:,:2] centroid np.mean(positions, axis0) radii np.linalg.norm(positions - centroid, axis1) avg_radius np.mean(radii) # 理论转弯半径 theoretical_radius model.wheelbase / np.tan(np.radians(10)) print(f实测平均半径: {avg_radius:.2f}m, 理论半径: {theoretical_radius:.2f}m) return abs(avg_radius - theoretical_radius) / theoretical_radius5.2 误差来源与改进运动学模型的主要误差来源包括轮胎侧偏忽略实际车辆转弯时轮胎会产生侧偏角悬架系统影响车身侧倾会改变轮胎接地特性执行器延迟转向和驱动系统存在响应延迟提示在低速场景5m/s下运动学模型误差通常在可接受范围内。对于高速场景建议切换到动力学模型。6. 进阶应用与扩展6.1 与控制系统集成将运动学模型作为模型预测控制(MPC)的预测模型def mpc_controller(model, reference_path, horizon10): 简化的MPC控制器 current_state model.state s, d reference_path.global_to_frenet(current_state[:2]) # 预测未来状态 predicted_states [] state current_state.copy() for i in range(horizon): # 简单控制策略减小横向偏差 target_steer -0.5 * d state model.update(2.0, target_steer, 0.1) predicted_states.append(state) _, d reference_path.global_to_frenet(state[:2]) return predicted_states6.2 多模型对比分析扩展模型类支持不同变体class ExtendedBicycleModel(BicycleModel): 考虑轮胎侧偏的扩展模型 def __init__(self, wheelbase, max_steer, cornering_stiffness): super().__init__(wheelbase, max_steer) self.cornering_stiffness cornering_stiffness def update(self, velocity, steer_angle, dt): # 计算侧偏角 if abs(velocity) 0.1: beta np.arctan(0.5 * np.tan(steer_angle)) effective_steer steer_angle - beta else: effective_steer steer_angle # 调用父类更新方法 return super().update(velocity, effective_steer, dt)7. 性能优化技巧提升运动学模型计算效率的关键方法向量化运算使用NumPy进行批量状态更新查表法预计算常用参数组合并行计算利用多核处理多个轨迹预测def batch_predict(initial_states, controls, dt): 批量预测车辆状态 :param initial_states: 初始状态数组(N,3) :param controls: 控制输入数组(N,2) (velocity, steer) :param dt: 时间步长 :return: 预测状态数组(N,3) velocities controls[:,0] steers controls[:,1] headings initial_states[:,2] # 向量化计算 new_x initial_states[:,0] velocities * np.cos(headings) * dt new_y initial_states[:,1] velocities * np.sin(headings) * dt new_heading headings velocities * np.tan(steers) / self.wheelbase * dt # 角度归一化 new_heading (new_heading np.pi) % (2 * np.pi) - np.pi return np.column_stack((new_x, new_y, new_heading))8. 实际工程注意事项在实车应用中需考虑以下实际问题参数标定精确测量车辆轴距、最大转向角等参数传感器融合结合GPS、IMU和轮速计数据提高状态估计精度执行器限制考虑转向电机响应速度和加速度限制注意仿真与实车之间存在差距建议通过以下步骤验证在仿真环境中充分测试进行低速实车测试逐步提高测试速度根据实测数据修正模型参数