ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

智能车摄像头组十字识别与补线:从图像处理到斜入十字判断

智能车摄像头组十字识别与补线:从图像处理到斜入十字判断 调试了一晚上终于把十字的判断从“肉眼认得”变成了“代码认得出”。这篇是智能车学习日记的第二篇主要记录摄像头组处理赛道元素时最折磨人的一个点——十字和斜入十字。如果你正在调车正被“车过十字就乱打方向”或者“斜着进十字直接冲出去”折磨这篇文章应该能帮你省几个通宵。内容会从图像处理的基础流程讲起重点拆解十字的判断逻辑、补线策略以及斜入十字的识别技巧全程都是我这段时间在赛道上实测过的方案。1. 十字与斜入十字为什么这个元素最磨人1.1 规则里的十字到了图像里变成了什么全国大学生智能车竞赛摄像头组里十字属于基础赛道元素但“基础”不等于“简单”。规则上它就是一个横竖交叉的黑线图案车从下方驶入穿过交叉点后从上方或侧方离开。但问题是图像传感器看到的不是“十字”这个抽象概念而是每一行扫描线上黑白跳变的位置。在二值化图像里正入十字的典型特征是底部若干行是正常赛道左边线、右边线都清晰再往上进入交叉区域后左右边线同时向外“跳”了一下中间出现一大块黑色区域。如果继续往上横线结束赛道恢复成正常的左右边线。这个“跳变宽黑区”就是十字在图像里的本质。但实际跑起来你会发现十字并不是每次都很规矩地出现在画面中央。车体有姿态角摄像头有安装倾角赛道光照不均匀导致十字在图像里的形态千奇百怪。这也是为什么很多人明明写了十字判断代码上了赛道就失灵因为代码只针对了“理想十字”没有覆盖实际变化。1.2 斜入十字为什么比正入十字难一个档次斜入十字最大的麻烦在于它不是“左右对称”地进入十字区域的。正入十字时左右边线同时受到横线干扰丢线特征明显且同步斜入时车体与十字横线有一个夹角车身一侧先靠近横线另一侧后靠近导致左右边线的异常出现明显的时间差。这就带来两个问题。第一判断窗口变短正入十字可以从“左右同时异常”这个特征入手斜入十字则要等两侧异常都出现才能确认但那时候车可能已经冲进交叉点了。第二容易与弯道混淆斜入十字初期先异常的那一侧丢线特征和过弯时的丢线非常像如果阈值设置不当就会把十字当成弯道处理转向控制完全错误。我自己在调试时就遇到过这种情况车斜着进十字程序判断成了“右弯”猛打方向结果直接冲出赛道。后来才明白斜入十字的判断不能只靠丢线必须结合边线的斜率变化和异常区域的高度范围来做综合判断。1.3 我选用的硬件和图像处理底子说具体方案之前先交代一下我的硬件配置。主控用的是逐飞科技的TC264开发板22届智能车很多队伍都在用这款摄像头是总钻风灰度摄像头分辨率开的是188×120通过DMA直接搬运到内存数组里。图像处理整体流程是灰度图 → 固定阈值二值化 → 从底部向上逐行扫描左右边线 → 提取中线 → 根据中线偏差算转向。这套流程在普通赛道元素直道、弯道、环岛上都跑得比较稳但到了十字就暴露了问题因为十字区域的边线特征和普通赛道差异太大如果不做专门的“十字状态识别”边线提取就会出错中线计算也会乱掉。所以十字处理的核心不是在“正常模式”里打补丁而是要建立一个“十字模式”一旦识别到就切换处理逻辑。2. 从二值化到边线十字识别的准备工作2.1 二值化阈值怎么选才不容易被光照骗二值化是把灰度图变成黑白图的关键一步阈值选得好不好直接影响后续所有判断。官方推荐的大津法Ostu在理论上很完美但实际跑车时我基本不用——它的计算量偏大而且在十字区域这种黑白面积比例突变的情况下阈值会被“带跑偏”。我用的方法是固定阈值加动态补偿基础阈值在初始化时通过环境光校准一次运行过程中根据图像整体灰度均值做小范围修正。具体来说取图像中间区域的灰度平均值如果平均值比基准值高说明环境变亮了阈值也适当上调反之则下调。修正幅度限制在正负15以内防止剧烈跳动。这里有个很关键的细节二值化一定要做ROI裁剪不要对整幅图做。因为车体前方近处的图像底部几行全是近景赛道远处的图像顶部区域往往是空白或远景这些区域会干扰阈值统计。我把有效区域裁剪为图像中间偏下的60行只对这个区域做二值化既稳定又省时间。2.2 左右边线扫描和丢线标记边线扫描是从二值化图像中提取赛道边界的基础操作。我的扫描逻辑是从图像底部一行开始先根据上一次的边线位置确定搜索起点然后分别向左和向右搜索黑白跳变点记录为左边界和右边界如果某一行找不到有效的跳变点就标记这一行“丢线”。丢线标记要特别注意“孤立丢线”和“连续丢线”的区别。一行两行的孤立丢线大概率是噪点或者反光直接忽略但连续多行丢线就是真正的赛道特征了。我在代码里用了一个lost_count数组记录每一侧的连续丢线行数只有连续丢线超过一定行数才认为真的丢了这个设计在后面十字判断中帮了大忙。搜索起点的选择也有讲究。直接每行都从图像边缘开始搜容易把赛道外的噪点当成边线。更好的做法是“就近搜索”当前行的搜索起点是上一行找到的边线位置搜索范围限制在左右各40个像素内。这样既能跟上赛道变化又能过滤掉远处的干扰。2.3 8邻域搜索为什么让边线更稳最早我用的逐行独立扫描后来发现边线在十字附近会出现“断点”上一行正常下一行突然找不到跳变再下一行又出现了。如果每一行都独立搜索这种断点很容易让程序误判为丢线。后来参考了网上一些开源方案改用了8邻域搜索。8邻域搜索的思路是从底部中心点出发找到第一个黑点后下一行不是重新全局搜索而是在上一行黑点的周围8个方向上、下、左、右、左上、右上、左下、右下范围内寻找下一个黑点。这样边线就具备了一定的“连续性约束”不会因为个别行的噪点而突然断裂。这个改进对十字判断尤其重要因为十字区域的横线会产生多个黑白跳变点如果逐行独立扫描边线很容易被横线“吸走”导致边线位置错误。8邻域搜索配合“距离约束”可以确保边线沿着赛道方向延伸而不是跳到横线上去。实测下来边线断点率降低了至少60%。3. 正入十字的判断逻辑与补线实操3.1 正入十字的三个特征信号我在代码里通过三个信号综合判断正入十字缺一不可。第一个信号是“左右同时长距离丢线”。正常弯道最多单侧丢线环岛的丢线也是单侧为主正入十字时横线覆盖了左右两侧导致左右边线从某一行开始同时丢失而且这个丢线行数要足够长我设置的是连续10行以上。这是正入十字最核心的特征。第二个信号是“底部边线正常上方边线异常”。如果一上来就丢线那可能是车已经冲出赛道了。正入十字的特点是图像底部几行近处边线完全正常从中部某一行开始左右边线同时消失再往上又恢复正常。这种“中间异常、两边正常”的分布是十字的标志。第三个信号是“异常区域宽度突变”。在丢线的那一段左右边线之间的距离会突然增大到正常赛道宽度的1.5倍以上。这说明车正处于横线覆盖区域内视野中的黑色区域明显变宽。我用一个 variable_width 记录这个宽度超过阈值就认为满足条件。三个信号都满足时状态机从“常规循迹”切换到“十字模式”。3.2 判断阈值怎么定才不容易误判阈值定得太松弯道会被误判成十字定得太紧真十字又识别不出来。这是调参过程中最折磨人的环节。我的做法是把阈值参数化提供三个可调节的宏定义然后在调试界面实时查看当前值边跑边调。丢线行数阈值我设置在8到12行之间这个值跟车速有关车速越快同样时间内车前进的距离越长丢线行数会相对少一些。跑2.5m/s的时候我用的10行如果提速到3m/s就要降到8行左右不然判断会滞后。宽度突变阈值我用的是“相对值”而不是“绝对值”。因为不同赛道宽度、不同摄像头安装高度导致正常赛道在图像里的宽度千差万别。我的做法是取图像底部五行正常宽度做一个基准值base_width然后检测到丢线区域的宽度超过1.4倍base_width时才算有效。用相对值的好处是换场地、换赛道不需要重新整定这套参数。还有一个细节判断十字的时机要选在“横线刚进入视野”的时候而不是“横线已经在画面中央”的时候。因为转向控制是有延迟的等横线到中央再反应车已经冲到交叉点了。所以我的判断窗口放在图像高度的30%到60%这一段这个区域的异常最能提前反映十字的到来。3.3 补线策略十字模式下的中线生成识别到十字之后最大的问题是原来的中线算法在丢线区域是失效的必须“补线”。补线的目标不是还原真实赛道而是给转向控制一个稳定的参考线让车能平滑地穿过交叉点。我的补线方案分三步走。第一步确定“补线起点”取丢线区域的前一行也就是最后一个边线正常的行记录该行左右边线的中点作为补线起点。第二步确定“补线终点”取丢线区域结束后的第一行记录该行左右边线的中点作为终点。第三步用线性插值把起点到终点之间的所有行都补上一条虚拟中线。这个方案的核心思想是十字区域的中线应该是从入场点到出场点的平滑过渡而不是跟着横线乱走。线性插值虽然简单但实测效果很好因为十字交叉点内部本身就不需要精确循迹只需要保证方向大致正确就行。补线完成后转向PID照常工作用的还是“中线偏差”这个量只不过这个中线是补出来的。这样做的最大好处是转向控制逻辑完全不用改只是输入的数据更可靠了。代码层面改动量小出问题的概率也小。4. 斜入十字识别从丢线模式到斜率判别4.1 斜入十字和正入十字在图像里的不同表现斜入十字最典型的图像特征是不对称。正入十字是左右边线同时往上丢线斜入十字则是某一侧先出现丢线另一侧还在正常跟踪。比如车从十字的右下方向左上方斜穿那么右边线会先接触到横线出现丢线左边线还要再晚个十行左右才会丢。这种“单侧先丢”的特征和弯道非常像。右弯的时候左边线会丢因为赛道向左弯左边线超出视野左弯的时候右边线会丢。所以斜入十字的第一阶段程序很容易把十字误判成弯道。我最初调这一块的时候车总是斜着进十字然后突然大角度转向就是这个问题。但从另一个角度看斜入十字也有它独特的特征单侧丢线之后另一侧很快也会跟着丢线这个“第二侧丢线”发生在很短的行数差内。而普通弯道只有一侧丢线另一侧会一直保持到弯道结束。这个“行数差”就是斜入十字和弯道的分水岭。4.2 我用的是斜率差分判断法为了区分斜入十字和弯道我设计了一个“斜率差分”判断法简单说就是记录丢线侧边线在连续多行内的水平偏移量计算它的斜率变化趋势。具体实现当检测到单侧丢线时进入“疑似十字”状态。在这个状态下我持续记录另一侧正常边线每一行的位置计算它的斜率。如果是弯道正常侧的边线斜率是持续单向变化的比如一直往左偏如果是斜入十字正常侧的边线斜率会先保持一个方向然后突然反向或者剧烈跳变因为横线开始影响这一侧的边线提取了。这个斜率跳变发生的时刻就是斜入十字真正“露出真面目”的时刻。我设定了一个斜率差分阈值当相邻5行内边线斜率的变化量超过设定值时判定为斜入十字立刻切换到十字模式。这个方案的优点是不需要额外传感器完全靠图像特征缺点是斜率计算对噪点敏感。所以我先对边线位置做了一次中值滤波滤掉单点的跳变再算斜率这样数据就稳定多了。4.3 斜入十字与大弯道的区分技巧除了斜率差分我还有一个辅助判断横向位置约束。十字不管怎么斜入车在进入交叉点时车体一定是朝着十字中心区域移动的。所以丢线那一侧的边线位置不会无限往外偏而是会在某个范围内“停住”。弯道则不同弯道里丢线侧的边线如果一直丢说明车在持续转向边线位置会一直偏向极端值。我用一个计数器来统计“单侧丢线持续的行数”如果超过30行还没有出现另一侧丢线那就基本可以排除十字按弯道处理。还有一个实用技巧通过摄像头安装角度来判断。因为我的摄像头视角范围是有限的如果十字横线在画面中的位置很高接近图像顶部说明车距离十字还有一段距离这时候即使出现斜入特征也不用急着切十字模式可以先按弯道处理等横线进入中段区域再切。这相当于人为加上了一个“距离门控”能有效减少误判。4.4 斜入十字时的补线修正斜入十字的补线不能直接套用正入十字的方案。因为正入十字的丢线区域左右对称线性插值效果很好斜入十字丢线区域不对称起点和终点的中线不在一条直线上直接用线性插值会导致补出来的中线有一个明显的折角转向会抖。我的解决方案是补线分两段做。第一段从“丢线开始”到“丢线中部”补线斜率采用丢线前正常边线的趋势延伸第二段从“丢线中部”到“丢线结束”补线斜率采用丢线后正常边线的趋势延伸。这样补出来的中线是一个平滑的折线更加接近车实际应该走的路径。虽然多了一段判断但代码量增加不多效果却很明显。我用同一组PID参数跑正入十字和斜入十字转向输出都相当平滑没有出现明显的抖动或过冲。5. TC264上的代码实现与调参细节5.1 十字判断函数的代码结构TC264的主频虽然不低但图像处理本身就占了不少CPU时间所以十字判断的代码我一律写成纯计算不用浮点运算能用查表就不用函数计算。下面是我十字判断核心部分的代码逻辑框架。typedef struct { uint8_t lost_left[ROW]; // 每一行左侧是否丢线 uint8_t lost_right[ROW]; // 每一行右侧是否丢线 int16_t left_edge[ROW]; // 左侧边线位置 int16_t right_edge[ROW]; // 右侧边线位置 uint8_t cross_flag; // 当前是否处于十字模式 } CrossJudge_t; uint8_t check_cross(CrossJudge_t *cj) { // 1. 统计连续丢线行数和起始行 uint8_t start_row 0; uint8_t lost_len 0; for (uint8_t i ROW_BOTTOM; i ROW_TOP; i--) { if (cj-lost_left[i] cj-lost_right[i]) { if (lost_len 0) start_row i; lost_len; } else if (lost_len 0) { break; } } if (lost_len CROSS_LOST_MIN) return 0; // 2. 计算底部正常段宽度基准 uint8_t base_width 0; for (uint8_t i 0; i 5; i) { base_width (cj-right_edge[i] - cj-left_edge[i]); } base_width / 5; // 3. 计算丢线段的宽度突变 uint16_t cross_width cj-right_edge[start_row] - cj-left_edge[start_row]; if (cross_width base_width * 14 / 10) return 0; // 4. 确认十字 cj-cross_flag 1; return 1; }这段代码的逻辑就是我在第3节讲的三个信号的具体实现。实际项目中我加了一个简单的状态机用cross_flag区分正常、疑似、确认三种状态避免在十字边缘反复切换。5.2 三个关键参数的调参方法十字识别有三个参数最影响效果我一个个说。第一个是CROSS_LOST_MIN最小连续丢线行数。这个参数决定判断灵敏度。跑2m/s左右时我建议设10到12行速度越快这个值要越小因为同样时间车跑得更远丢线的“行数”会更少。但太小也不行弯道里偶尔也会出现8行左右的连续丢线容易误判。第二个是宽度突变比例代码里的14/10。这个参数决定“宽度要多宽才算十字”。赛道正常宽度的1.4倍是我试下来比较合适的值。如果摄像头视角比较窄可以降到1.3如果视角很宽建议设1.5以上因为宽视角下赛道在图像里本来就比较宽横线带来的宽度变化会被“稀释”。第三个是判断窗口的范围。我限制十字判断只在图像30%到60%高度区域内做这是最核心的防误判手段。如果这个区域太大十字横线在很远处就会被检测到容易和远景噪点混淆如果太小检测就太晚了转向来不及。具体区域要根据摄像头俯仰角调整我的摄像头俯仰角是15度左右30%到60%是最佳区间。5.3 实测效果车速、转向与稳定性整套方案联调之后我在实验室的两条赛道上做了测试。第一条是正规的十字元素车速2.8m/s连续跑了20圈十字识别成功率100%没有一次误判也没有一次在十字里剧烈抖动。第二条是我自己摆的斜入十字由直道斜向进入大概30度夹角车速降到2.3m/s成功率大概在90%左右偶尔会有一次判断偏晚但补线的容错性能让车不冲出赛道。稳定性方面我重点观察了转向输出PWM的平滑度。在十字区域转向输出没有出现正负来回跳的情况说明补线生成的虚拟中线确实起到了稳定作用。对比之前用“丢线就保持上一方向”的简单策略转向平滑度提升了很多。6. 实际调车中最容易翻车的四类问题6.1 十字前车辆抖动这个问题的典型表现是车快到十字时方向左右摆动像在犹豫要不要转弯。原因通常是“疑似十字”状态和“常规循迹”状态在反复切换导致转向指令忽左忽右。解决方法是引入迟滞一旦进入“疑似十字”至少要持续固定行数我用的是15行才能退出不允许在边缘快速切换。6.2 误判大弯为十字高速过一个急弯时丢线行数可能超过阈值宽度也可能因为丢线而看起来变宽于是被误判成十字。我踩过这个坑之后加了一条硬性约束在判断十字之前先检查丢线区域两侧的边线是否存在“外扩”特征。弯道丢线区域是一侧边线逐渐消失另一侧边线正常且位置连续十字丢线区域是两侧边线同时往外跳变位置有一个明显的台阶。通过检测这个台阶能过滤掉80%以上的弯道误判。6.3 斜入十字漏判漏判的根源是判断窗口太窄或者斜率差分阈值太大。如果是窗口问题把窗口范围从“30%到60%”调整为“25%到65%”如果是阈值问题把斜率差分阈值往下调一档试试。我自己的经验是斜率差分阈值宁可调小一点多触发几次“疑似”也比漏判好。因为疑似状态只是多做一些判断不会立刻改变转向输出漏判则会直接飞车。6.4 光照变化导致十字误判这是最隐蔽的问题。场地灯光在十字区域的反光会让横线部分变亮二值化之后横线被“截断”十字特征不完整程序识别不到。我的应对策略是在二值化时增加一个“暗区补偿”如果某一行的最大灰度值明显低于正常范围说明这一行可能存在阴影遮挡把阈值适当降低。同时在十字判断中加入“区域连续性”检查即使横线中间有几行断裂只要断裂行数少于3行仍然认为是完整的十字。最后补一句经验十字和斜入十字的识别说到底就是在“灵敏度”和“可靠性”之间找平衡。我调试了差不多两周最深的体会是别指望一套参数走天下车速、摄像头角度、甚至赛道表面的反光程度都会影响判断。比较好的做法是把判断逻辑和参数完全分开逻辑是通用的参数留着调。这样每次换场地只需要花几分钟调几个宏定义而不是重写判断流程。另外有个小技巧调试时一定把图像处理的结果实时显示出来——左右边线、丢线标记、十字模式状态、补线位置全部画在画面上。哪怕代码逻辑你觉得自己想得再清楚不看到可视化效果很多问题你根本意识不到。我有一半以上的bug都是看着实时图像才定位到的。
返回列表