上一讲我们把ACC自适应巡航的系统基础讲完了:毫米波雷达怎么测距测速、前车目标怎么映射到自车车道、恒速与跟车巡航的模式切换、还有最基础的PID速度控制怎么跑通一个直道跟车Demo。那次算入门,这一讲我想做点更实际的东西——目标到底该跟谁、切入切出时怎么给油收油、弯道里为什么容易跟丢、纵向控制用PID还是MPC、雷达和摄像头数据对不上时听谁的,以及低速拥堵和功能降级这些真正决定ACC好不好用的细节。如果你刚接手ADAS算法模块,或者正在做ACC相关的毕业设计,这篇文章应该能帮你少踩几个坑。
1. 总体思路:从“能动”到“能看懂路况”
1.1 “(二)”到底补了什么
第一讲的核心是“把速度跟住”。给一个前车目标,算出一个期望加速度,让自车在直道上老老实实地跟着走。那一套在封闭场地和高速公路直道上确实够用了,但一上真实道路就会露馅:前面有好几辆车,雷达一下丢出十几个目标,ACC要自己判断哪一个才是“该跟的车”;前车忽然变道出去,你还没来得及反应,系统就猛加速往前冲;路过弯道时,明明隔壁车道的车也在前方,系统却把它误当成主目标一脚刹车。
这一讲把视角从“单车控制”抬到“整车场景策略”。简单说,就是ACC不再只关心怎么踩油门刹车,而是先回答几个问题:当前车道里谁是我的目标、这个目标可不可靠、我现在应该处于定速还是跟车、如果传感器坏了该怎么办。这里的核心不是某一层算法有多花哨,而是每一层的职责边界要清楚,层与层之间的接口要稳定。只有这样,换传感器、换车型、换控制器的时候,算法框架才不用推倒重来。
1.2 模块化链路让问题可拆解
我习惯把整个ACC系统拆成下面这条链路:
感知融合层、目标管理层、场景决策层、纵向控制层、执行器接口层。
感知融合层输出的是目标列表,每个目标带距离、相对速度、横向位置、存活时间、类型(车辆/行人/自行车)等信息;目标管理层从这些目标里挑出唯一一个“主目标”,也叫TOI(Target of Interest);场景决策层根据主目标存在与否、车速、距离、驾驶员操作,决定当前处于定速巡航模式、跟车巡航模式还是停车保持模式;纵向控制层负责把模式转换成具体的加速度请求,发给发动机和制动系统。
这个结构最直接的好处是每个模块都能单独拿出来测试。目标选择有没有问题,灌一段录制好的雷达和摄像头数据就能验证;控制逻辑有没有问题,用一台仿真车接上同样的目标曲线也能验证。彼此可以并行迭代,不用每次都在实车上做全链路验证。
别把接口设计得太复杂。我见过不少项目,状态机里塞了几十个标志位,模块之间互相读私有数据,最后出了bug查起来非常痛苦。好的接口设计就是每个模块输出一个明确的结构体,各层之间靠这个结构体通信,内部怎么实现是自由。你可以替换一个更强的目标跟踪算法,只要输出格式不变,下游完全不受影响。这就像餐厅的流水线:接单、备菜、炒菜、传菜各司其职,换掉某个环节,其他环节不用跟着改。
2. 目标级联与场景划分:ACC的每一步都在回答“跟谁、怎么跟”
2.1 主目标选择的工程化流程
主目标选的准不准,直接决定ACC的体验。选错了,轻则莫名其妙加速减速,重则把护栏当车踩一脚刹。
一个工程上可落地的筛选流程大概是这样:
候选目标先过滤:把本车后方、横向距离过大的目标剔除。雷达原始目标里可能有高速路旁的护栏、路牌、隔离带反射点,这些点通常横向距离大、横向速度小、类型标签为静态,首先就要过滤掉。
然后算“同车道概率”。这一步不能只看目标当前在哪里,还要往前看一步。因为车辆是在动的,本车前方是一条弯道时,直线上看起来在邻道的车,进入弯道后可能正好就在本车轨迹上。所以工程上会先做本车道中心线外推,方法是根据本车横摆角速度、车速和车道线曲率,把未来一段距离内的车道中心轨迹算出来,然后计算目标的横向坐标到这个轨迹的偏移量。
简单示意一下:
def select_target(candidates, ego_v, lane_curvature, dt, n_points): future_path = generate_ego_trajectory(ego_v, lane_curvature, dt, n_points) valid = [] for obj in candidates: lateral_off = estimate_lateral_offset_from_path(obj, future_path) if abs(lateral_off) < LANE_WIDTH * 0.5: valid.append(obj) if not valid: return None return min(valid, key=lambda o: o.longitudinal_distance)这里有两个参数需要标定:一个是车道宽度假设,一般取3.5米左右;一个是横向偏移阈值,通常取半个车道宽。别小看这个阈值,设大了会把邻道车算进来,设小了在弯道里又会把真实主目标漏掉。实车上一般会根据车道线质量做动态调整,车道线置信度高时用0.4个车道宽,置信度低时放宽到0.6。
2.2 切入切出:不能突然冲刺也不能犹犹豫豫
真实道路上的主目标不是一直不变的。邻道车辆突然并入本车道,或者主目标变道离开,这是ACC调参里最容易出现投诉的两个场景。
切入场景的核心是“早识别、缓介入”。判断切入不能只看某一帧的横向位置,要看横向位置的变化趋势。比如一个目标在当前帧横向偏移是1.2米,看起来还在邻道,但它的横向速度是1.5米/秒且方向指向本车道中心,那在0.5秒内它就会进入本车道。如果算法等到它完全进入车道才反应,留给制动的时间就不多了。
正确做法是预测切入,提前开始温和减速。减速度不需要一次给满,可以按一个斜坡增加到目标减速度。比如目标本身要求的减速度是2.5米/秒²,可以先在200毫秒内从0.5线性爬到2.0,再保持到距离误差收敛。这样驾驶员和乘客的体感是“系统预料到了”,而不是“系统被吓到了”。
切出场景则要反着来:主目标切出后,自车不应该立刻踩油门追向前方空位。原因很简单,驾驶员会本能地害怕,谁知道前方会不会突然又出现一个目标。工程上普遍的做法是设置一个延迟窗口,比如1.5到2秒,窗口内保持当前速度,窗口结束后再以限制好的加速度斜率恢复加速。
2.3 弯道里跟丢了?多半是坐标投影的问题
弯道可以说是ACC最容易露怯的场景。直道上一切正常,一个弯道过来就开始误判。
原因在于坐标投影。ACC传感器输出的目标经纬度坐标是以雷达安装位置为原点的笛卡尔坐标,但控制逻辑需要的是“相对本车行驶轨迹的纵向和横向距离”。在弯道上,本车有一个横摆角,传感器的坐标系其实一直在转,如果不做轨迹投影补偿,一个小弯道都会让某个邻道车辆看起来“跑”到了本车前方。
工程上一般有两种解法。一种是基于曲率的前方轨迹投影,根据自车速和横摆角速度积分出前方路径,再把目标点投影到这条路径上求弧长和横向偏差。另一种是基于车道线地图,把目标点匹配到车道中心线上。前者对车道线质量不敏感,适合纯雷达方案;后者精度更高,依赖摄像头车道线输出,两者可以融合使用。
弯道里还有一个容易被忽略的问题:目标被遮挡。一辆车在弯道里被路边树木或大型车辆遮挡几秒后重新出现在视野里,如果目标跟踪模块没有保留它的历史状态,它会被当成一个新目标,主目标ID瞬间变化,自车会以为前车忽然消失又出现,产生一次明显的顿挫感。处理办法是给目标一个“保留期”,比如500毫秒内即使没有关联到测量值,也用模型预测位置继续输出。
3. 纵向控制进阶:从串级PID到模型预测控制
3.1 串级PID先讲明白
第一讲提到PID,实际工程里单环PID根本不够用。ACC的纵向控制普遍是串级结构:外层是距离环,输入是期望跟车距离与实际距离的误差,输出是期望速度;内层是速度环,输入是期望速度和实际速度的误差,输出是加速度请求。
为什么分成两层?因为纯位置控制直接输出加速度的话,内环对速度波动的响应太慢,前车稍微加个速,自车就容易追过或拉开过大。串级结构的好处是内环先把速度稳定住,外环只负责距离修正,各干各的事,调参也容易定位问题。
期望跟车距离一般用时间间隙策略计算:
d_des = v_ego * t_gap + d0
v_ego是自车速度,t_gap是时间间隙,通常取1.5秒,d0是停车时的最小安全间距,一般取2到5米。为什么用时间间隙而不是固定距离?道理很简单,车速120公里/小时时,固定距离20米是致命的;同样20米,在60公里/小时下又太保守,会被后车频繁加塞。
模式切换也要设计滞回区间。比如定速巡航模式和跟车模式之间不能以0.1米为阈值来回切换,否则驾驶员会感觉到油门和刹车在抖动。工程上一般做一个两档阈值,比如距离小于35米且相对速度为负时进入跟车模式,距离大于45米时才切回定速模式。
3.2 为什么需要MPC
串级PID可以在直线跟车场景下做得很好,但实际驾驶里它的短板很明显:反应永远慢半拍。
因为PID是“看到误差才反应”。前车急刹车,距离误差已经变大,系统才开始降速;前车急加速,相对距离已经拉大了,系统才给油门。而且执行器有延迟,发动机扭矩响应需要几百毫秒,ESP制动系统建立压力也要时间。PID在延迟面前很容易产生振荡。
MPC,模型预测控制,思路从根本上不一样:它不是等误差出现再去纠正,而是用一个模型预测未来一段时间内自车和前车的状态,按照一个包含安全距离、速度、加速度、舒适性的目标函数,找出一段最优的控制序列,然后只执行第一步,下个周期重新算。
我常用一个类比解释MPC:PID是盯着当前误差踩刹车的老司机,MPC是看前车尾灯提前松油门的熟练司机。后者不慌,因为它在脑子里预演了未来几秒。
ACC这个场景用MPC特别合适,因为它本身就是一个带约束的最优控制问题:加速度有限制,不是你想刹到5米/秒²就立刻能办到;舒适性有限制,人受不了太快的加速度变化率;安全性有限制,相对距离不能小于某个阈值。
3.3 一个可落地的代价函数与简化求解
我把一个工程上能跑的ACC-MPC代价函数拆给你看。
预测模型采用一阶积分模型,采样周期100毫秒,预测时域取50步,也就是往后看5秒。状态量取距离误差e_d、相对速度e_v、自车速度v,控制量取期望加速度a,前车的未来加速度a_lead当作已知扰动输入。
代价函数可以写成:
J = Σ( w_d * e_d² + w_v * e_v² + w_a * a² + w_j * jerk² )
其中jerk是加速度的变化率,也就是(a[k] - a[k-1]) / dt。w_d、w_v、w_a、w_j是权重,决定系统在安全和舒适之间怎么取舍。
约束条件是加速度下限和上限、jerk的下限和上限:
a_min ≤ a ≤ a_max,jerk_min ≤ jerk ≤ jerk_max
一个简化求解示意是这样的:
def solve_mpc(e_d0, e_v0, v0, a_lead_seq, dt, N): best_cost = float('inf') best_a_seq = None # 在可行域内枚举加速度序列(实际工程用QP求解器) for a_seq in enumerate_feasible_sequences(a_min, a_max, jerk_limit, N): e_d, e_v = e_d0, e_v0 v = v0 cost = 0 for k in range(N): a = a_seq[k] e_d = e_d + e_v * dt e_v = e_v + (a_lead_seq[k] - a) * dt v = v + a * dt jerk = (a - a_seq[k-1]) / dt if k > 0 else 0 cost += w_d * e_d**2 + w_v * e_v**2 + w_a * a**2 + w_j * jerk**2 if cost < best_cost: best_cost = cost best_a_seq = a_seq return best_a_seq[0]实际工程不会用枚举,效率太低,一般用二次规划QP求解器或者基于动力学模型推导的简化求解器。但代价函数的形状就是上面这个思路。
调参顺序建议先调好权重再调约束。先把加速度约束放宽,让系统在自由环境下动作,观察距离误差收敛情况,再逐步收紧jerk约束,确认舒适性。直接一上来就把jerk限得很死,系统会表现出“一直在磨磨蹭蹭”的感觉,很憋屈。
3.4 舒适性:加速度斜率比峰值更重要
乘客抱怨ACC“急”“晕”,往往不是加速度峰值太大,而是加速度变化太快。学术上叫jerk,也就是加速度的导数。
经验值方面,正常驾驶时舒适性较好的jerk上限在2到3米/秒³附近。紧急制动时目标减速度可以达到4到5米/秒²,但如果jerk超过5米/秒³,乘客会有明显的点头感。
所以控制策略里一定要给加速度请求做斜率限制,不能直接要求从0加到3米/秒²。实现上可以做一个一阶惯性环节,让加速度请求按时间常数平滑过渡,也可以用MPC的jerk约束直接限制。
4. 传感器融合与状态估计:别让ACC变成“近视眼”
4.1 单传感器各自“偏科”,融合才是正解
只用毫米波雷达做ACC,最核心的痛点是横向精度差。雷达的多普勒测速很准,纵向距离也不错,但横向位置误差经常超过0.5米,而且对静止目标容易产生虚假回波,匝道护栏、路沿反光、隧道墙壁都会报出假目标。
只用摄像头做ACC,痛点反过来。摄像头横向位置识别准确,车道线检测能力强,但单目测距本质上依赖地面假设和先验尺寸,纵向距离误差在小目标上很容易达到10%以上,夜间和逆光时性能还会进一步下降。
融合的目的不是简单取平均,而是充分利用各自优势:雷达负责纵向距离和相对速度,摄像头负责横向位置和目标类型识别,两者经过时间同步和空间对齐后,生成一个同时具备稳定纵向和横向精度的目标。
4.2 目标跟踪与滤波的工程要点
目标从传感器进入系统后,不能直接用原始测量值驱动控制,必须经过跟踪滤波。ACC里常用的状态模型是CA模型,也就是恒加速度模型,状态向量包含距离、速度、加速度三个量。
卡尔曼滤波在这里的作用是:把测量值里的噪声滤掉,并在目标短暂遮挡时用模型预测位置顶住。但工程上有几个坑:
滤波器状态更新周期和目标上报周期不匹配时,协方差矩阵容易“飘”,导致滤波输出抖动。解决方法是严格按照传感器时间戳做预测更新,而不是在每个算法周期里假设数据新鲜。
目标ID切换时,状态会突变。一个目标丢失又重新关联,如果把它当成新目标,卡尔曼滤波器要从初始状态重新收敛,收敛期间输出的距离、速度往往不可信。工程上会给每个目标一个连续存活时间和关联计数,只有连续关联多帧后,滤波器输出才允许进入控制环。
前车加速度估计也是一个关键点。MPC需要预测前车的未来加速度,但这个量没法直接测量,只能通过对前车位置和速度的差分结合滤波来估计。估计误差大的时候,优化出来的轨迹会过于乐观或过于悲观。实际操作中我会对前车加速度估计结果做一个可信度标签,可信度低时把MPC里的前车扰动项置零,避免错误预测引发误刹车。
4.3 时间同步与数据关联
雷达和摄像头的输出周期不一样,时间戳也不同,融合前必须做时间对齐。一个常见做法是把所有传感器数据先缓冲到统一时间基准上,然后按融合周期取每个传感器最近的有效帧,用其时间戳做插值补偿。补偿精度直接影响目标位置,因为50毫秒的时间差在高速公路上就对应1米多的位置差。
数据关联解决的是“哪几个传感器目标其实是同一个物体”的问题。简单场景用最近邻匹配就够了,但目标密集时,最近邻容易串。稍微复杂一点就用全局最近关联,即把雷达目标和视觉目标的匹配看成一个指派问题,最小化总的关联代价。代价矩阵里的距离度量用马氏距离比欧氏距离好,因为它考虑了不同维度上的不确定性差异。
关联时必须设置门限,匹配代价超过门限的目标就是新目标,不能强行关联。我见过一个案例:门限设大了,一个雷达点和一个视觉目标明明是不同物体却被硬拼在一起,融合目标一会儿跳前一会儿跳后,导致ACC全程在抖。
5. 低速拥堵场景与安全降级:该停就停,该退就退
5.1 自动跟停与起步的策略
ACC在高速上表现好,一到城市低速拥堵路况就容易暴露出设计缺陷。这时候车辆经常要在走走停停中反复起步和刹停,对算法和标定都是严峻考验。
自动跟停的核心难点不是“刹住”,而是“怎么刹住”。前车停车时,如果系统按常规跟车策略一直收敛到目标距离才停,程序就会用一个比较大的减速度突然刹停,乘客身体前倾明显。所以工程上会在预计停车前一段距离(比如10米左右)就开始提前降速,用爬行减速的方式慢慢凑近前车。目标停车距离不再是固定值,而是和速度、前车加速度趋势绑定,前车减速得快,自车就早减速。
停车之后的起步策略需要设计确认机制。很多量产ACC在停车超过3到5秒后不会自动起步,必须驾驶员轻点油门或者拨一下按键才恢复。原因不在于算法做不到,而在于安全逻辑:长期停车后前车状况未知,驾驶员可能正打算开门下车,此刻自动起步容易出事故。起步前还得重新做一次目标选择,因为停车期间主目标很可能已经换道了。
低速场景下还有一个参数要特殊处理:时间间隙。低速时如果沿用1.5秒时间间隙,期望距离会小于停车间距,逻辑混乱。所以低速域要切换成固定间距策略,比如车速低于5米/秒时,期望距离直接取一个定值,带一个小的速度修正项。
5.2 传感器失效时的降级链路
ACC做得好不好,失效场景下的表现比正常场景更能说明问题。
毫米波雷达和摄像头都有自己的健康状态监测。雷达会有自检故障码,摄像头会有遮挡、标定漂移、供电异常等诊断信息。算法层除了看故障码,还要监测数据帧的连续性和合理性,比如连续200毫秒没有收到雷达目标数据,那就是异常。
失效后的降级策略分三层:
第一层是传感器部分失效,比如摄像头被泥水遮挡但雷达正常。此时系统可以降级为“纯雷达ACC”,但必须同时限制最高速度和工作场景,比如车速只能到80公里/小时,且不提供跟停功能。
第二层是核心传感器完全失效,比如雷达故障。这时候ACC无法可靠检测前方目标,系统应立即发出接管请求,同时保持当前加速度不超过一个安全值。这个阶段的目标不是继续ACC,而是安全退出。
第三层是系统无法确保安全但又不能立刻停车时,比如在高速中间,刹车减速到安全速率,打开危险报警灯,保持车辆缓慢行驶并持续提示驾驶员接管。这种设计叫fail-safe,核心原则是:ACC可以在任何时刻退出,但不能在退出时制造危险。
5.3 数据回放与问题复现
调ACC最怕的是问题无法复现。实车上偶发一次误刹车,可能好几周都碰不到第二次,这时候数据回放就是救命稻草。
我建议所有ACC相关的输入输出都做完整录包,包括雷达原始目标、摄像头输出、本车速、横摆角速度、方向盘转角、油门刹车指令、ACC状态机状态。字段要带上毫秒级时间戳,同时记录一个GPS时间基准,方便后续多路数据对齐。
回放时用离线工具把同一份数据灌进算法看输出,可以在几秒钟内复现实车上的行为。这样调整参数后可以立刻看到同一场景下的算法行为变化,不用再冒着风险上路试。
6. 实测中遇到的几个典型问题与调参思路
6.1 目标抖动导致加减速频繁
现象:高速巡航时,前车明明在匀速行驶,自车却隔几秒就轻微加减速。
排查方向首先要看主目标ID是否稳定。如果雷达目标在几帧内反复切换ID,系统就会认为前车速度在跳变。解决办法通常是给目标关联计数加滞回,连续3帧关联同一个目标后才允许它成为主目标,丢失目标后保留一段时间。
其次要看滤波参数是否太激进。卡尔曼滤波的测量噪声矩阵设得太小,滤波输出会跟随测量值的抖动;设得太大,又会出现“跟不住”的滞后感。一般先根据传感器厂商给的噪声标定初值,再在回放数据上做离线仿真调参。
6.2 误触发刹车(对护栏和路沿的响应)
现象:左侧是护栏的弯道处,系统突然减速,屏幕上主目标显示在左侧护栏上。
这是毫米波雷达的经典问题,弯道护栏会产生很强的镜面反射,雷达会输出一个和真实目标距离很接近的虚假点。如果这个点恰好落在本车道外推轨迹附近,就会被选成主目标。
处理思路有三步。第一步,给目标打类型标签,静态目标候选需要更严格的同车道判定条件。第二步,融合摄像头车道线信息,用车道线曲率修正本车道外推轨迹,让护栏点的横向偏移计算更准确。第三步,引入目标横向运动趋势一致性检验,一个横向位置和横向速度都稳定在同一点的目标才可能是真实交通参与者,剧烈跳变的目标可信度要打折扣。
6.3 舒适性和安全性怎么平衡
这是所有ACC项目中讨论最多的话题。安全性要求跟车距离近一点,这样反应时间更长;舒适性要求加速度和jerk都小一点,这样动作更柔和。这两个目标天然冲突。
我的经验是把安全性拆成硬约束和软约束两级。硬约束是绝对不能突破的边界,比如紧急制动时最大减速度不超过5米/秒²,跟车距离不小于5米。软约束是MPC代价函数里的权重项,正常工况下系统会在安全边界内尽量舒适。这样在紧急情况下,系统可以突破舒适性软约束来保证安全,而平时则保持柔和。
调参时间线大概是:先确定硬约束边界,再调MPC的权重,让正常跟车时动作温和,最后拉一个紧急场景库,验证硬约束场景下系统能否触发保护性制动。这个流程比一上来就追求极端舒适性要稳妥得多。
还有一个实际操作中的细节值得单独说:每次修改参数后,不要只跑一个场景,要至少在仿真环境里过一遍标准场景集,包括前车突然切入、前车急刹、相邻车道车辆并行、隧道入口、雨天传感器降级等。我见过太多人把某个场景调顺了,另一个场景又出现新问题,原因就是缺乏标准回归测试。建一个场景用例库,每次改动后全量回归,哪怕多花半天时间,也比实车出险情划算。
我个人调ACC这几年最大的感受是,控制算法本身的学习成本并不高,难的是对“目标管理”的理解。PID也好,MPC也好,它们都是在解决“怎么动”的问题;目标选择、切入切出、弯道投影、传感器融合解决的是“动谁”的问题。后者没做好,前者再精细也白搭。很多刚开始做ACC的同学会把大量精力放在调MPC权重上,上车之后被安全员吐槽,问题往往出在目标选择上而不是控制上。
最后再补一个小技巧:录数据时别只录正常场景,遇到误动作、急刹、目标丢失的时候,手动打一个标记,回办公室后优先分析这些标记段。这些“坏数据”比一百公里正常路跑的数据都有价值。
ACC这个功能看似简单,实际上牵涉传感器、融合、决策、控制、安全降级和人机共驾多个层面,每一步都有大量工程细节。这篇文章抛出的是我在实践中验证过的思路和参数范围,真到具体车型上还需要根据传感器配置、制动系统响应特性和整车标定反复打磨。希望这些经验能帮你绕开我走过的弯路。