智能车摄像头组八邻域边界追踪实战:从像素邻域到转向控制

发布时间:2026/10/4 7:21:24
智能车摄像头组八邻域边界追踪实战:从像素邻域到转向控制 写这篇东西的时候我刚从实验室出来屏幕上还留着赛道图像的回放。智能车摄像头组的图像处理绕不开边界提取而边界提取里最常用也最容易被低估的就是八邻域。很多人觉得八邻域不就是看周围8个像素嘛有什么好讲的——但实际调车的时候你会发现同样的八邻域思路有人提的边界干净利落有人提的边界全是毛刺差距全在细节里。这篇文章是八邻域应用的第二篇。上一篇我梳理了像素邻域的基本概念和最简单的边界点判断这一篇直接进入实战层面从八邻域追踪的底层实现到边界数据怎么变成转向控制再到十字、环岛这些比赛元素的识别思路最后把我调车一年多踩过的坑集中倒出来。如果你正在准备智能车竞赛的摄像头组或者只是对嵌入式图像算法感兴趣这篇应该能帮你少走很多弯路。1. 从采集到控制八邻域在智能车图像链路中的真正位置1.1 摄像头组图像处理的标准流程先把摄像头组的数据通路理清楚。摄像头常见的有总钻风、MT9V034这类灰度摄像头通过DMA把图像数据传到单片机内存得到一个灰度数组。以我用的188×120分辨率为例一帧图像就是188×120个uint8_t数据。接下来要做二值化把灰度图转成0和1——通常赛道是白色、背景是深色所以设定一个阈值大于阈值的置1、小于阈值的置0。这一步输出的二值图就是八邻域算法的主战场。二值化之后才是重头戏。我们要从这张0/1图里提取赛道的左右边界再由边界算出中线最后把中线的偏移量喂给转向舵机的PD控制器。整个链路可以概括成采集 → 二值化 → 边界提取 → 中线计算 → 转向控制。八邻域在这个链路里承担的就是边界提取这一环它的输出质量直接决定了后面所有环节的稳定性。很多新手容易犯一个误区拿到一张二值图就急着算中线简单地从上到下逐行扫描每行找到最左和最右的白点就当作边界。这种做法在空旷的直道上确实能跑但一旦遇到光照变化、阴影、反光或者赛道上出现干扰物逐行扫描就会输出一堆乱跳的边界点中线被带偏车就会蛇形甚至冲出赛道。1.2 为什么用八邻域追踪而不是盲目逐行扫描逐行扫描的问题是它没有任何记忆。处理第50行的时候它不知道第49行的边界在哪里每一行都是独立判断。而八邻域边界追踪的思路完全相反——它像是一个人沿着墙走你从某个边界点出发每一步都检查当前位置周围的8个像素找到下一个属于边界的点然后走过去。这样走出来的边界是一条连续的路径即便某一行因为噪声导致二值化出错只要上一行的边界方向是对的追踪结果也不会偏差太多。我把这个区别跟队友解释过很多次逐行扫描是每行独立猜一次边界八邻域追踪是顺着边界走一圈中间不会丢。在智能车这种实时性要求高、计算资源有限的场景里后者天然更适合作为边界提取的主算法。当然八邻域也不是银弹。它的前提是二值化结果基本可靠、赛道区域连通性较好。如果遇到强烈的阳光直射、赛道反光导致大片区域二值化错误再好的追踪算法也会失效。所以实际工程里一般会用八邻域追踪为主、逐行扫描为辅配合阈值自适应来做兜底。这个后面细说。1.3 边界追踪到底要输出什么写代码之前先搞清楚接口。八邻域边界追踪输出的是两组数据左右边界点数组。每个元素记录边界点在图像中的横坐标x和纵坐标y数组下标就是行号或者追踪序号。边界有效长度。追踪了多少个点、覆盖了图像哪些行这个信息用来判断赛道在视野里的可见范围。有了左右边界数组中线就很好算了每一行取(left_x right_x) / 2得到一条中线的离散点序列。再往下中线的横向偏移量比如图像底部一行中线相对画面中心的像素差经过比例放大就变成了舵机的转角输出。所以你看八邻域的输出并不仅仅是画一条好看的线——它是在为整个控制回路提供最核心的反馈信息。边界数组的准确性、连续性、实时性每一项都会直接反映在赛车的行驶品质上。2. 八邻域边界追踪的底层实现方向表、起点选择与终止条件2.1 像素坐标系与方向表在动手写追踪循环之前先约定坐标系。图像处理里一般用x表示列向右增大y表示行向下增大原点在左上角。假设当前边界点坐标是(cx, cy)它周围的8个邻居可以用8个方向向量表示右(1, 0)右下(1, 1)下(0, 1)左下(-1, 1)左(-1, 0)左上(-1, -1)上(0, -1)右上(1, -1)在代码里我习惯用两个查表数组来存放这8个方向的增量// 顺时针方向表从正右方开始 static const int8_t dir_dx[8] {1, 1, 0, -1, -1, -1, 0, 1}; static const int8_t dir_dy[8] {0, 1, 1, 1, 0, -1, -1, -1};注意方向表的顺序是有讲究的。顺时针和逆时针两种排法会影响追踪时先看哪个方向。实际使用中追踪右边界和左边界往往需要不同的方向优先级这个细节很多人会忽略。我建议把8个方向写成顺时针表追踪的时候按需偏移起始搜索方向这样逻辑最清晰。2.2 追踪循环的核心逻辑边界追踪的主体是一个循环每一步做三件事在当前点周围找下一个目标点、移动到目标点、记录路径。找下一个目标点时不是每轮都从方向0开始蛮力扫描而是从上一次前进方向的下一个方向开始。这样做的好处是保持前进趋势的连续性避免边界点来回抖动。核心伪代码如下当前点 起点; 记录当前点; for (;;) { found 0; for (i 0; i 8; i) { d (上一次方向 1 i) 7; nx 当前点.x dir_dx[d]; ny 当前点.y dir_dy[d]; if (nx/ny 越界) continue; if (二值图像[ny][nx] 赛道像素) { 当前点 (nx, ny); 上一次方向 d; 记录当前点; found 1; break; } } if (!found) break; // 周围没有赛道像素追踪结束 if (当前点 起点) break; // 回到起点闭合边界 }这里有个容易迷惑的地方为什么是(上一次方向 1 i) 7因为上一次前进方向存储的是0~7的索引加1再对8取模相当于把搜索的起始角度从正前方偏到偏左一侧然后在八个方向上逐个试探。 7是对8取模的位运算写法比% 8快很多在单片机上值得养成习惯。实测下来这个循环在188×120的二值图上单次追踪的耗时在几十微秒到一两百微秒之间取决于MCU主频和编译器优化等级完全满足智能车摄像头组20ms一帧图像的处理要求。2.3 起点的选择与防死循环设计起点选择直接决定追踪的成败。我用的方案是从图像底部行开始从中间向左右两侧扫描找到第一个赛道像素作为左边界起点再从右向左扫描找到第一个赛道像素作为右边界起点。这样选起点有两个好处一是底部区域离车最近透视畸变最小二是图像底部大概率是赛道而非背景找到起点的概率高。防死循环是八邻域追踪里最不能省的一步。如果二值图像里有孤立的噪声点或者赛道区域形成了环形结构追踪可能陷入无限循环。所以我设置了两个退出条件一是连续8个方向都找不到目标像素就退出二是累计追踪点数超过预设上限比如255就强制退出。第二个条件尤其重要它确保即使算法出错也不会卡死主循环。我在代码里还加了一个方向回退保护如果当前点的下一步搜索方向和上一步完全相反相差4个方向索引说明可能走进了死胡同这时宁可提前终止也不要让边界路径来回折返。这个保护在环岛和十字区域特别有用后面会展开讲。3. 从边界到中线后处理、补线与特殊元素识别3.1 追踪完成后边界数据还要过一遍修正八邻域追踪输出的原始边界点是按路径顺序排列的不是按图像行号排列的。这就有个问题转向控制需要知道每一行赛道的左右边界在哪里所以要把追踪得到的点序列映射到行号上。我的做法是建立一个行号到边界坐标的映射表遍历追踪得到的点序列对于每个点把它的x坐标存到对应行号的槽位里。如果同一行出现了多个边界点保留最左边或最右边的一个根据是左边界还是右边界决定。这样处理后就得到了一个按行号索引、可以直接用于中线计算的边界数组。这个映射过程还有个意外收获它能天然剔除一部分追踪噪声。比如某个行号上出现了一个孤立的毛刺点距离相邻行的边界点明显偏离我会做一个简单的跳变检测如果当前行边界坐标与上一行差值的绝对值超过设定阈值比如30像素就认为这一行数据异常用上一行的数据填充或者做线性插值。这个一行的跳变处理比后期做各种滤波效果都直接。3.2 中线的三种计算方式与补线策略有了左右边界数组中线计算看起来很简单中线x (左边界x 右边界x) / 2。但实际跑车的时候你会发现边界数据经常是残缺的——有时候左边界追踪断了有时候右边界在远方丢了一截。这时候直接做平均会把中线拉得乱七八糟。我总结了几种补线策略按优先级排序双边有效时正常取平均。某一侧边界缺失但另一侧正常时用镜像补线取正常侧边界坐标 一个经验补偿值通常取上一帧该行的赛道宽度补出缺失侧的位置。两侧都缺失的远端区域直接置无效不参与中线计算。控制层看到无效区域会降低速度或者忽略远处误差。中线计算出来之后我还会做一层滑动平均滤波。常见做法是对连续5帧的中线坐标做加权平均权重偏向当前帧让转向响应既平滑又不至于太迟钝。注意滤波窗口不要开太大否则过弯时中线会甩尾导致转向滞后。3.3 十字、环岛、坡道的边界特征怎么用八邻域判断这部分是我觉得整个系列最实用的内容。智能车竞赛里十字、环岛、坡道这些元素是摄像头组的必考项目而它们本质上都是特殊的边界结构。十字路口当车驶近十字时二值图像里会出现一条横向的白色条带就是横着的另一条赛道。从八邻域追踪的角度看特征非常明显——左右边界追踪到十字区域时会突然断开因为边界不再是一条纵向线而是在交叉处与横向赛道融为一体。我的判断逻辑是检测到左右边界在连续多行内同时失效且失效区域下方存在一条横向白色连通带就判定是十字。此时控制策略是保持方向、减速通过等边界恢复后继续正常循迹。环岛环岛是比十字更麻烦的元素。进环时赛道会出现一条分支入环口图像里能看到一个封闭或半封闭的白色区域。用八邻域追踪左边界时会发现边界在某个位置出现分叉——一条继续向前一条向内侧弯。我这里的经验是同时启动两条追踪路径分别记录外边界和内边界的长度与曲率变化根据内边界是否形成了近似圆形的封闭轮廓来判断环岛入口。出环的判定则看内边界是否突然消失、外边界是否恢复连续直线。坡道坡道对八邻域追踪的挑战主要来自透视形变。上坡时图像中远处的赛道会突然抬高原本在某个行号范围内的赛道像素会整体上移导致追踪到的边界点发生阶梯状跳变。我的处理办法是检测到边界高度在连续几帧内整体抬升超过一个阈值时进入坡道模式坡道模式下降低追踪起始行看得更近同时放宽跳变检测阈值避免把坡道边缘误判为噪声。搞定了这几个特殊元素的边界特征八邻域追踪的输出就真正变成了可用的引导信息而不只是一条画在屏幕上的线。4. 八邻域的进阶用法连通域标记与种子填充4.1 用种子填充提取赛道连通区域八邻域除了做边界追踪还有一个重要用途是判断像素间的连通性。所谓连通域就是相互连通的同色像素构成的区域。在智能车场景里我经常需要从图像底部中心选取一个种子点用八邻域做种子填充也叫泛洪填充把整条赛道区域提取出来。种子填充的算法比边界追踪更简单维护一个待处理队列初始时把种子点加入队列弹出队列头部像素检查它的8个邻居凡是属于赛道像素且还没被访问过的就标记为已访问并加入队列尾部重复这个过程直到队列为空。在188×120的图像上做一次全图种子填充耗时大约2~4ms主频150MHz的MCU虽然比边界追踪贵但在比赛中的有些场景非常值。比如当赛道有大面积阴影、边界追踪效果很差时用种子填充提取出整个赛道连通域再对连通域的轮廓求边界鲁棒性比直接追踪高很多。我把这种方式当作重武器平时不轻易用遇到复杂光照或者规则改变的赛道工况时再启动。4.2 腐蚀膨胀形态学操作与八邻域的关系形态学操作腐蚀、膨胀的本质也是邻域判断。腐蚀的规则是如果中心像素的邻域范围内有任何背景像素就把中心像素置为背景膨胀则相反邻域内有任何赛道像素就把中心置为赛道。这里八邻域和四邻域的选择会影响处理效果。八邻域腐蚀的去噪能力更强但也更容易把细小的赛道结构比如窄道、断头路抹掉四邻域更保守保留细节的能力更好。我个人的经验是智能车二值图做去噪优先用八邻域腐蚀因为它能更快地消除孤立噪点如果赛道本身有细长结构需要保留再退回到四邻域。形态学操作在八邻域追踪之前做效果最为明显。先腐蚀掉孤立噪声点再膨胀恢复赛道宽度最后做边界追踪这样追踪到的边界毛刺会大幅减少。不过要注意每次形态学操作都会改变赛道边缘的位置腐蚀会向内缩膨胀会向外扩如果做了多轮操作边界坐标会偏移好几个像素。我的习惯是只在调试阶段用形态学做离线对比实车运行尽量少用因为实时计算开销不小。4.3 面积和宽高比障碍物判断的最简手段八邻域连通域标记的另一个直接应用是障碍物识别。比赛里经常会出现路障、锥桶这些障碍物它们的共同特点是在二值图像中形成一个独立的小连通域与赛道区域分离或者部分重叠。我用的判断方法是对二值图做连通域标记两遍扫描法统计每个连通域的面积和包围盒宽高比。面积明显小于赛道区域、宽高比在一定范围内的连通域就标记为可疑障碍物。然后以该连通域的重心坐标作为障碍物位置结合它出现在边界的哪一侧决定绕行方向。这个方法虽然朴素但在实际比赛中足够用。相比之下如果试图用机器学习或者模板匹配去识别障碍物的具体形状在MCU上算力根本吃不消。八邻域连通域标记天生就是轻量级的面积、周长、宽高比这些几何特征都是现成的拿来即用。5. 实测中的异常现象与排查思路5.1 边界毛刺、跳变与断裂的形成原因我在调车时遇到的第一个棘手问题就是毛刺。表现是追踪到的边界线上有大量细小的锯齿状凸起整条边界看起来像一把锯子。排查下来主要有两个原因一是二值化阈值偏低导致背景里的浅色纹理被误判成赛道二是图像本身有传感器噪声在灰度接近阈值的区域频繁翻转。解决办法是提高二值化阈值、或者对灰度图先做一次3×3均值滤波均值滤波可以用移位运算优化成本很低。跳变和断裂则是更麻烦的问题。跳变通常出现在逆光或者强光反射场景——某一段赛道反光后灰度值骤增二值化把那一块全判成白色边界点一下子跳出几十个像素。断裂往往是因为阴影把赛道切成好几段追踪到阴影边缘时找不到下一个赛道像素而提前终止。这两种情况我最终的解决方案是放弃单一固定阈值改用动态阈值——每行根据该行的灰度均值动态调整二值化阈值能明显缓解光照不均引起的边界问题。5.2 八邻域追踪死循环的排查全过程有一次调试时程序在特定赛道位置卡死串口打印显示边界追踪一直没有返回。我第一反应是终止条件出了问题。仔细检查后发现问题出在方向表的定义上我用的是dir_dx[8] {1, 0, -1, 0, 1, 1, -1, -1}这种非顺时针顺序导致搜索方向的优先级在特定边界形状下反复横跳算法在两个像素点之间来回切换永远走不到终止条件。排查方法其实不复杂在追踪循环里加一个静态计数器每轮循环自增超过500就强制退出并打印当前坐标和方向索引。把这几个变量通过串口发到上位机你就能清楚看到算法卡在哪一步、为什么绕圈。我还习惯在每次强制退出时保存当时的二值图像数据方便离线复现。那次之后我给自己定了个规矩任何涉及邻域搜索的循环第一版代码就必须带最大迭代次数保护不能等出了问题再加。5.3 从图像到实车的调试步骤建议最后分享一套我自己实践的调试路径按顺序走能节省大量时间先把摄像头图像用上位机实时显示出来确认二值化效果。这是第一步也是最重要的一步图像不对后面全白搭。离线跑通八邻域追踪录一段赛道视频在电脑上逐帧跑追踪算法把边界点叠加在原图上输出。这一步能快速发现方向表、起点选择的问题。上单片机跑实时版本用串口把追踪的边界点数和起止坐标发出来和离线结果对比检查嵌入式移植是否引入了问题。最后才上赛道整车调试。先低速跑直道再逐步加速、加弯道、加元素。很多同学一上来就直接把车放赛道上调出了状况根本不知道是图像处理的问题还是控制的问题。先用上位机把图像层的问题解决掉再谈控制层的调参这是最稳妥的顺序。最后再说一个细节。调八邻域追踪时我强烈建议把追踪路径用上位机画出来哪怕只是把边界点的坐标点用不同颜色标在原图上。眼见为实这四个字在调智能车图像算法时比任何理论分析都管用。看得见边界在哪才能改得动算法。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询