3. 车辆运动学模型
各位同学,大家好。今天我们聊一个非常基础但又极其重要的内容——车辆运动学模型。说实话,我在刚入行那会儿,总觉得运动学模型太简单,不就是个几何关系嘛。直到有一次在实车测试中,因为模型精度不够,导致车辆在泊车时蹭到了路沿……嗯,从那以后,我再也不敢小看这个“简单”的模型了。
3.1 为什么需要运动学模型?
你想想看,自动驾驶系统要控制车辆,总得知道车接下来会怎么动吧?运动学模型就是干这个的。它描述了车辆在不考虑力和力矩的情况下,纯粹由几何约束决定的运动关系。
说白了,运动学模型回答三个问题:
- 车现在在哪儿?(位置)
- 车头朝哪儿?(航向角)
- 打方向盘后,车会怎么走?(运动趋势)
我个人习惯把运动学模型比作“车辆的骨架”,动力学模型则是“肌肉和神经”。做规划控制时,我们先用骨架搭好框架,再考虑肌肉的力量。这样分层处理,问题会清晰很多。
3.2 自行车模型(Bicycle Model)
这是最经典的简化模型。为什么叫自行车模型?因为它把四轮车辆等效成两轮——前轮和后轮各一个,就像自行车一样。
核心假设:
- 车辆左右对称,左右轮转角相同
- 忽略车辆的侧倾和俯仰
- 轮胎无侧偏(纯滚动)
- 车辆在平面内运动
我在项目中遇到过一个问题:用自行车模型做路径跟踪,结果在低速时效果很好,一上高速就偏差很大。后来发现,高速时轮胎侧偏不可忽略,这时候就得用动力学模型了。所以记住:自行车模型适用于低速场景(一般 < 10 m/s)。
自行车模型的运动学方程如下:
状态变量:
x : 车辆后轴中心 x 坐标
y : 车辆后轴中心 y 坐标
θ : 车辆航向角(车头朝向与 x 轴夹角)
δ : 前轮转角
v : 车辆纵向速度
运动学方程:
dx/dt = v * cos(θ)
dy/dt = v * sin(θ)
dθ/dt = v * tan(δ) / L
其中 L 为轴距(前后轮距离)
这里有个细节要注意:dθ/dt 就是横摆角速度。你想想看,如果 δ=0,车直行,横摆角速度就是0。如果 δ 很大,转弯半径就小,横摆角速度就大。这个关系在规划中非常常用。
3.3 阿克曼转向模型(Ackermann Steering Model)
自行车模型假设左右轮转角相同,但实际车辆不是这样的。真实车辆转弯时,内侧轮转角比外侧轮大,这样才能保证所有车轮绕同一个圆心转动——这就是阿克曼转向原理。
阿克曼转向几何关系:
cot(δ_out) - cot(δ_in) = T / L
其中 δ_out 为外侧轮转角,δ_in 为内侧轮转角,T 为轮距,L 为轴距。
实际中,我们通常用等效前轮转角 δ 来近似:δ ≈ (δ_in + δ_out) / 2
我曾经在调试自动泊车功能时,发现车辆总是停偏。排查了半天,最后发现是阿克曼转向的补偿没做好。内侧轮和外侧轮的转角差如果不考虑,泊车轨迹会偏差十几厘米——这在狭窄车位里就是灾难。
阿克曼转向模型和自行车模型的关系是这样的:
| 对比项 | 自行车模型 | 阿克曼转向模型 |
|---|---|---|
| 车轮数量 | 等效2轮 | 实际4轮 |
| 左右轮转角 | 相同 | 不同(满足阿克曼几何) |
| 适用场景 | 规划、控制算法设计 | 精确轨迹计算、实车标定 |
| 计算复杂度 | 低 | 中 |
在实际工程中,我建议:做规划算法时用自行车模型,做控制执行时用阿克曼模型。这样既保证了效率,又兼顾了精度。
3.4 状态空间表示
有了运动学方程,我们怎么在计算机里表示它?答案是状态空间模型。
状态空间表示说白了就是:把系统写成 x_dot = f(x, u) 的形式。
对于自行车模型:
状态向量:x = [x, y, θ, δ, v]^T
控制输入:u = [δ_dot, a]^T (前轮转角速度、加速度)
状态方程:
x_dot = v * cos(θ)
y_dot = v * sin(θ)
θ_dot = v * tan(δ) / L
δ_dot = u[0]
v_dot = u[1]
为什么要把系统写成状态空间形式?因为后续的模型预测控制(MPC)、线性二次型调节器(LQR)等算法,都直接基于状态空间模型。你想想看,如果连状态方程都写不出来,后面的控制算法根本没法用。
注意:状态空间模型中的状态变量必须是最小完备集。什么意思?就是这些变量足以描述系统的全部运动状态,且没有冗余。比如,如果你已经知道 x, y, θ,就不需要再单独存一个“车头方向向量”了。
3.5 离散化方法
计算机是离散的,而车辆运动是连续的。所以我们需要把连续的运动学方程离散化。
常用的离散化方法有三种:
- 欧拉法(Euler Method):最简单,但精度一般
- 龙格-库塔法(Runge-Kutta):精度高,计算量稍大
- 零阶保持法(ZOH):假设控制输入在采样间隔内不变
我个人最常用的是欧拉法,因为它在大多数规划场景中已经够用了。代码实现如下:
// 欧拉法离散化
// dt: 采样时间间隔
// x_k: 当前状态
// u_k: 当前控制输入
function discreteStep(x_k, u_k, dt) {
// 计算导数
dx = v_k * cos(theta_k);
dy = v_k * sin(theta_k);
dtheta = v_k * tan(delta_k) / L;
ddelta = u_k[0];
dv = u_k[1];
// 更新状态
x_next = x_k + dx * dt;
y_next = y_k + dy * dt;
theta_next = theta_k + dtheta * dt;
delta_next = delta_k + ddelta * dt;
v_next = v_k + dv * dt;
return [x_next, y_next, theta_next, delta_next, v_next];
}
经验之谈:采样时间 dt 的选择很关键。dt 太大,离散化误差大;dt 太小,计算量太大。我一般取 dt = 0.05s(20Hz)作为基准,然后根据场景调整。高速场景用更小的 dt,低速场景可以适当放大。
3.6 本章知识体系
为了让大家更直观地理解本章的知识结构,我画了一张图:
从这张图可以看出,自行车模型和阿克曼模型是运动学描述的两种方式,状态空间表示是它们的数学抽象,而离散化方法则是连接连续模型和数字计算机的桥梁。最终,这些知识都服务于上层控制算法。
3.7 避坑指南
最后,分享几个我踩过的坑:
我曾经犯过的错误:
- 直接用自行车模型做高速路径跟踪,结果车辆剧烈振荡——后来加了动力学补偿才解决
- 离散化时 dt 取得太大(0.1s),导致轨迹出现锯齿——改成 0.05s 后就好了
- 状态空间表示中漏掉了 δ 这个状态,导致控制量无法约束——这个bug查了我两天
嗯,今天就讲到这里。运动学模型虽然基础,但它是整个规划控制算法的基石。希望大家课后能动手写一下代码,把自行车模型离散化,然后做个简单的圆周运动仿真。你会发现,理论到实践之间,还有不少细节需要打磨。