十字斜入十字识别与决策:从图像特征到状态机控制
发布时间:2026/10/5 3:02:50 锦皓数字建站

如果你正在做摄像头组的智能车大概率会对“十字”这个元素印象深刻。它不像环岛或者坡道那样需要一套复杂的绕行动作但恰恰因为它看起来简单翻车的队伍一点都不少。尤其是“斜入十字”也就是车身从侧向以一个夹角冲进十字区域很多新手调到这里会卡上整整一两周还说不清到底是哪里出了问题。这篇学习日记我打算把十字和斜入十字从“图像识别”到“控制决策”完整拆一遍包括我自己调车过程中踩过的坑、总结的判据、以及可以直接抄的代码思路。内容主要面向摄像头组的同学但电磁组和光电组在思路上也能借鉴不少毕竟十字对车的考验并不只在传感器端更多是在“你看懂了但不知道该不该动”的决策端。1. 从赛道元素说起十字为什么容易翻车1.1 十字在赛道地图里的真实样貌先说清楚十字是什么。规则里的十字就是两条赛道在同一个平面内正交相交形成一个“十”字路口。车从其中一个方向进入穿过交汇区域再从对向赛道离开。这里有一个关键认知十字的正确答案通常不是转弯而是直行。很多刚开始调车的人看到十字会下意识联想到“路口”总想给它加一套转向逻辑。但比赛赛道的十字元素对车的考验就是能不能稳定地走直线不偏不斜地穿过路口而不是模拟现实车辆在十字路口的转弯行为。规则中十字的两条赛道都是可以通行的但从速度控制和稳定性角度来看保持直行是代价最低、风险最小的策略。正入十字的情形很简单车头正对着交汇区域进入前、中、后阶段的图像特征都是对称的处理起来比较友好。真正麻烦的是斜入十字。所谓斜入就是说车不是垂直进入交汇区域而是以大约30度到60度这样的斜角冲进十字对应到实际赛道里往往是车从弯道出来后没有完全摆正直接就是斜着进十字或者是赛道本身设计和出弯位置造成了这种非对称进入。在这种姿态下正入时那些“对称性好”的特征全部被打乱了图像里看到的赛道边缘关系、中线走势都会呈现出明显不对称。如果算法只按正入十字的逻辑处理很容易在关键的进入阶段把斜前方那条交叉赛道误判成正常弯道舵机跟着错误偏差一打车就直接窜出赛道了。1.2 正入与斜入的核心差异正入和斜入在图像特征上的差异可以归纳成三句话正入时左右两侧的边缘几乎同时断开斜入时靠近交叉道一侧的边缘会率先丢失。正入时十字的远端边界基本在画面中间偏上的位置形成一条接近水平的横边斜入时这条横边会歪向一侧并且露出来的长度明显变短。正入时中线在进入十字前是平滑连续地向远方延伸斜入时中线受交叉道影响会先发生一次明显的偏移甚至出现错误“吸”向交叉道的趋势。这三条差异直接决定了算法设计的侧重点。正入的判据可以设计得比较宽松因为特征太明显了斜入则必须把“边缘单侧丢失”和“上边线位置偏移”这两类信号综合起来才能准确判断车已经进入了十字区域而不是单纯丢线或者进了一个超大半径的弯。我在调车时还发现一个问题斜入十字如果处理不好往往不是“完全不识别”而是“识别晚了”。等算法判断出前方是十字的时候车头已经骑在交叉赛道边缘线上了这时候再去执行直行策略理论上没错但车身姿态已经被系统性地带偏了导致出十字之后无法回到正确赛道中心。所以斜入十字的重点不在十字中间而在于入十字之前的那个“准备阶段”这一点后面会展开讲。2. 图像侧认出十字特征提取与判据设计2.1 从原始图像到可用的边缘和中线不管用什么摄像头归一化之后的处理流程基本一致采集原图、二值化、逐行扫描边缘、计算中线。二值化这一步看起来基础实际对十字识别影响非常直接。我用过固定阈值也用过大津法后者的自适应能力在光照变化大的场地里明显更稳但缺点是计算量稍大对单片机性能有一定要求。如果算力紧张可以只在每帧图像中取一个局部ROI重新计算阈值而不是全图动态二值化这样既能兼顾抗光照又能节省时间。边缘扫描一般从图像最底部离车最近的一行开始向上逐行进行。每一行分别从图像中心向左、向右找黑白跳变点分别记为左边缘点和右边缘点。#define IMAGE_W 188 #define IMAGE_H 120 #define SCAN_ROWS 60 uint8_t left_edge[IMAGE_H]; uint8_t right_edge[IMAGE_H]; uint8_t edge_valid[IMAGE_H]; void scan_edges(uint8_t image[IMAGE_H][IMAGE_W]) { uint8_t row, col; for (row IMAGE_H - 1; row IMAGE_H - SCAN_ROWS - 1; row--) { left_edge[row] 0; right_edge[row] 0; edge_valid[row] 0; for (col IMAGE_W / 2; col 2; col--) { if (image[row][col] WHITE image[row][col - 1] BLACK) { left_edge[row] col; break; } } for (col IMAGE_W / 2; col IMAGE_W - 2; col) { if (image[row][col] WHITE image[row][col 1] BLACK) { right_edge[row] col; break; } } if (left_edge[row] 0 right_edge[row] 0 right_edge[row] - left_edge[row] 3) { edge_valid[row] 1; } } }注意这段代码里有个边缘跳跃约束条件只有左右边距大于3个像素的行才认为是有效行。这个约束是为了过滤掉那些因为赛道外噪点或者反光造成的“伪边缘点”。如果不对边缘宽度做最小限制逐行扫描很容易在十字区域抓住交叉道的边线产生一连串紊乱的边缘点。有了逐行的左右边缘之后中线就是左右边界的均值。正常情况下每一行都能算出中线整个图像可以形成一条连续的引导线。当进入十字区域后交叉道会在某些行形成额外的边缘导致左右边界不再来自同一条赛道这时简单的中线计算就会失真必须配合十字判据进行特殊处理。2.2 十字识别的三个典型判据十字识别的核心思想是判断“当前赛道信息是否出现了不属于正常弯道/直道的结构性变化”。我自己在实践中总结出三个比较可靠的判据三个信号同时满足基本可以确认是十字。第一个判据是边缘断点。逐行扫描时记录连续有效行的数量。如果从底部往上扫描累计超过N行没有检测到有效边缘或者某一行突然只有单侧边缘就说明赛道边缘信息出现了中断。十字正入时左右两侧几乎同时中断从图像上看就是一个明显的“断带”。第二个判据是上边界横线。十字的对向赛道在图像中会表现为一条大致横向的边缘线。用代码表达就是在断点之上的若干行中如果重新出现了符合赛道宽度的边缘对并且这个边缘对的宽度明显比当前行以下的正常赛道宽或者呈现水平走势就可以把它认定为十字的上边线。uint8_t detect_cross_edge(uint8_t start_row) { uint8_t row, count 0; for (row start_row; row start_row - 8; row--) { if (edge_valid[row]) { uint16_t width right_edge[row] - left_edge[row]; if (width NORMAL_WIDTH_MAX) { count; } } } if (count 3) { return 1; } return 0; }第三个判据是中线突变。把当前帧的中线走势和上一帧进行比较如果某一侧偏移量突然增大并且方向与车轮转角变化不一致说明图像里出现了异常结构。这个判据本质上是对前两个判据的补充主要用来防止第零帧刚进十字时误判。三个判据我建议写成独立的函数返回各自的置信度最后在决策层做加权综合而不是用一个嵌套大if去处理。这样在实车调参时你可以单独打印每个判据的输出值快速定位是图像侧没识别出来还是识别出来了控制侧没动作。2.3 斜入十字的特征补充斜入十字和正入十字在图像特征上的最大区别是“参考系歪了”。正入时从底部往上扫描到上边线之前左右边缘断开的位置基本在同一行斜入时靠交叉道那一侧的边缘会提前断开而行数较高的另一侧边缘还能继续延伸若干行形成一个不对称的边缘有效区。我的处理思路是这样的不再只依赖“左右同时断开”这个特征而是把单侧断开的行区间也作为触发条件。只要有一侧边缘在连续K行内都找不到同时另一侧边缘仍然有效就进入斜入十字候选状态。然后在这个候选状态下开始向上寻找交叉道横边特征如果找到就确认是斜入十字如果找不到就按单纯的单侧丢线处理可能是弯道外圈或者遮挡。这里有个细节很容易被忽略斜入十字的交叉道上边线在图像中的位置并不像正入那样在图像中央附近而是偏向一侧甚至可能落在图像的角落区域。因此上边线的搜索范围要适当扩大到左右两侧的图像边缘而不能只盯着中间区域。我把搜索区域从图像中心的等宽区间改成了整幅图像宽度后斜入十字的识别率明显上升误判率反而下降了因为误判更多是来自那些看起来像横边但实际是赛道外噪点的干扰而真正的斜入十字横边位置往往很固定。斜入十字还需要注意“交叉道入口”和“正常弯道入口”的区分。在一个很大的弯道里弯心一侧的边缘也可能在高端图像中丢失如果只看单侧断带就判定为十字必然会把大弯误判成十字。我的经验是弯道丢线的行数变化是渐进的边缘宽度的变化也是平滑的而斜入十字的断带是突发的并且往往伴随另一侧边缘的上沿出现一个明显的“台阶”。通过计算断点行附近边缘宽度的变化率可以很好地区分这两种情况。3. 控制侧走好十字状态机与决策策略3.1 先把结果想清楚直行通过很多队伍在十字处理上栽跟头不是因为图像没识别出来而是控制策略没想清楚。看到十字信号之后到底该让舵机保持什么角度是该加油门还是收油门这些问题在代码里没有统一的设计最后就是靠一堆临时补丁撑着换个场地就崩了。我的建议是先明确一个总原则检测到十字之后车辆保持当前姿态直行通过。也就是说进入十字前一刻的舵机角度就是穿越十字过程中的舵机期望角度。陀螺仪数据可以用来做角度微调但本质上不引入新的转向趋势不额外打方向。之所以强调这个原则是因为很多十字翻车其实是“车自己把自己吓拐了”。进入十字时交叉道入口的斜边缘会让常规循迹逻辑计算出巨大的偏差舵机大幅度转向车头一偏再想救就来不及了。所以十字处理的核心不是“更聪明地转向”而是“有依据地不转”。3.2 进入十字前的减速与保持识别到十字之后我一般会在状态机里切到CROSS_IN状态同时执行两个动作速度环目标值乘以一个预设的减速系数舵机期望角度锁定为进入十字前几个控制周期的均值。减速系数的取值和速度档位强相关。低速组可以设在0.7到0.8高速组需要更狠一点0.5到0.6也不夸张。原因很简单十字区域的赛道拓扑关系比较复杂即使判定正确车身出十字后的姿态依然有一定随机性留出更多的响应余量对后续恢复循迹更有利。舵机角度锁定不能直接锁死当前一帧的角度因为那一帧可能刚好有噪声。我用的是“进入十字前10帧角度做均值但去掉最大最小两个极值”的方式这样得到的直行基准更稳定。锁定之后并不是完全不动而是只允许在这个基准角度附近做很小的微调微调范围控制在正负两三个像素对应的转角以内。3.3 出十字后的恢复逻辑出十字的判断我依赖于“中线重新连续有效”。具体实现是维护一个有效计数变量每当一行同时检测到左边缘和右边缘并且边缘宽度落在正常赛道宽度范围内计数值加一。当连续有效行数超过预设阈值时认为车辆已经离开了十字区域可以恢复正常的循迹逻辑。这个阈值的设置需要结合十字长度和车速。我一般取图像底部往上扫描的有效行数连续20行有效作为出十字标志。太早恢复可能还在交叉区域边缘会把交叉道残余信息当作正常赛道太晚恢复出十字之后有一段真空期如果真遇到了紧接着的弯道根本没有反应时间。恢复循迹之后我会在接着的二三十毫秒内让舵机角度与正常计算值做一次线性插值过渡避免从“锁定直行”瞬间切换到“循迹转向”引起车头抖动。这个过渡在低速下看不出来但高速过十字之后接一个S弯时会非常明显不加过渡很容易甩尾。状态机的整体框架大概是四条状态正常循迹、十字判定中、十字通过中、十字退出恢复。代码上就是一个枚举变量加一个switch状态切换都记录当前帧数方便调试时通过数据回放确认每一帧的状态变化是否符合预期。typedef enum { STATE_NORMAL, STATE_CROSS_CANDIDATE, STATE_CROSS_INSIDE, STATE_CROSS_EXIT } car_state_t; car_state_t car_state STATE_NORMAL; void state_machine_update(void) { switch (car_state) { case STATE_NORMAL: if (cross_detect_flag) { car_state STATE_CROSS_CANDIDATE; } break; case STATE_CROSS_CANDIDATE: if (cross_confirm_flag) { car_state STATE_CROSS_INSIDE; record_steer_history(); } break; case STATE_CROSS_INSIDE: if (line_recover_count 20) { car_state STATE_CROSS_EXIT; } break; case STATE_CROSS_EXIT: if (exit_transition_done) { car_state STATE_NORMAL; } break; } }4. 实操实录从零搭一套可用的十字处理4.1 图像采集与离线分析调十字处理最忌讳的是直接改代码上车试跑飞了再改再试。这样的循环效率极低而且很多间歇性问题是复现不出来的。我强烈建议先做图像采集。方法很简单把车架起来用手推着走或者拿一块做好的赛道板让摄像头对着不同姿态的十字录制图像序列。录制时刻意把车头摆出各种角度正入、斜入、贴着边入十字都来几组。然后把图像通过串口或者SD卡导出用上位机逐帧查看。这一步的目的不是看图像好不好看而是要把赛道特征量化。比如你录了30帧从正常赛道进入十字的序列把每一帧的边缘断点行、上边线位置、中线偏移量记下来做成一张表你就能看到判据阈值应该设在哪里。比凭感觉设参数靠谱得多。我做过一个比较笨但有效的方法把图像里出现的所有边缘宽度都统计出来画成直方图。正常赛道宽度在一个窄范围内十字区域的宽度会明显偏宽两者之间往往存在一个清晰的低谷。在这个低谷区域取阈值比肉眼估计准确很多。4.2 核心代码结构与关键参数整个十字处理模块我会拆成三个文件图像处理、特征判定、状态决策。图像处理负责二值化和边缘扫描特征判定负责输出断点位置、上边线位置、边缘宽度等特征量状态决策负责结合特征量驱动状态机。这样拆的好处是你可以在不修改控制逻辑的情况下单独优化图像算法也可以在上位机上把特征判定模块单独编译跑离线数据。关键参数我整理了一张表这里给出我调车时的一组初始参考值不同赛道和摄像头安装角度下需要重新标定。参数名参考值说明二值化阈值系数大津法加权0.6光照变化大的场地建议动态阈值边缘最小宽度3像素过滤伪边缘断点连续丢失行数5行低于该值不算边缘断线上边线搜索行数8行断点上方搜索横边的范围上边线宽度阈值正常宽度1.8倍超过视为交叉道横边斜入单侧断带行数4行单侧边缘连续丢失即触发候选出十字恢复有效行数20行连续有效行数达到后退出十字状态舵机角度平滑帧数10帧十字中锁定角度的均值窗口这些参数都不是一次就能调好的。我的顺序是先调图像侧再调判定侧最后调控制侧。图像侧看到边缘扫描稳定了再开始调判据阈值判据在离线数据上能把所有十字都找出来了再上车跑。4.3 实车测试的观察点与调优方向实车测试十字时我习惯把主频拉低一点跑方便观察状态切换的时序同时通过无线模块实时回传状态机状态和关键特征值。车上必须能看清“当前帧到底进入了哪个状态”否则出了问题根本没法判断是图像侧还是控制侧。第一个观察点是距离。车距离十字路口大约多少厘米时状态机从正常切到了十字候选。如果切得太晚说明断点判据的阈值偏大或者扫描范围不够靠上如果切得太早说明误把前方赛道弧度的边缘变化当成了断线需要提高断带行数阈值。第二个观察点是舵机的反应。十字进入中舵机的锁定角度是否和进入前的循迹趋势一致。如果锁定角度和当前赛道走势相差悬殊说明均值窗口取得太短或者车辆在进入十字瞬间本身姿态就很差需要调整的是入十字前的循迹控制而不是十字本身的逻辑。第三个观察点是出十字后的轨迹。出十字后车身是稳定回到赛道中心还是偏向一侧压线。如果有持续偏向说明十字通过中的微调范围给得太大吸收了交叉道边缘的错误信息如果左右摇摆说明退出过渡时间设置得太短需要延长插值过渡的时长。5. 踩坑总结这些问题排查起来一个比一个磨人5.1 常见问题速查表现象可能原因排查与解决十字完全不动作直接飞出去判据阈值太严断点行数统计不满足离线回放特征值降低断带阈值大弯被误判为十字弯道丢线被当成断带增加上边线宽度判定结合中线突变检验十字能识别但入十字瞬间猛打方向舵机锁定逻辑未生效控制侧还在用正常循迹检查状态切换时序确认锁定角度赋值位置斜入十字总是右侧出界交叉道入口的斜边影响了中线计算在候选状态就切换中线来源改用锁定的舵机基准出十字后车身摇摆退出过渡时间过短延长插值过渡时长或降低过渡期速度状态机频繁切换抖动严重判定标志没有加消抖滤波对cross_detect_flag做连续多帧确认5.2 那些“玄学”现象背后的真实原因调车时经常有人说“这个情况上一版还能过去这版就过不去了”。大多数时候不是程序真的变了而是环境或者车辆状态变了。比如电池电压下降后电机响应变慢入十字瞬间车身姿态和满电时完全不一样又比如摄像头固定螺丝松动了一丁点视角低了几个像素图像里上边线的位置和宽度就全变了原来设定好的阈值自然不再适用。十字这种元素特别敏感因为它对距离、视角、姿态的耦合都很强。同一个程序车高调高2厘米可能十字识别的距离就差了十几厘米。所以每次动过机械结构之后第一件事不是跑速度而是确认图像里的赛道特征是否和上次一致。还有一个非常容易被忽视的问题二值化图像里十字上边线的显示效果受曝光影响很大。如果摄像头用的是自动曝光进入十字区域时画面亮度会突变造成上边线一两帧之内消失又出现状态机还没来得及确认就错过了窗口。我的处理是把曝光固定在白天赛道光照条件下的合理值不开放自动曝光除非场地光线变化实在太大才考虑加一层简单的亮度自适应。5.3 我的几点心得十字这个元素从表面看是图像识别问题往深了看是控制决策问题再往深了看其实是整车调校的综合问题。我在排除了一堆“算法bug”之后发现很多十字翻车其实是入十字前最后一个弯道的出弯速度太高车身姿态根本没有为十字做好准备。换句话说十字处理在逻辑上的复杂度并不高难的是让车辆在接近十字时保持一个稳定、可预测的姿态让算法有足够的余量去识别和执行。我后来专门为十字加了一个“入十字准备”的概念即在判定到十字的前一段距离内就不再对轨迹做激进跟随而是有意识地平滑舵机输出和速度输出。这样做的效果甚至比把判据调得更精细还要明显因为车在十字里的行为很大程度上是由入十字前那一刻的状态决定的。另外一个让我印象深刻的点是离线数据的重要性。很多时候你在赛道上反复试跑了十几圈也复现不了一次斜入十字的失败过程。但只要把上一次失败时的图像序列导出来逐帧看特征变化基本十分钟就能定位问题。所以如果你现在还在为十字头疼我建议先别急着改算法花一晚上把摄像头数据采集系统搭好后面能省出几倍的调试时间。最后再分享一个小技巧十字判据的几个阈值不要单独调调完之后用同一段录制的离线数据整体回归一遍确认没有因为改了某个阈值而破坏其他场景的识别率。我就是吃了一次亏为了斜入十字把单侧断带行数调小了结果环岛入口被大量误判成十字整个跑圈成绩反而更差了。回归测试虽然烦但能让每一次修改都心里有底。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。