1. 横向控制在无人驾驶里到底解决什么问题
我先说一个背景,省的后面聊算法时大家脑补不出来画面。无人驾驶系统的软件架构通常被切成分层结构:感知、融合、预测、决策规划、控制。前面几层往往最受关注,比如激光雷达点云、目标检测、车道线识别、行为预测那套,热闹且视觉冲击力强。但真正到了车辆执行层面,就是个把“脑子里想好的路径”变成“前轮实际打过去的角度”的问题,这一层叫车辆控制,又分横向控制和纵向控制。
纵向控制管油门和刹车,解决的是“什么时候加速、什么时候减速、最终稳在目标车速上”。横向控制管方向盘,解决的是“这条期望路径,车怎么跟踪住、不偏离、不振荡”。做控制的朋友应该能体会:规划层给出再漂亮的轨迹,控制层跟不住,或者跟出了蛇形,整个自动驾驶表现得就像个新手司机,体验非常差。更严重的是,在狭窄工况、高速避障、倒车入库这些场景里,横向跟踪误差一上去,直接就是剐蹭甚至碰撞的风险。
所以横向控制是无人驾驶车从L2辅助驾驶一路走到L4、L5绕不开的核心模块。今天聊的是横向控制里两个最经典、也是目前落地工程里最常见的几何类跟踪算法:Pure Pursuit(纯跟踪)和 Stanley。这两个算法不依赖复杂的车辆动力模型,也不要求你提前把整个动力学矩阵建出来,它们都以“车辆运动学自行车模型”为底层假设,只要知道轴距、当前速度和前轮转角指令,就能把车沿路推着走。它们足够简洁、可调参项直观、便于嵌入式部署,在无数真实项目和仿真环境里经历了大量验证。
这两个算法,我后来在实车和仿真里都反复调过。今天的文章我打算从横向控制的误差定义说起,把两条算法的几何原理、数学模型、代码实现、参数整定逻辑和常见坑一次性讲透。内容不抬杠、不堆公式,但该有的数学推导我也不会省,毕竟控制这种事,定性描述只能帮你理解八成,剩下两成必须在式子里才能看着不掉坑。适合入门无人驾驶控制方向的工程师、准备做毕设或课题的硕博生,以及想把横向控制模块整合进自己的无人车平台、或者在Simulink/Carla/ROS里做仿真实验的开发者。
2. 先搞明白横向控制的目标和误差定义
2.1 横向控制的输入输出长什么样
在实际的控制器实现里,横向控制的输入一般有这么几组:
- 参考路径,通常来自规划模块,是一条带有姿态信息的轨迹点序列,每个点至少包含x、y坐标和航向角。
- 当前车辆状态,来自定位与惯性导航融合后的结果,包括车辆后轴中心位置、横摆角、横摆角速度、纵向车速等。
- 车辆参数,比如轴距、转向机构传动比等。
- 输出,则是一个方向盘的转角指令,或者下发给转向执行机构的弧度/角度值。
这里有个非常关键的概念:横向控制跟踪的参考路径到底以哪个点为基准。不同的算法基准不一样,这直接决定公式的形式和控制效果。Pure Pursuit和Stanley虽然都基于自行车模型,但Pure Pursuit本质上是基于“车辆后轴中心点”的跟踪,而Stanley则是基于“车辆前轴中心点”的跟踪。这一点很多人学的时候容易忽略,等到实车调试才发现同样的路径下两种算法的误差表现差异很大,很多时候就是吃了这种基准理解的亏。
2.2 横向跟踪误差分哪些
在深入算法之前,把误差的类型捋清楚非常必要。横向控制领域里有三种最常见的误差:
- 横向位置误差,即车辆当前位置到参考路径上最近点的垂直距离,通常记为e_lat或者cross_track_error,这个误差是控制的核心目标之一。
- 航向误差,即车辆当前航向和参考路径上最近点期望航向之间的差值,记为e_heading。
- 曲率误差,即参考路径在当前点的曲率和车辆实际行驶曲率之间的偏差,可以理解成车辆是否“弯得够快”、是否“转的过猛”。
三种误差里,横向位置误差是控制主目标,航向误差是保证车辆姿态正确的重要指标,曲率误差其实由规划路径的连续性和前两个误差的控制效果共同决定。Pure Pursuit算法能直接压制横向位置误差,但对于路径曲率突变的情况响应有限;Stanley算法则同时对横向位置误差和航向误差施加反馈,所以在急弯和切线段过渡的地方表现得更有张力。
在工程上,目标跟踪质量通常用横向误差的均值、最大绝对值和均方根误差来衡量。一个调校良好的横向控制器,在低速园区场景下横向误差均值应该在5厘米以内,高速快速路段也不应该超过25~30厘米。如果超过这个范围,路径跟踪质量就会出现肉眼可感知的偏移。
3. Pure Pursuit 纯跟踪算法拆解
3.1 几何直觉:就像追着一个前方目标点走
Pure Pursuit的核心思想用一句话就能说清楚:在当前车辆后轴中心位置,沿着前方一定距离处选取参考路径上的一个目标点,然后控制车辆前轮转角,让车辆恰好沿着一小段圆弧“追”到这个目标点。
这个思路像极了人走路时的预瞄行为。你往前走路的时候,眼睛往往会盯着前方两三米的路径上某个点,然后自然调整脚步方向,让身体走出平滑的轨迹,而不是低头一步步盯着脚下走。预瞄距离越长,路线越平滑但容易抄近路;预瞄距离太短,则动作迅速但容易抖动。Pure Pursuit的前视距离,就是这个算法里最核心、最需要调参的量。
由此还带出了一个关键特性:Pure Pursuit天然带有“前瞻性”。因为它追踪的是前方路径点而不是当前位置最近点,所以即使参考路径上有一个小小的尖角或者突变,只要预瞄点还没落到那个突变位置,车辆的转角就不太会被扰动。这也是为什么在曲率比较连续的路径上,Pure Pursuit开起来特别顺滑。
3.2 数学模型推导
下面把Pure Pursuit的数学框架推导一遍。为了避免公式符号混乱,先约定几个变量:
- ld:预瞄点到车辆后轴中心的直线距离,即前视距离。
- α:车辆当前航向与预瞄点方向之间的夹角。
- δ:前轮转角。
- L:车辆轴距。
- R:车辆转向圆弧的半径。
控制目标是让车辆沿圆弧到达预瞄点。由平面几何关系,车辆后轴中心、预瞄点与圆弧圆心构成等腰三角形,其中腰的长度是R。结合正弦定理可以得到:
ld / sin(2α) = R / sin(π/2 - α)
化简这个式子。注意sin(2α) = 2 sinα cosα,而sin(π/2 - α) = cosα,所以:
ld / (2 sinα cosα) = R / cosα
两边同时约去一个cosα(前提是α不为90度,正常预瞄场景都不为90度),得到:
ld / (2 sinα) = R
于是圆弧半径R = ld / (2 sinα)。
接下来由自行车模型的转向几何关系,前轮转角δ与转弯半径R之间的关系是:
δ = arctan(L / R)
把上面的R替换进去,最终得到纯跟踪控制律:
δ = arctan(2 L sinα / ld)
这就是Pure Pursuit的核心公式。注意这里面的α虽然叫航向角误差,但实际上它隐含了预瞄点在车辆前方这一条件,所以这也意味着假如预瞄点跑到车辆后方去了,这个式子就不再适用,需要在工程上做特殊处理防止奇异。
这里值得多说一句:从纯运动学的角度看,只要α不为0且ld不为0,前轮转角指令就可以让车辆沿圆弧收敛到预瞄点。但这个“收敛”是开环意义上的收敛,它假设车辆严格按自行车模型运动并且不滑动。考虑到真实车辆的轮胎侧偏、路面附着、转向执行延迟等因素,单靠这个几何关系其实无法保证系统稳定,工程上还必须配合足够的控制频率和预测校正机制。
3.3 前视距离怎么算:最核心的经验公式
前面说了,Pure Pursuit里前视距离ld直接决定跟踪质量。ld太小,车辆对路径变化太敏感,容易出现蛇形摇摆;ld太大,车辆会“抄近路”,转弯时明显切弯,误差在弯道内会被放大。工程上最常用的处理方式是把ld做成纵向车速的线性函数:
ld = k * v + ld0
这里的k称为预瞄时间(或时间增益),单位是秒,ld0是基础预瞄距离,单位是米。常见标定范围低速场景下k在1.0到2.0秒之间,ld0在1.0到3.0米之间;高速场景需要减小k值避免振荡。我自己实际踩过的经验是:在5米长的轴距测试车上,园区低速(5~20km/h)时候取k=1.6、ld0=2.0效果不错,但到了高速公路工况就必须降到k=0.8、ld0=1.0附近,否则车辆在换道曲线上会出现明显的反向回摆。
还要留意一个问题:预瞄点在参考路径上的选取,到底是等间距选取,还是按弧长等距选取。如果参考路径点的间距不均匀,直接按最小欧氏距离找预瞄点会导致预瞄点跳动,从而让转角指令出现毛刺。因此工程上通常先把参考路径点按固定弧长重采样,一般采样间隔取0.1米到0.5米,保证相邻两个路径点的距离远小于前视距离。
这里给一个我用Python写的重采样参考代码,也顺便把预瞄点搜索逻辑一起放出来。这个代码不是最优实现,但足够直观、容易改成C++。
import numpy as np from scipy.interpolate import splprep, splev def resample_path(path_xy, interval=0.2): # path_xy: (N, 2) 数组,包含x,y坐标 x = path_xy[:, 0] y = path_xy[:, 1] # 用样条插值重采样,保证等间距 tck, u = splprep([x, y], s=0) u_new = np.linspace(0, 1, 10000) x_new, y_new = splev(u_new, tck) # 计算累计弧长 diff = np.diff(np.vstack([x_new, y_new]), axis=0) seg_len = np.hypot(diff[:, 0], diff[:, 1]) cum_len = np.cumsum(seg_len) total_len = cum_len[-1] n_points = int(np.ceil(total_len / interval)) resampled = np.zeros((n_points + 1, 2)) resampled[0] = [x_new[0], y_new[0]] target_dist = interval j = 0 for i in range(1, n_points + 1): while cum_len[j] < target_dist and j < len(cum_len) - 1: j += 1 # 线性插值 if j == 0: resampled[i] = [x_new[0], y_new[0]] else: excess = target_dist - cum_len[j-1] ratio = excess / (cum_len[j] - cum_len[j-1]) resampled[i] = [x_new[j-1] + ratio * (x_new[j] - x_new[j-1]), y_new[j-1] + ratio * (y_new[j] - y_new[j-1])] target_dist += interval return resampled重采样之后,预瞄点的搜索只需要在路径上找第一个距离大于当前车辆的ld的点即可。考虑到路径点数量可能很大,用线性搜索在实时系统里可能压力稍大,更快的做法是维护一个“上一帧最近点索引”,然后从那个索引附近开始向后搜索,避免每次全路径扫描。
def find_lookahead_point(current_pose, path_xy, ld, last_idx=0): # current_pose: (x, y) 当前车辆后轴中心 x, y = current_pose n = len(path_xy) # 从上次索引开始搜索 for i in range(last_idx, n): dist = np.hypot(path_xy[i, 0] - x, path_xy[i, 1] - y) if dist >= ld: return path_xy[i], i # 如果没找到(说明离终点太近),返回最后一个点 return path_xy[-1], n - 13.4 纯跟踪的代码实现与仿真
直接给一个完整的控制器类实现,包含了上文提到的核心公式和预瞄距离车速关联逻辑。为了容易测试,我用的是Python,实际项目里改成C++非常容易,也就是一个类的事。
import math class PurePursuitController: def __init__(self, wheelbase=2.5, k=1.6, ld0=2.0, max_steer=0.6): self.L = wheelbase self.k = k self.ld0 = ld0 self.max_steer = max_steer # 单位弧度 def get_lookahead_distance(self, velocity): # velocity 单位 m/s ld = self.k * velocity + self.ld0 return ld def compute_steer_cmd(self, current_pose, current_yaw, velocity, target_point): # current_pose: (x, y) 后轴中心坐标 x, y = current_pose dx = target_point[0] - x dy = target_point[1] - y # 目标点在车体坐标系下的方向角 alpha = math.atan2(dy, dx) - current_yaw # 把alpha归一化到[-pi, pi] alpha = math.atan2(math.sin(alpha), math.cos(alpha)) ld = self.get_lookahead_distance(velocity) if ld < 1e-6: return 0.0 steer = math.atan2(2 * self.L * math.sin(alpha), ld) # 限幅 steer = max(-self.max_steer, min(self.max_steer, steer)) return steer这个实现里有个细节:atan2和角度归一化。工程上很容易犯的错误是直接用dy / dx求角度差,然后接着叠加上车辆航向角做差,结果角度在±π边界处跳变,转角指令瞬间跳变,车就抖了。正确做法是先atan2(dy, dx)得到目标点方向角,再减current_yaw,然后用atan2(sin(α), cos(α))归一化到[-π, π]。这个细节对后续的横向控制稳定性影响很大。
3.5 参数标定心得
我在多款不同轴距的试验车辆上调过Pure Pursuit参数,发现几个规律:
第一,前视距离的上下限一定要钳制住。哪怕车速公式已经给了一个值,实际执行时仍然建议增加一个最小值和最大值限制。比如最小ld不能小于4米,太小了在石子路、起伏路面上响应容易过于激进;最大ld不要超过30米,太远了转弯时会严重提前转向,弯道里内切严重。这个钳制范围跟车辆轴距强相关,我一般按轴距的3到6倍来设定初值。
第二,方向盘的执行延迟会直接影响k值的选取。转向执行器响应慢的车,如果k值取得太小,控制器以为已经转向、实际上前轮还没打到位,就会出现明显的相位滞后,表现就是弯道里先外飘再拉回。遇到这种情况,可以用纯延迟模型做一个Smith预估器,或者直接把k加大一点,给预瞄多留一些时间。调整的时候建议用仿真先测出来执行器时间常数,再结合实际路试效果综合定。
第三,Pure Pursuit对路径点的密度不敏感,这点是它的一个优点。因为预瞄点搜索只要最近点在前方,路径点的疏密只影响预瞄点位置精度,不直接进入转角公式。但是到路径终点时,由于找不到足够远距离的预瞄点,控制器会出现“目标点索引回退”的情况,导致转角突变。解决办法是终点前做一个减速或者停车目标点的切换,比如把最终目标点锁定在路径末端不动,并让速度逐渐趋零。
4. Stanley算法拆解
4.1 和Pure Pursuit本质上的区别
Stanley算法由斯坦福大学的Hoffmann等人提出,最早是用在斯坦福自动驾驶赛车Stanley上的,这也是它名字的由来。它在概念上和Pure Pursuit最大的不同是:它不再通过一个前视目标点来生成圆弧轨迹,而是直接把前轴中心位置投影到参考路径上,然后用两个误差——航向误差和横向位置误差——构造一个非线性反馈控制律。
把两种算法放到一个画面对比:Pure Pursuit像是个“只管远方目标”的追踪者,Stanley更像是个“盯着当前偏差”的修正者。Pure Pursuit在弯道中因为持续跟踪前方点,转弯半径天然比较平滑;Stanley则因为直接对横向误差做比例放大,横向偏差收敛要快得多,但控制量也更容易振荡。所以业界有个经验:路径较为平滑、速度较高时用Pure Pursuit更舒服;路径弯道密集、需要精确跟踪时,Stanley往往表现更硬核。
4.2 控制律推导
Stanley算法的控制律由两项叠加而成:
δ = ψ_e + arctan(k * e / v)
其中:
- ψ_e 是航向误差,定义为参考路径最近点的切向航向与车辆当前航向之差。
- e 是横向位置误差,定义为前轴中心到参考路径最近点的垂直距离,通常规定车辆在参考路径左侧时为正。
- k 是可调增益,反应横向误差收敛的速率。
- v 是纵向车速。
先说第一项ψ_e。它的作用是让车辆朝向与路径方向一致,抵消航向偏差。如果车辆方向正确、只是平行偏移了一段距离,那么ψ_e为零,此时转向完全由第二项控制。这比Pure Pursuit要更“诚实”:Pure Pursuit没有直接使用路径切向航向的信息,只用预瞄点的方向角,所以它在平行偏移状态下仍然会先转向预瞄点,产生一段弧线逼近;而Stanley则会直接线性地朝路径靠拢,轨迹更接近“拉直线修正”而不是“弧线追踪”。
第二项里有个关键行为特征:横向误差e越大,转角越接近±90度的极限值,意味着车辆会以近乎垂直的方向切回路径;随着误差变小,转角收敛到零。这个非线性项本质上是一个带饱和特性的比例控制器,饱和点由k的取值决定。
注意到分母上有个车速v,这一点非常有意思。车速越低,同一横向误差产生的转角越大。这保证了低速时修正动作足够迅猛,而高速时不会因为过大的横向误差导致转角饱和、车辆姿态失控。但这也带来一个不好的副作用:车速趋近零时,第二项趋近±π/2,转角指令直接饱和到极限,这在泊车和拥堵跟车场景中必须特别小心。通常工程上需要把低速段做保护,比如v小于0.5m/s时直接用固定角度或者切换到其他算法。
4.3 代码实现与仿真
直接给Stanley算法的Python实现。这里我选择前轴中心作为跟踪基准点,所以状态输入除了后轴中心的坐标,还要根据当前航向角和轴距推算前轴中心位置。
import math class StanleyController: def __init__(self, wheelbase=2.5, k=1.0, max_steer=0.6): self.L = wheelbase self.k = k self.max_steer = max_steer def compute_steer_cmd(self, current_pose, current_yaw, velocity, ref_point, ref_yaw): # current_pose: 后轴中心坐标 (x, y) # ref_point: 参考路径最近点坐标 (x, y) # ref_yaw: 参考路径最近点的切向航向角 x, y = current_pose fx = x + self.L * math.cos(current_yaw) fy = y + self.L * math.sin(current_yaw) # 横向误差:前轴中心到参考路径最近点的距离,带符号 # 通过向量叉积判断正负 dx = fx - ref_point[0] dy = fy - ref_point[1] # 参考路径切向方向 tx = math.cos(ref_yaw) ty = math.sin(ref_yaw) # 叉积: tx*dy - ty*dx,大于0表示车辆在路径左侧 cross = tx * dy - ty * dx e = cross # 横向误差,单位米 # 航向误差 psi_e = ref_yaw - current_yaw psi_e = math.atan2(math.sin(psi_e), math.cos(psi_e)) # 非线性反馈项 v = max(velocity, 0.1) # 防止除零 delta2 = math.atan2(self.k * e, v) steer = psi_e + delta2 steer = max(-self.max_steer, min(self.max_steer, steer)) return steer这里最容易被忽略的一个点是横向误差e的正负定义。很多初学者直接用最近点到前轴中心的欧氏距离来当作e,结果发现车在路径两侧时控制效果完全相同,完全无法收敛,实际上就是因为符号丢失了。一定要通过向量叉积来判断车辆在参考路径的哪一侧,这个符号信息决定了转角的修正方向。
4.4 最近点搜索与航向插值
Stanley算法必须找到“参考路径上离前轴中心最近的点”,这就需要做最近点搜索。跟Pure Pursuit的预瞄点搜索不同,最近点搜索应该是全局搜索或者局部维护一个固定窗口。因为车辆在急弯处可能暂时偏离参考路径很远,如果搜索窗口过小,可能找不到真正的最近点,导致误差计算不连续。
实际工程里一个高效的方案是:维护上一帧的最近点索引,然后从附近向两侧各扩张若干点做搜索,每次扩张范围取决于路径点密度和车速。路径点密度为0.2米时,我通常向两侧各搜索50到100个点,基本覆盖20米范围,足够普通工况下的最近点搜索。
最近点找出来之后,参考路径在该点的切向航向可以用相邻路径点做差分,或者直接用当前点与下一个点连线方向。相邻点间距越小,切向航向越准确。前面提到的重采样操作在Stanley算法里同样很重要,因为如果路径点间距不均匀,最近点对应的切向航向会出现类似“多边形折线”的跳变,导致ψ_e项抖动。
5. 两种算法的横向控制性能对比与选型依据
5.1 做一个简单的车辆运动学仿真对比
为了直观看清两个算法的差异,我写了一个小仿真脚本,模拟车辆以自行车模型行驶,并跟踪一条包括直线、S弯和U形弯的参考路径。初始位置故意给了一个0.5米的横向偏移,观察横向误差的收敛速度。
仿真里我用的参数:轴距2.5米,车速恒定5m/s,Pure Pursuit的k=1.6、ld0=2.0,Stanley的k=1.0。每条曲线都取横向误差绝对值。
用这段代码可以很快地扫出一批曲线:
def simulate_path_tracking(path_xy, velocity, controller, dt=0.05, steps=5000, start_offset=0.5): # 初始状态:从起点前方偏移start_offset米开始 x = path_xy[0, 0] + start_offset y = path_xy[0, 1] yaw = math.atan2(path_xy[1,1]-path_xy[0,1], path_xy[1,0]-path_xy[0,0]) # ... 省略绘图细节,只提核心循环 errors = [] for _ in range(steps): # 找最近点/预瞄点 # 计算控制量 # 用自行车模型运动学递推状态 pass return errors光看仿真曲线的形态,能总结出几条直观规律:
- Stanley的横向误差衰减速度快了大约2~3倍,在初始大偏移时尤其明显,车辆会很快切入参考路径。
- Pure Pursuit在线路段上的跟踪更平滑,横向误差曲线几乎没有高频振荡,但初始误差的大幅收敛需要更长时间(因为它是弧线逼近)。
- 在U形弯道中,Pure Pursuit会明显切弯,最大横向误差可以达到0.3米左右,而Stanley则可以把误差控制在0.12米以内。
- 在S弯连续变曲率路径上,Pure Pursuit的预瞄特性减少了转向频率,转向指令曲线更平滑;Stanley则需要更频繁的转向修正,执行器负担更高。
这些差异在实际车辆上会进一步放大。因为真实车辆存在转向执行器延迟和轮胎侧偏特性,Stanley那种“高增益硬反馈”更容易激发极限振荡,所以做高速或者大型车辆时,我一般更偏向用Pure Pursuit做基础跟踪,再用Stanley做误差修正的双环结构。
5.2 算法适用场景速查表
| 场景 | 推荐算法 | 理由 |
|---|---|---|
| 高速公路、城市快速路 | Pure Pursuit | 路径曲率小、连续,预瞄特性让转向平滑,高速侧向加速度小,执行器友好 |
| 园区低速、封闭场地 | Stanley | 低速下横向修正能力更强,对路径边界的逼近精度高,适合狭窄空间 |
| 弯道密集的山路 | 混合或Stanley增强 | 纯跟踪切弯严重,Stanley低速修正更直接,但要注意增益别太大 |
| 高速换道、避障 | Pure Pursuit增大前视 | 换道轨迹曲率不大,平滑度为第一优先级,预瞄距离大更稳 |
| 泊车、倒车入库 | 自定义算法或MPC | 两个算法都不适合倒车,Stanley低速段还会饱和,泊车建议用几何规划+MPC |
表格里比较关键的一条:倒车工况下两个算法都不可直接使用。Pure Pursuit要求预瞄点在行驶方向前方,倒车时这个前提不成立;Stanley虽然有学者扩展了倒车版本,但经典形式同样不适合倒车。有倒车需求的场景,还是老实走路径规划+MPC(模型预测控制)或者A* + 后轮反馈控制那套方案。
5.3 实车调参时的一些差异
实车和仿真最大的差异有两个:一是转向执行延迟,二是车辆的转向传动比线性度。
Pure Pursuit对转向延迟相对耐受,因为预瞄点的存在,转角指令本身带有一定“提前量”。但Stanley对延迟很敏感,如果方向盘的执行延迟超过100ms,弯道里的横向误差容易变成等幅振荡。遇到这种情况,我在工程上通常采用两个处理办法:要么增加Stanley的前向补偿(在参考路径上取一个前方点做近似,而非严格最近点);要么降低增益k,让修正动作更温和,承受一部分稳态误差。
转向传动比线性度则影响两个算法的转角指令到实际前轮转角之间的映射精度。如果转向系统在中间位置线性很好、在大角度附近开始出现饱和或非线性,那么Stanley那种经常触发大转角的控制律会出现“指令跳变”;Pure Pursuit因为控制律偏平滑,对大角度工况的容忍度反而更好。因此对于大型工程机械那种大转角、多节铰接的转向系统,很多人会优先考虑Pure Pursuit做底层基础控制,再用额外反馈做误差修正。
6. 工程机械无人驾驶场景下的特殊问题
6.1 工程机械和乘用车在横向控制上的区别
最近“工程机械无人驾驶”这个概念越来越热,很多读者可能以为学会乘用车横向控制就能平移到挖掘机、装载机、矿卡上去,实际上差异挺大的。工程机械有几个显著特点:
- 轴距长,整车尺寸大,部分矿卡的轴距超过7米,前视距离即使按线性公式取值也很大,Pure Pursuit的切弯现象会被几何放大。
- 车速低,装载机、挖掘机的作业速度常常在5km/h以下,Stanley算法里的e/v项会膨胀到饱和状态,必须做速度下限保护。
- 转向方式特殊,压路机、装载机很多是铰接式转向,也就是前车架和后车架之间通过铰接油缸推拉形成折角,而不是传统前轮转向。这导致自行车模型的假设需要修改:等效“轴距”为铰接点到前/后车桥的距离,转角指令变成了铰接角指令。
6.2 铰接式工程机械怎么改造这两个算法
先理清铰接式运动学的关键点。以装载机为例,它的前车架和后车架通过一个竖直铰接销连接,转向时前后车架绕铰接点相对转动。等效转向角就是前后车架中心线之间的夹角,称为铰接角。这个铰接角跟普通前轮转角的关系可以类比:前轮转角是前轮与车架中心线的夹角,铰接角是前车架与后车架中心线的夹角。因此,只要把自行车模型里的前轮转角δ替换成铰接角,再把“轴距”改写成铰接点到前桥和后桥的等效距离,两个算法的核心公式依然可用。
但有一个非常重要的差异:Pure Pursuit的几何推导是基于后轴中心的,而铰接式车辆的“后轴中心”仍然可以定义在后车架后轴上,这一点没变;可是前轴中心在铰接转向时已经不再沿期望圆弧运动,因为前车架会相对后车架偏转。所以在铰接式车辆上直接套用经典Stanley算法效果会差一些,因为前轴中心轨迹受到铰接角变化的影响,e的符号判断容易出现错误。
工程上的折中方案是:把Stanley的基准点从“前轴中心”改到“铰接点”,然后用铰接角作为控制量。这样车辆等效模型变成:后车架跟随铰接点,铰接点跟踪参考路径,前车架随动。实测下来,这种“铰接点跟踪”的Stanley变体在低速装载机自动铲装作业中能保持5~10厘米的跟踪精度,配合激光雷达停障和位姿调整环节,已经能完成许多重复性作业。
6.3 低速大负载场景下必须做的工程修整
工程机械作业过程中还有个特殊性:负载变化巨大且伴随重心的明显移动。装载机铲斗满载和空载时,整车惯性参数差异非常大,运动学类算法对此天然不敏感,这也是为什么在工程机械低速场景里,运动学控制算法仍然能撑起一片天。但如果要让车在复杂地面保持稳定,还必须叠加以下工程修整:
首先是横向误差的滤波。工程机械作业时车架振动、颠簸远比乘用车严重,横向位置误差信号如果直接进控制器,高频噪声会被放大成方向盘或铰接油缸的抖动。一般需要对误差信号做一阶低通滤波或中值滤波,截止频率设置在1到3Hz是比较合理的起点。
其次是速率限制。执行器顶层通常有一个转角速率限制,比如铰接式转向系统的油缸流量决定了铰接角的角速度上限。控制算法计算出的转角指令变化如果超过执行器最大速率,就必须做限速处理,否则会出现指令超前于实际执行的问题,闭环上表现为持续的超调性振荡。这个限速可以用一阶惯性环节或者简单增量限制实现。
最后还需要考虑地形不平导致的参考路径坐标系问题。路径规划通常是在大地坐标系里完成的,但工程机械在坑洼田地中作业时,车身俯仰和侧倾角度很大,定位系统给出的数据如果不能正确转换到地面平面坐标系上,横向误差e里会混入坡度引起的伪误差。很多团队在这块选择用IMU+RTK的融合定位,把车体姿态对位置做补偿,尽可能消除地形影响。
7. 实操阶段常见的坑和排查技巧
7.1 车辆跑蛇形,到底是谁的问题
横向控制项目里最常见的现象就是“蛇形行驶”,车在直道上左右晃,看着像喝了酒。遇到这种情况,先别急着改算法,按顺序排查:
先看误差来源。打开日志,看看横向误差曲线是不是一个有规律的振荡。如果是正弦状且频率比较高,大概率是前视距离或增益设置过小,车辆反应过度活跃;如果振荡频率低、周期长,大概率是预瞄距离或增益过大,导致车辆处于欠阻尼状态。我自己的经验是,先用仿真扫一遍不同参数下的误差曲线,把目光集中在振荡频率上,基本能快速定位问题的方向。
接着看执行器实际转角。很多团队只在仿真里验证算法,不检查实车上的转向执行跟随性能。方向盘指令和实际转角之间往往有100~200ms延迟和一个稳态误差。如果执行器的跟随误差超过5%,控制算法调得再精细也白搭。一定要先在空载工况下做一次转向执行器的阶跃响应测试,把延迟时间、稳态误差、最大速率全部测出来,再决定控制参数。
再看定位数据质量。横向控制对定位质量极其敏感。RTK信号在没有遮挡的地方定位精度能到2厘米,一旦到了高架桥下或者树荫里,可能存在多路径效应、跳变点,甚至定位丢失。定位跳变会在误差信号里引入尖峰,直接被控制律放大成方向盘猛打一下。处理办法包括定位质量检查、跳变剔除、多传感器融合定位等。
7.2 弯道出口外飘怎么解决
弯道出口外飘是Pure Pursuit的高频问题。原因是:弯道内车辆处于持续转向状态,但当路径点开始向直线过渡时,预瞄点还在弯道内部,转角指令仍然带着一个往弯内的角度;等到预瞄点进入直线段,车辆已经积累了过大的侧向速度,转弯半径一下拉不回来,于是向外飘。表现为出弯后短时间横摆角速度过大、横向误差骤增。
解决方向有几个:
- 适当缩小前视距离,让预瞄点更快进入直线段,但这里要小心诱发蛇形。
- 在弯道出口附近人为给路径增加曲率平滑过渡,让参考路径本身不存在突变。
- 增加一个航向误差阻尼项,也就是在Pure Pursuit控制律后面并联一个比例于航向误差的修正角度。这也是我在实际中最常用的简单增强,效果立竿见影。
最后一种做法的代码其实就是:
steer = pure_pursuit_steer + k_yaw * yaw_error这个k_yaw取值不能太大,一般0.2~0.4之间,太大了会把Pure Pursuit的预瞄平滑效果破坏掉。这个改造虽然不再是严格的Pure Pursuit,但工程上很多人都在用,这里讲清楚缘由,希望能帮大家少走弯路。
7.3 Stanley算法在起点处疯狂打方向怎么处理
Stanley算法在起点处如果车辆初始横向误差很大,转角指令很容易饱和到极限值,车辆会对着参考路径“猛扎过去”,前期轨迹非常激进,而且还可能因为大转角导致的C.G.(重心)横向移动带来额外的方向扰动。这在自动驾驶最需要平顺起步的阶段非常不友好。
一个经验做法是:在误差较大时限制最大转角增量,而不是直接限制最大转角值。也就是每帧转角指令的变化量不超过某个阈值。这样即使Stanley算法下达了一个极限转角命令,执行器也只能以受限速率逼近目标,车辆在起步时就会以一个相对平滑的弧线收敛到路径上,而不是画一个尖锐的急转弯。
另一个做法是给Stanley的增益k加一个随车速变化的调度表。低速起步时k取小一些,让修正更柔和;车跑起来以后k再逐步加大到正常值。这个调度在标定表里维护起来也很简单,而且对整车操稳性的改善非常明显。
8. 两种算法之外的一些扩展思考
写到这里,其实想多说一句:Pure Pursuit和Stanley并不总是什么最优控制策略,很多项目做到后期会转向MPC或者LQR这类基于模型的控制器。但作为运动学阶段的基石,这两个算法依然在大量产品里扮演基础角色,而且理解它们的几何直觉对后续学习更复杂的控制策略非常有帮助。
在纯几何算法之上,经常能看到三种增强方向:一是把它们与动力学结合,利用PID或LQR对误差做二次调节,形成串级控制结构;二是把它们与SLAM/路径规划打通,利用前视偏差信息实时调整规划路径的参数,形成规划控制联合优化;三是把纯跟踪中的预瞄距离做成动态可变——比如根据参考路径的曲率预测自适应调整前视距离。第三个方向上我试验过一种思路:在转弯前提前增大前视距离,保证入弯平滑;在弯心处减小前视距离,提高跟踪精度;出弯时再逐步恢复。实测效果比固定参数有显著提升,代价只是多了一个曲率前馈模块,代码量不大。
工程项目的价值往往就藏在这类“小改动”里。两个算法本身不复杂,复杂的是结合具体车型、场景、执行器特性后的调校和组合。很多人以为控制算法的终点是最小化某个误差指标,但实际做下来,你需要的往往是让车辆在安全性、舒适性、准确性之间找到可接受的平衡。这个平衡没有标准答案,它高度依赖场景。这也是为什么我在文中反复强调要从误差定义、基准点选择、执行器特性这些看似底层的地方抓起,而不是拿到一个公式就盲目上参数试跑。底层的东西搞清楚了,参数怎么调、算法怎么改、出了问题朝哪个方向排查,思路自然就顺了。