首页 / 资讯中心 / 文章详情

视觉后处理与辅助驾驶:从检测框到稳定目标的关键技术

视觉后处理与辅助驾驶:从检测框到稳定目标的关键技术 ★ FEATURED ARTICLE
搞智能驾驶视觉感知的人对这样的场景应该不陌生模型明明框到了前方的车但框在连续几帧里左右跳类别一会儿是轿车一会儿是货车框的中心位置漂移得离谱。你要是敢把这个结果直接丢给AEB或者ACC去用车在高速上能给你来一脚莫名其妙的刹车。模型输出的是“框”是单帧的、带噪声的、彼此独立的“检测假设”而下游功能要的是“目标”——是连续、稳定、带着运动状态和置信度评价的对象。这中间缺的那一大块就是视觉后处理。这篇是视觉障碍物后处理系列的第五篇聊的主题是视觉后处理与辅助驾驶功能。前面几篇分别讲了检测输入、前后处理链路、以及障碍物基本信息处理这篇更侧重一个层面后处理好的障碍物目标到底怎么跟FCW、AEB、ACC这些辅助驾驶功能衔接后处理结果的质量会怎么影响功能表现以及实操过程中那些文档里不会告诉你的坑。适合做感知后处理、融合、功能算法或者系统集成的人参考也适合想搞清楚“感知和功能之间到底发生了什么”的入门者。1. 先弄清楚后处理在整条链路里到底要干什么1.1 从“框”到“目标”后处理解决的不是检测而是信任问题很多刚接触这个领域的人觉得后处理不就是把检测结果的置信度过滤一下、把重叠框去掉吗如果只做这两件事那叫“检测结果清理”不叫后处理。真正的视觉后处理任务是把单帧的检测假设变成时间上连续、物理上可信、语义上稳定的目标级输出。换句话说它解决的是“信任”问题——让下游功能愿意相信这个目标存在、位置可信、状态可预测。你看检测模型的输出格式就知道了每帧给出一堆结构化数据包含类别标签、框坐标、置信度分数。这些数据之间没有任何关联前一帧的框和后一帧的框谁是谁、哪里去了模型不关心。而后处理恰恰要解决这个问题帧与帧之间的目标如何关联目标被遮挡了怎么办目标的速度和加速度怎么估算目标类别闪变了怎么平滑这些如果不处理ACC跟车时前方车辆速度稍微变一下感知速度就跳一下跟车就会一冲一冲的坐在车里的人分分钟晕车。所以整体链路是这样的检测模型感知→ 障碍物后处理目标化→ 决策规划功能决策→ 执行器控制。后处理是感知的“收尾者”也是功能的“接口者”。它的质量好不好用户体感最直观的两个指标就是误报率和漏报率。误报率高AEB天天制造惊险瞬间漏报率高FCW该响不响功能就成了摆设。1.2 为什么模型输出不能直接进功能一个例子看清楚我举个特别典型的例子。某次我调一个前向的FCW功能最初直接把YOLO系检测器的输出拿来计算TTC碰撞时间。正常跟车时前方目标相对速度大约20km/h相对距离30米TTC应该稳定在5秒左右。但由于检测框的宽度抖动比较厉害目标在图像上的尺寸一会儿大一会儿小换算出的相对距离就是33米、28米、35米这样来回波动TTC也跟着从4秒跳到7秒FCW的触发逻辑被搞得疲于奔命该报警的时候没报不该报警的时候响个不停。检测框抖动是必然的神经网络对目标边缘的回归有噪声光照变化、目标部分遮挡、车辆颠簸都会放大这种噪声。而后处理的一个重要工作就是对这些有噪声的输入做时间和空间上的滤波让输出的目标位置、尺寸、速度有“惯性感”。这不是简单做一下平均而是需要建立目标状态估计模型用历史信息和当前观测融合还得考虑目标的运动模型是匀速还是匀加速、传感器的观测噪声有多大。这些都是后处理的核心内容。2. 视觉后处理的核心细节从框到轨迹的关键技术2.1 目标关联怎么判断这两帧的框是同一个人后处理的第一步也是最基础的一步是帧间目标关联。上一帧检测到前方第1辆车、第2辆车这一帧又检测到一辆车你得知道这一帧的这辆车是不是上一帧的1号车这个问题解决不好后面全是空中楼阁。常用的关联方法有两大类一类是基于位置重叠的比如IoU交并比一类是基于特征相似度的比如外观特征、尺寸特征。但在纯视觉后处理里最主流、性价比最高的做法是“预测框与检测框的IoU匹配”。你先用上一帧目标的位置和速度预测出这一帧它大概在哪然后用预测框和一帧检测框算IoU如果IoU大于某个阈值就可以认为是同一个目标。实际操作中我用的是匈牙利算法来做全局最优匹配。先构建一个代价矩阵矩阵元素是预测框和检测框的IoU或距离的负值然后通过匈牙利算法找到整体代价最小的匹配方案。核心经验是IoU阈值的选择——我一般设0.30.5之间。阈值太低比如0.2容易出现两个不同目标错配在一起阈值太高比如0.7目标稍微移动快一点就断开连接、重新分配ID。低速城区场景可以设高一些高速场景建议设低一些因为高速下相同时间间隔内目标的位移更大帧间重叠率天然偏小。2.2 轨迹生命周期管理目标不是一出现就是“真”的目标发出来一个ID不是马上就推给下游使用的。因为检测模型偶尔会对背景误检比如路牌、树影、桥上横梁都可能被误检为障碍物。如果误检也被当成稳定目标FCW会对天降横梁疯狂报警。因此我会给每条轨迹定义几个生命周期状态未确认候选、已确认、丢失中、已删除。一个新目标进入系统时先挂一个候选轨迹连续几帧都能匹配到检测框才升级为已确认轨迹并输出给下游功能。反过来已确认目标连续多帧匹配不上比如被前车遮挡轨迹进入丢失状态在丢失状态下目标位置继续用运动模型推测但置信度会随时间衰减。连续丢失时间超过阈值轨迹直接删除ID不再复用。参数怎么定我自己的经验是确认帧数设3~5帧丢失帧数设30~50帧。确认帧数设太短误检容易混进来设太长真实目标刚出现在视野边缘时功能层迟迟拿不到确认结果等都确认完目标已经离很近了留给系统的反应时间变短。丢失帧数的设置要考虑目标遮挡场景。高速上如果前车被旁边大车挡住通常只会遮挡一两秒30~50帧假设25~30fps足够撑过遮挡期目标ID不会丢但遮挡时间再长轨迹预测的置信度已经很低继续跟下去反而可能导致目标位置漂移很远的假轨迹这时候删除轨迹反而干净。2.3 目标运动状态估计卡尔曼滤波和它的工程落地辅助驾驶功能里目标的速度、加速度是核心输入。ACC要知道前车是加速还是减速AEB要知道碰撞时间还有多少FCW要判断危险等级。那么目标速度怎么来最朴素的方法是相邻两帧位置差除以时间差但这样算出来的速度噪声非常大你根本没法用。所以工程上普遍用卡尔曼滤波做运动状态估计。这里分享一个基础的匀速模型CV模型实现思路。目标状态定义为 x [pos_x, pos_y, vel_x, vel_y]观测是检测输出的目标中心位置 [pos_x, pos_y]。每帧做两步操作先通过运动模型预测目标状态预测步再用当前帧检测结果对预测进行修正更新步。核心要调的两个参数是过程噪声协方差Q和观测噪声协方差R。Q大表示更信任观测、响应快但噪声大Q小表示更平滑但滞后明显。我调车时的经验是初始先给Q设一个相对偏小的值让速度曲线平滑再根据实际跟车体感逐步调整。如果发现目标速度响应太慢前车刹车了感知速度还在慢慢降就适当增大Q如果速度曲线毛刺多就增大R。卡尔曼滤波在工程上的一个复杂点是目标初始化和终止条件要做处理。第一帧出现的目标没有历史速度需要用连续两帧的位置差来初始化速度但这两帧检测噪声会让初始速度偏差很大。我的做法是限制初始速度的范围比如相机感知的目标速度不能超过物理极限比如200km/h超过就置为边界值再用后续帧的观测逐步修正。2.4 类别与置信度的时序平滑让车不再“变性”检测模型对目标类别的判断在单帧上会存在抖动。同一个目标这一帧输出是Car下一帧输出是Truck再下一帧又是Car。如果你直接把类别变化传给下游功能层会非常困惑——Truck和Car的动力学模型、刹车能力、危险程度完全不一样。我做类别平滑的方法很简单给每条轨迹维护一个类别投票器。每帧匹配上检测框时把检测类别的得分加进去最后输出得分最高的类别且只有得分超过一定比例才允许类别切换。一个比较保守的策略是新类别连续N帧比如5帧都占优势才切换类别否则保持原类别输出。这能有效过滤掉单帧的闪烁。代价是真实类别变化时比如拖挂车切换会有几个帧的延迟但这对功能层面通常是可接受的。置信度的处理也类似。模型输出的置信度分数在不做处理时抖动很大尤其夜间、逆光、雨天场景。我习惯对置信度做时间维度的加权平均当前帧置信度占60%70%历史置信度占30%40%。这样单个低置信度的突变帧不会导致目标被直接删除也不会让一个高置信度的偶然误检瞬间升级成稳定目标。置信度阈值的选择则需要分场景城区场景我通常把初始阈值设0.4左右郊区高速设0.5因为高速上误检代价更高宁可多漏一些也不能乱报。3. 实操后处理管线实现与辅助驾驶功能对接3.1 一个可复现的视觉后处理管线架构理清核心点之后我直接给一套最常见的视觉后处理管线架构。输入是检测模型输出的检测框列表输出是带有ID、类别、位置、速度、置信度等属性的目标列表。管线内部通常分四个模块预处理与坐标转换、目标关联与匹配、轨迹管理与状态估计、输出与接口适配。预处理模块负责把检测框从像素坐标换到相机坐标系或车身坐标系。这里特别提醒一点像素坐标框不能直接用于速度估算。如果不做坐标转换用像素位移近似物理位移目标距离越远越不准。通常我们是利用相机内参、外参和地面平面假设把检测框底边中心点投影到地面上得到目标的世界坐标x, y。这一步的精度直接决定后续距离和速度的精度标定参数要定期检查和校验。输出接口是给下游功能用的object list结构。我常用的结构体包含以下关键字段target_id // 全局唯一ID class_type // 障碍物类别轿车/卡车/行人/骑行者/未知 bbox // 原始图像检测框调试用 position_x // 车身坐标系下的纵向位置米 position_y // 车身坐标系下的横向位置米 velocity_x // 纵向速度米/秒 velocity_y // 横向速度米/秒 confidence // 目标置信度得分 existence_score // 目标存在概率 track_age // 轨迹已维持帧数 lost_age // 轨迹丢失帧数 valid_flag // 是否可作为可信目标参与功能决策valid_flag这个字段在设计时一定要留。它表示该目标是否满足功能安全的最低要求比如是否经过足够帧数确认、置信度是否超过阈值、时间戳是否新鲜。功能层拿到目标列表后优先只看valid_flag为真的目标。这样即使感知端有异常、目标处于丢失状态只要valid_flag置为false功能层就不会用它做决策从源头避免误触发。3.2 关键模块伪代码目标关联、生命周期、状态更新我写一个简化但可运行的伪代码流程方便大家对照自己的工程实现思路。# 伪代码视觉后处理主循环 for frame_idx in range(total_frames): detections detector.infer(image) # 当前帧检测结果 # 1. 坐标转换检测框到车身坐标系 dets_world world_coord_transform(detections) # 2. 预测用已有轨迹的运动模型预测当前帧状态 predicted_tracks [track.predict(dt) for track in tracks] # 3. 关联预测轨迹与当前帧检测做匹配 cost_matrix build_iou_cost_matrix(predicted_tracks, dets_world) matched_pairs, unmatched_dets, unmatched_tracks hungarian_match(cost_matrix) # 4. 更新匹配上的检测用于修正轨迹状态 for track_idx, det_idx in matched_pairs: tracks[track_idx].update(dets_world[det_idx]) tracks[track_idx].confirm() # 累加确认计数 # 5. 处理未匹配的检测新轨迹候选 for det_idx in unmatched_dets: new_track Tracker.initialize(dets_world[det_idx]) tracks.append(new_track) # 6. 处理未匹配的轨迹进入丢失状态 for track_idx in unmatched_tracks: tracks[track_idx].mark_lost() # 7. 生命周期管理确认/删除 for track in tracks: track.update_state_machine() # 8. 产出过滤无效目标输出object list output_targets [track.to_object() for track in tracks if track.is_confirmed()] publish(message_frame_id, output_targets)这里每一步都有细节讲究。第三步的代价矩阵如果纯用IoU遇到快速接近的大货车和远处的小轿车时容易匹配混乱。我建议代价矩阵不要只用IoU可以加入位置距离和高宽比约束IoU低于0.2的配对直接设为无穷大排除高宽比差异超过一定倍数也排除。这样即使两个目标空间上接近但形状差异大也不会错配。另外一个实操技巧是如果检测帧率稳定每次预测的dt要严格用时间戳计算不要默认等于1/帧率。相机帧率在颠簸路面上可能偶发掉帧统一用时间戳计算dt可以让速度估算更稳。3.3 时间戳对齐最容易翻车的细节时间戳对齐是我见过最容易被忽略、也是最容易翻车的问题。视觉后处理输出的目标列表带上时间戳是不够的关键是你这个时间戳是“算法完成时间”还是“图像曝光时间”。这两个时间差了可能几十到上百毫秒高速场景下100毫秒意味着车辆向前移动了好几米。如果你的目标位置是100毫秒前的而下游功能按“当前时间”做决策那AEB的反应距离凭空缩短好几米。我的做法是感知输出里的每个目标都带两个时间戳一个是图像曝光时间一个是后处理完成时间。下游决策用曝光时间作为目标状态的有效时刻用完成时间判断数据的时效性。如果完成时间和当前规控时刻差超过一个阈值比如150ms这帧感知数据就不能参与实时安全决策功能层要降级处理。另外后处理内部在计算速度时必须用相邻两帧曝光时间戳的差值作为dt而不是用算法执行间隔否则速度里会混入处理延迟的误差。3.4 后处理如何支撑FCW、AEB、ACC这些具体功能后处理做完最终要落到功能上。这里讲三个最典型的对接实例。先看FCW前向碰撞预警。FCW的核心参数是TTC计算方式是相对距离除以相对速度。后处理给的是自车的目标纵向位置和纵向速度经过差值得出相对速度和相对距离再算出TTC。FCW的报警阈值一般分两级预警级TTC在2.6~3.0秒介入级在1.2~1.5秒。但单纯靠TTC触发工程上会误报很大因为目标可能正在切入或者本身在制动过程中。所以功能层通常还要求后处理额外提供目标的生命周期稳定度和横向运动趋势。如果目标刚确认不久、横向速度明显即使TTC很小也不会触发FCW。我在调这个的时候发现后处理输出的目标横向速度估算尤其重要横向速度估算不准功能会把旁边车道正常行驶的车误判为切入车辆。然后是AEB自动紧急制动。AEB对感知置信度的要求比FCW高得多因为它要主动制动。功能层一般需要后处理提供一个“危险目标等级”的评价除了TTC还会结合目标类别行人、骑行者、车辆权重不同、目标是否被遮挡遮挡状态、置信度分数。AEB触发的目标后处理必须给出valid_flag为真且已经确认跟踪超过一定帧数并且目标位置必须经过滤波稳定。工程上这类目标通常还要过一遍“安全逻辑”——如果后处理内部检出目标状态异常或者时间戳超时宁可延迟一帧制动也不能在目标不明确时误制动。ACC自适应巡航是另一个典型场景。ACC的核心是选目标从所有有效目标里选出同车道内距离最近的目标作为跟随目标。后处理在这里要做的是保证每个目标的速度估计平滑尤其是目标刚进入ACC跟随范围时速度不能突变。否则ACC控制端发现目标速度从0突然跳到20m/s会先急减速再慢慢跟上那种驾驶体验约等于新手司机猛踩刹车。我的经验是ACC用到的目标速度至少经过5帧以上的滤波再进入控制且速度变化率也就是加速度也需要平滑输出让控制端有预测性。3.5 输出质量的评价闭环你说了不算回归数据说了算后处理做得好不好不能靠主观感觉需要一套评价闭环。我自己的习惯是除了常规的检测指标Precision/Recall对后处理效果重点盯三个指标ID Switch次数ID切换率、轨迹中断次数、以及跟车速度波动幅度。ID Switch是行业里衡量追踪稳定性的核心指标一帧之内目标ID发生切换的次数。高速场景下一次ID Switch就会让ACC把目标当成新目标处理导致一顿一挫。轨迹中断次数统计的是已确认轨迹因为遮挡或丢失而结束的次数如果一条轨迹在中远距离频繁中断下游功能就会频繁重新选取目标。速度波动幅度则是直接在功能层看跟车目标速度序列的方差正常情况波动应该很小。怎么测我的做法是录一批包含城区、高速、夜晚、雨天、隧道进出口等场景的测试数据跑完后处理算法用标注工具比如分析追踪结果的脚本脚本自动统计上述指标。每次改动关联阈值、卡尔曼参数或者生命周期参数都要跑一遍回归对比指标变化。我调参时最常遇到的情况是某个场景ID Switch变好了另一个场景误检变多了。这种时候就需要定义场景权重不是简单求平均而是按场景安全性分配权重后综合评分。4. 踩坑实录视觉后处理常见问题与排查经验4.1 目标ID频繁切换先别改算法先看数据ID Switch算是后处理问题里最让人抓狂的一个。目标ID频繁切换会导致功能层对目标状态感知不连续AEB可能把同一个目标当成新目标重新评估ACC会重新选目标体感就是车在高速上“犹豫不决”。遇到ID Switch我的排查路径是固定的。第一步把前后几帧图像数据抽出来看目标在图像上的重叠度。如果两个相邻目标在图像上几乎完全重叠那是当前匹配逻辑无法区分的正常现象需要考虑加入特征匹配比如目标外观特征或者用多传感器融合辅助分离。第二步检查检测框的稳定性。如果检测框宽度在连续帧内剧烈变化导致IoU不稳定优先解决检测框抖动比如对检测框做宽高比平滑。第三步调整匈牙利算法的代价矩阵。这个我在前面提过IoU阈值太严格会导致同一目标短暂失配然后重新分配ID太宽松又会让两个目标交叉时错配。我最终的做法是把IoU阈值作为场景参数缓存在不同场景高速、城市、隧道波动而不是全局固定一个值。4.2 高速上的目标速度突然跳到离谱值这是我自己踩过比较深的一个坑。某次测试中前方目标速度在200多公里每小时和正常值之间来回跳。后来排查发现问题不在卡尔曼增益而在坐标转换。车辆在弯道上行驶时如果是做纯像素到地面的投影目标框底边中心点在地面上的投影位置会有很大的系统偏差因为相机外参中的俯仰角在车辆加减速时会变化车辆抬头点头导致目标底边投影位置在前后方向上剧烈漂移速度自然就跳变了。解决思路有两个方向。一是做俯仰角补偿利用车道线消失点、或者IMU数据来实时估计相机俯仰角变化。二是目标位置滤波时增加观测噪声的自适应调整当检测到目标在图像上处于远距离、且地面投影不稳定的边缘区域时增大观测噪声R让滤波器更信任预测而不是观测。注意这是过滤问题不是根治问题根治还是需要前端做稳定的相机姿态补偿。但后处理端的这个自适应R能极大缓解功能层面的体感问题。4.3 低置信度目标造成误检置信度阈值不是一拍脑袋定的很多人一开始面试时问“置信度阈值设多少”脱口而出0.5。但0.5根本不具备普适性。检测模型在不同天气、不同距离上的置信度分布完全不一样。远距离的行人置信度普遍在0.3~0.5之间你要是全局设0.5行人目标全被滤掉AEB对行人功能就失效了。夜间大灯照射下目标的置信度整体偏低但目标往往是真的。所以阈值设定要考虑场景和距离维度。我的做法是把图像划分成近、中、远三个区域对应不同阈值同时结合时间信息连续低置信度但稳定的轨迹可以作为候选目标不立即丢弃。还有一个技巧是利用“消失一致性”——如果一个低置信度的目标轨迹在连续几帧内保持了稳定的运动趋势它比一个突然出现的高置信度目标更值得保留。功能层更怕的是那种“突然冒出来、又突然消失”的不稳定目标。4.4 雨夜、逆光、隧道口的经典翻车场景这几个场景几乎每个做后处理的人都会遇到我就直接给结论和经验。白天进隧道那一瞬间相机自动曝光来不及调整画面要么过曝要么全黑检测结果基本没法用。后处理这时候要做两件事一是对感知结果的时间戳做“无效标记”告诉下游这一帧数据不可信二是利用轨迹预测维持目标输出但不能让目标速度产生假变化。如果功能层因为进隧道瞬间感知失效而退出ACC体验很糟糕但如果后处理强行输出预测位置又可能因为预测误差积累导致目标位置偏差很大。折中方案是输出预测轨迹但降低置信度同时告诉功能层数据处于“感知降级”状态ACC可以继续保持但限制加减速幅度。雨夜场景则是另一个问题。地面反光、雨滴在镜头前形成光晕检测模型容易把水渍、灯光反射误检成障碍物。后处理层面能做的是加大确认帧数让这类误检因为时空不一致而被过滤掉。逆光场景目标往往只检测到上半部分或者出现“局部检测”框高度偏矮这会导致目标实际位置投影偏差。后处理可以加一个针对性的箱体过滤目标高度低于物理合理高度比如0.5米以下且ID又是首次出现就提高确认门槛。5. 一些个人经验与后续扩展方向说到这再补充一个我实际调试中摸索出来的经验。后处理里面参数“跟着场景走”这件事不要只靠感觉去调最好做成一张场景参数表。高速、城市、隧道、雨天、夜晚分别配一组不同的确认帧数、IoU阈值、置信度阈值、卡尔曼Q/R参数。运行的时候根据当前的场景识别结果光照、车速、雨刮状态、车道场景自动切换参数组。这比用一个全局参数跑所有场景要稳得多。但也要注意参数不能切得太频繁否则场景识别抖动会造成参数反复切换目标状态也跟着抖。我一般对场景标签做5秒以上的“粘滞”处理标签变了至少保持几秒再切换参数。另一个值得扩展的方向是和雷达、超声波的融合问题。纯视觉后处理做到最后你会发现一些结构性问题靠视觉本身很难解决比如单目视觉对目标绝对距离估计天生不精确尤其是远距离目标。这时候就需要多传感器融合来做互补。视觉后处理的输出结果可以作为融合的输入之一而融合后的目标质量反过来也可以校验视觉后处理的好坏。这个我后续准备单独写一篇这里先提一个核心观点视觉后处理做到后半段目标已经不是“检不出来”的问题而是“信不信得过”的问题而融合恰好是解决“信得过”的另一条关键路径。最后再分享一个小技巧调试后处理算法时记得做一个可视化的调试工具在图像上同时绘制检测框和目标轨迹、目标ID、速度矢量能极大提升排查问题的效率。很多复杂问题比如ID切换、速度跳变、轨迹漂移你光看数字是看不出来的但在图上放慢播放一眼就能定位。好的可视化调试工具是后处理算法迭代效率的分水岭——这个工具值得你花时间建好越早越好。
阅读完成 · 觉得有帮助?
咨询建站