Python实战:用Matplotlib模拟半挂车倒车轨迹(附完整代码)

张开发
2026/4/14 20:02:19 15 分钟阅读

分享文章

Python实战:用Matplotlib模拟半挂车倒车轨迹(附完整代码)
Python实战用Matplotlib模拟半挂车倒车轨迹附完整代码半挂车倒车一直是驾驶技术中的高难度操作即使是经验丰富的司机也常常感到头疼。想象一下当你需要将一辆带有挂车的卡车倒进狭窄的装卸区时方向盘向左打还是向右打挂车会如何响应这些问题在实际操作中往往需要反复尝试才能掌握。但今天我们将用Python和Matplotlib来模拟这一过程让你在代码中直观地看到半挂车倒车的轨迹变化。对于Python开发者和车辆运动学爱好者来说这个项目不仅能提升编程技能还能深入理解复杂车辆系统的运动规律。我们将避开繁琐的数学推导直接通过代码实现可视化效果让你看到每一步操作如何影响车辆的轨迹。1. 环境准备与基础设置在开始之前我们需要确保开发环境配置正确。这个项目主要依赖Python的科学计算栈特别是NumPy和Matplotlib这两个库。首先安装必要的依赖pip install numpy matplotlib imageio接下来我们创建一个VehicleParamInfo类来存储半挂车的各项参数。这些参数包括牵引车和挂车的尺寸、轴距等关键数据它们将直接影响模拟的准确性。class VehicleParamInfo: # 牵引车参数 L 3.0 # 轴距 W 2.0 # 宽度 LF 3.8 # 后轴中心到车头距离 LB 0.8 # 后轴中心到车尾距离 MAX_STEER 0.6 # 最大前轮转角 TR 0.5 # 轮子半径 TW 0.5 # 轮子宽度 WD W # 轮距 LENGTH LB LF # 车辆长度 La 2.7 # 铰接点到牵引车前轴中心的距离 Lb 0.3 # 铰接点到牵引车后轴中心的距离 # 挂车参数 L1 7 # 挂车轴距 LF1 8.0 # 挂车后轴中心到车头距离 LB1 1.0 # 挂车后轴中心到车尾距离为了处理角度计算中的周期性问题我们还需要一个角度归一化函数import math import numpy as np def normalize_angle(angle): 将角度归一化到[-π, π]区间 a math.fmod(angle np.pi, 2 * np.pi) if a 0.0: a (2.0 * np.pi) return a - np.pi2. 车辆模型类实现现在我们来创建Vehicle类它将封装半挂车的状态和运动逻辑。这个类需要跟踪牵引车和挂车的位置、航向角等信息并根据转向输入更新这些状态。class Vehicle: def __init__(self, x10.0, y10.0, yaw0.0, v0.0, dt0.1, vehicle_param_infoVehicleParamInfo): self.steer 0 # 当前转向角 self.x1 x1 # 挂车后轴中心x坐标 self.y1 y1 # 挂车后轴中心y坐标 self.yaw yaw # 牵引车航向角 self.v v # 车速 self.dt dt # 时间步长 self.vehicle_param_info vehicle_param_info # 初始化挂车状态 self.yaw1 yaw # 挂车航向角 self.x_front1 self.x1 self.vehicle_param_info.L1 * math.cos(self.yaw1) self.y_front1 self.y1 self.vehicle_param_info.L1 * math.sin(self.yaw1) # 初始化牵引车状态 self.x self.x_front1 - self.vehicle_param_info.Lb * math.cos(self.yaw) self.y self.y_front1 - self.vehicle_param_info.Lb * math.sin(self.yaw) self.x_front self.x self.vehicle_param_info.L * math.cos(self.yaw) self.y_front self.y self.vehicle_param_info.L * math.sin(self.yaw)update方法是核心它根据当前转向输入更新车辆状态def update(self, a, delta, max_steerNone): if max_steer is None: max_steer self.vehicle_param_info.MAX_STEER delta np.clip(delta, -max_steer, max_steer) self.steer delta # 计算铰接角牵引车与挂车的航向差 hitchAngle normalize_angle(self.yaw - self.yaw1) # 更新挂车位置 self.x1 self.v * (math.cos(hitchAngle) self.vehicle_param_info.Lb * math.tan(delta) / self.vehicle_param_info.L * math.sin(hitchAngle)) * math.cos(self.yaw1) * self.dt self.y1 self.v * (math.cos(hitchAngle) self.vehicle_param_info.Lb * math.tan(delta) / self.vehicle_param_info.L * math.sin(hitchAngle)) * math.sin(self.yaw1) * self.dt # 更新挂车前轴中心位置 self.x_front1 self.x1 self.vehicle_param_info.L1 * math.cos(self.yaw1) self.y_front1 self.y1 self.vehicle_param_info.L1 * math.sin(self.yaw1) # 更新牵引车位置 self.x self.x_front1 - self.vehicle_param_info.Lb * math.cos(self.yaw) self.y self.y_front1 - self.vehicle_param_info.Lb * math.sin(self.yaw) self.x_front self.x self.vehicle_param_info.L * math.cos(self.yaw) self.y_front self.y self.vehicle_param_info.L * math.sin(self.yaw) # 更新牵引车航向角 self.yaw self.v / self.vehicle_param_info.L * math.tan(delta) * self.dt self.yaw normalize_angle(self.yaw) # 更新车速 self.v a * self.dt # 更新挂车航向角 self.yaw1 self.v / self.vehicle_param_info.L1 * ( math.sin(hitchAngle) - self.vehicle_param_info.Lb * math.tan(self.steer) * math.cos(hitchAngle) / self.vehicle_param_info.L) * self.dt self.yaw1 normalize_angle(self.yaw1)3. 可视化绘制功能为了直观展示半挂车的运动我们需要实现一个绘制函数能够在Matplotlib图上绘制车辆和挂车的轮廓。def draw_vehicle_trailer(x, y, yaw, x1, y1, yaw1, steer, ax, vehicle_param_infoVehicleParamInfo, colorblack): # 牵引车轮廓 vehicle_outline np.array([ [-vehicle_param_info.LB, vehicle_param_info.LF, vehicle_param_info.LF, -vehicle_param_info.LB, -vehicle_param_info.LB], [vehicle_param_info.W / 2, vehicle_param_info.W / 2, -vehicle_param_info.W / 2, -vehicle_param_info.W / 2, vehicle_param_info.W / 2] ]) # 挂车轮廓 vehicle_outline1 np.array([ [-vehicle_param_info.LB1, vehicle_param_info.LF1, vehicle_param_info.LF1, -vehicle_param_info.LB1, -vehicle_param_info.LB1], [vehicle_param_info.W / 2, vehicle_param_info.W / 2, -vehicle_param_info.W / 2, -vehicle_param_info.W / 2, vehicle_param_info.W / 2] ]) # 车轮轮廓 wheel np.array([ [-vehicle_param_info.TR, vehicle_param_info.TR, vehicle_param_info.TR, -vehicle_param_info.TR, -vehicle_param_info.TR], [vehicle_param_info.TW / 2, vehicle_param_info.TW / 2, -vehicle_param_info.TW / 2, -vehicle_param_info.TW / 2, vehicle_param_info.TW / 2] ]) # 创建四个车轮的副本 rr_wheel wheel.copy() # 右后轮 rl_wheel wheel.copy() # 左后轮 fr_wheel wheel.copy() # 右前轮 fl_wheel wheel.copy() # 左前轮 rr_wheel1 wheel.copy() # 挂车右后轮 rl_wheel1 wheel.copy() # 挂车左后轮 # 调整车轮位置 rr_wheel[1, :] vehicle_param_info.WD / 2 rl_wheel[1, :] - vehicle_param_info.WD / 2 rr_wheel1[1, :] vehicle_param_info.WD / 2 rl_wheel1[1, :] - vehicle_param_info.WD / 2 # 创建旋转矩阵 rot1 np.array([[np.cos(steer), -np.sin(steer)], [np.sin(steer), np.cos(steer)]]) rot2 np.array([[np.cos(yaw), -np.sin(yaw)], [np.sin(yaw), np.cos(yaw)]]) rot3 np.array([[np.cos(yaw1), -np.sin(yaw1)], [np.sin(yaw1), np.cos(yaw1)]]) # 旋转前轮 fr_wheel np.dot(rot1, fr_wheel) fl_wheel np.dot(rot1, fl_wheel) # 移动前轮到正确位置 fr_wheel np.array([[vehicle_param_info.L], [-vehicle_param_info.WD / 2]]) fl_wheel np.array([[vehicle_param_info.L], [vehicle_param_info.WD / 2]]) # 应用整体旋转 fr_wheel np.dot(rot2, fr_wheel) fr_wheel[0, :] x fr_wheel[1, :] y fl_wheel np.dot(rot2, fl_wheel) fl_wheel[0, :] x fl_wheel[1, :] y # 处理后轮 rr_wheel np.dot(rot2, rr_wheel) rr_wheel[0, :] x rr_wheel[1, :] y rl_wheel np.dot(rot2, rl_wheel) rl_wheel[0, :] x rl_wheel[1, :] y # 处理牵引车轮廓 vehicle_outline np.dot(rot2, vehicle_outline) vehicle_outline[0, :] x vehicle_outline[1, :] y # 处理挂车车轮 rr_wheel1 np.dot(rot3, rr_wheel1) rr_wheel1[0, :] x1 rr_wheel1[1, :] y1 rl_wheel1 np.dot(rot3, rl_wheel1) rl_wheel1[0, :] x1 rl_wheel1[1, :] y1 # 处理挂车轮廓 vehicle_outline1 np.dot(rot3, vehicle_outline1) vehicle_outline1[0, :] x1 vehicle_outline1[1, :] y1 # 绘制所有元素 ax.plot(fr_wheel[0, :], fr_wheel[1, :], color) ax.plot(rr_wheel[0, :], rr_wheel[1, :], color) ax.plot(fl_wheel[0, :], fl_wheel[1, :], color) ax.plot(rl_wheel[0, :], rl_wheel[1, :], color) ax.plot(rr_wheel1[0, :], rr_wheel1[1, :], color) ax.plot(rl_wheel1[0, :], rl_wheel1[1, :], color) ax.plot(vehicle_outline[0, :], vehicle_outline[1, :], color) ax.plot(vehicle_outline1[0, :], vehicle_outline1[1, :], color)4. 主程序与模拟运行现在我们可以编写主程序来运行模拟了。我们将创建一个动画展示半挂车倒车时的轨迹变化。if __name__ __main__: # 初始化车辆 vehicle Vehicle(x10.0, y10.0, yaw0.0, v0.0, dt0.1) vehicle.v -1 # 负值表示倒车 # 存储轨迹数据 trajectory_x [] trajectory_y [] trailer_trajectory_x [] trailer_trajectory_y [] # 创建图形 fig, ax plt.subplots() image_list [] # 用于存储动画帧 # 模拟循环 for i in range(500): ax.cla() ax.set_aspect(equal, adjustablebox) # 简单的转向控制逻辑 delta np.pi / 12 if (vehicle.yaw - vehicle.yaw1) 0.2 else -np.pi / 12 # 更新车辆状态 vehicle.update(0, delta) # 绘制当前车辆状态 draw_vehicle_trailer(vehicle.x, vehicle.y, vehicle.yaw, vehicle.x1, vehicle.y1, vehicle.yaw1, vehicle.steer, ax) # 记录轨迹 trajectory_x.append(vehicle.x) trajectory_y.append(vehicle.y) trailer_trajectory_x.append(vehicle.x1) trailer_trajectory_y.append(vehicle.y1) # 绘制轨迹 ax.plot(trajectory_x, trajectory_y, blue, label牵引车轨迹) ax.plot(trailer_trajectory_x, trailer_trajectory_y, green, label挂车轨迹) # 设置图形范围 ax.set_xlim(-45, 15) ax.set_ylim(-5, 35) ax.legend() plt.pause(0.001) # 如果需要生成GIF动画可以取消下面的注释 # plt.savefig(temp.png) # if (i % 30) 0: # image_list.append(imageio.imread(temp.png)) # 保存GIF动画 # imageio.mimsave(semi_truck_backing.gif, image_list, duration0.1)5. 模拟结果分析与优化运行上述代码后你将看到一个半挂车倒车的动态模拟。蓝色线条表示牵引车的轨迹绿色线条表示挂车的轨迹。从模拟中我们可以观察到几个关键现象铰接效应挂车的运动总是滞后于牵引车这种滞后效应随着铰接角牵引车与挂车的角度差的增大而更加明显。转向控制我们的简单控制逻辑基于铰接角调整转向能够使车辆保持相对稳定的倒车状态但仍有优化空间。轨迹差异牵引车和挂车的轨迹明显不同这解释了为什么半挂车倒车如此具有挑战性。为了改进模拟效果我们可以尝试以下优化控制算法改进# 更复杂的转向控制逻辑示例 def advanced_control(vehicle, target_angle0.2): hitch_angle normalize_angle(vehicle.yaw - vehicle.yaw1) error hitch_angle - target_angle delta -error * 2.0 # 比例控制 return np.clip(delta, -vehicle.vehicle_param_info.MAX_STEER, vehicle.vehicle_param_info.MAX_STEER)参数调整建议参数名称描述调整建议影响效果L (牵引车轴距)牵引车前轴到后轴的距离增大值转向响应变慢轨迹更平滑L1 (挂车轴距)挂车前轴到后轴的距离减小值挂车响应更快但稳定性降低Lb (铰接点到牵引车后轴距离)影响铰接效应强度减小值减弱铰接效应挂车更易控制MAX_STEER最大转向角增大值转向更灵敏但可能不稳定可视化增强# 在draw_vehicle_trailer函数中添加以下内容可以增强可视化效果 ax.plot([vehicle.x, vehicle.x_front1], [vehicle.y, vehicle.y_front1], r--, linewidth1) ax.plot([vehicle.x_front1, vehicle.x1], [vehicle.y_front1, vehicle.y1], r--, linewidth1)通过这些优化你可以创建更精确、更直观的半挂车倒车模拟深入理解这一复杂运动过程的动力学特性。

更多文章