车辆运动学模型:从理论到代码实现
做无人车路径跟踪,第一步要解决什么问题?
说白了,你得知道车怎么动。你给方向盘一个角度,车会怎么转弯?你踩油门,车会怎么加速?这些问题的答案,就藏在运动学模型里。
我个人习惯,先从最简单的模型入手。今天咱们就聊聊自行车模型、阿克曼转向,还有怎么把连续方程变成代码能跑的离散形式。
一、自行车模型:把四轮车简化成两轮
你想想看,一辆四轮车转弯时,四个轮子的转向角其实不一样。但如果我们只关心车身的运动轨迹,能不能简化一下?
当然可以。自行车模型就是干这个的。
它把前后轮分别合并成一个虚拟轮。前轮负责转向,后轮负责驱动。这样一来,四轮车的运动就变成了两轮车的运动。
核心假设:
- 车辆在平面上运动,忽略垂向跳动
- 轮胎无侧滑,即速度方向与轮胎朝向一致
- 前后轮刚性连接,车身为刚体
我在项目中遇到过一个问题:用自行车模型做高速路径跟踪,结果偏差越来越大。后来发现,高速时轮胎侧偏不可忽略,自行车模型就不够用了。所以记住,这个模型适合低速场景,比如泊车、园区低速巡航。
二、阿克曼转向模型:真实车辆的转向几何
自行车模型虽然简单,但真实车辆不是这么转的。你开过车就知道,转弯时内侧轮和外侧轮转的角度不一样。
这就是阿克曼转向几何要解决的问题。
阿克曼转向的核心:
所有车轮的转向中心交于一点。这样车轮在转弯时才能纯滚动,不产生侧滑。
数学上怎么描述?假设轴距为 L,前轮外轮转角为 δ_o,内轮转角为 δ_i,它们满足:
cot(δ_o) - cot(δ_i) = 轮距 / L
嗯,这里要注意。实际工程中,我们通常用等效前轮转角 δ 来近似。δ 取内外轮转角的平均值。这样就把阿克曼模型又简化回了自行车模型的形式。
我曾经在实车调试时,发现转向拉杆的机械间隙导致左右轮转角不对称。阿克曼条件被破坏,车辆跑偏严重。后来加了标定补偿才解决。所以做算法时,别忘了考虑机械误差。
三、运动学微分方程:车到底怎么动
有了模型,接下来要描述车的运动状态。我们关心三个量:位置 (x, y) 和航向角 θ。
对于自行车模型,运动学微分方程如下:
dx/dt = v * cos(θ)
dy/dt = v * sin(θ)
dθ/dt = v * tan(δ) / L
其中:
- v:后轴中心速度(纵向速度)
- δ:前轮转角
- L:轴距
为什么是 tan(δ) 而不是 sin(δ)?我刚开始学的时候也困惑过。其实很简单,因为前轮速度方向垂直于轮子,而车身的转弯半径 R = L / tan(δ)。角速度 ω = v / R = v * tan(δ) / L。
注意:当 δ 接近 ±90° 时,tan(δ) 趋于无穷大。实际车辆转向角通常不超过 ±40°,所以没问题。但代码里最好加个限幅保护。
四、模型离散化:让计算机能算
微分方程是连续的,但计算机只能处理离散数据。我们需要把连续方程变成差分方程。
最常用的方法是欧拉法。简单粗暴,但够用。
x(k+1) = x(k) + v * cos(θ(k)) * dt
y(k+1) = y(k) + v * sin(θ(k)) * dt
θ(k+1) = θ(k) + v * tan(δ(k)) / L * dt
dt 是采样时间。我建议 dt 取 0.01s 到 0.05s 之间。太小了计算量大,太大了精度不够。
离散化精度对比:
| 方法 | 精度 | 计算量 | 适用场景 |
|---|---|---|---|
| 欧拉法 | O(dt) | 低 | 低速、大采样频率 |
| 改进欧拉法 | O(dt²) | 中 | 中速、一般精度要求 |
| 龙格-库塔法 | O(dt⁴) | 高 | 高速、高精度要求 |
我个人习惯,做原型验证时先用欧拉法。跑通了再换高阶方法。别一开始就上龙格-库塔,调试起来太费劲。
五、知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以对照着看,心里有个框架。
六、代码实现:把公式跑起来
光说不练假把式。下面给一个 Python 实现,你直接复制就能跑。
import numpy as np
class BicycleModel:
"""自行车运动学模型"""
def __init__(self, L=2.5, dt=0.02):
self.L = L # 轴距,单位米
self.dt = dt # 采样时间,单位秒
def step(self, x, y, theta, v, delta):
"""
单步更新
x, y, theta: 当前状态
v: 纵向速度 (m/s)
delta: 前轮转角 (rad)
"""
# 限幅保护
delta = np.clip(delta, -0.6, 0.6)
# 欧拉法离散化
x_next = x + v * np.cos(theta) * self.dt
y_next = y + v * np.sin(theta) * self.dt
theta_next = theta + v * np.tan(delta) / self.L * self.dt
return x_next, y_next, theta_next
# 使用示例
model = BicycleModel(L=2.5, dt=0.02)
x, y, theta = 0.0, 0.0, 0.0
for i in range(100):
x, y, theta = model.step(x, y, theta, v=5.0, delta=0.1)
print(f"t={i*0.02:.2f}s: x={x:.2f}, y={y:.2f}, theta={theta:.3f}")
小技巧:调试时可以把 theta 打印出来看看。如果 theta 一直在增大或减小,说明转弯半径计算没问题。如果 theta 跳变,检查一下 tan(delta) 是不是溢出了。
七、避坑指南
最后分享几个我踩过的坑:
- 坐标系搞反:我曾经把 x 和 y 搞混,结果车在原地打转。建议统一用右手坐标系,x 向前,y 向左。
- 角度单位混淆:代码里用弧度,但实车标定数据可能是角度。我吃过这个亏,跑出来的轨迹完全不对。
- dt 不固定:实际系统中,控制周期可能有抖动。建议用实际时间戳计算 dt,别用固定值。
好了,运动学模型就聊到这儿。你把这些代码跑一遍,感受一下车的运动规律。下一章咱们聊路径跟踪控制,到时候这些模型就是基础了。
公众号:蓝海资料掘金营,微信deep3321