智能车视觉组工程复盘:走马观碑赛项的闭环调试与避坑指南 第21届智能车竞赛结束后笔者整理调试日志时发现真正让队伍止步于赛区二等奖的不是识别算法不够先进而是大量基础环节没有形成闭环。走马观碑赛项的难点并不在某个单独模块而在高速运动状态下图像采集、识别、决策和控制必须稳定配合。这篇文章以我们队伍的赛后复盘为基础记录从方案选型到现场调车的完整过程重点写哪些参数真正影响结果、哪些问题每次比赛都会出现以及下届队伍可以提前避免的坑。1. 从“走马观碑”赛项看视觉智能车的技术主线1.1 “走马观碑”到底考什么“走马观碑”原本形容骑马行进中还能看清碑文是一种极快的观察能力。放在智能车竞赛里这个赛项的核心场景是车模不能减速太多同时又要在赛道指定区域准确识别标牌或字符信息并根据识别结果完成后续动作。难点不在于单独跑得快也不在于单独识别得准而在于两者同时满足。车辆高速运动时摄像头画面会产生动态模糊、过曝或欠曝、赛道元素快速切换控制周期也会因为算法耗时被拉长。只要其中一个环节出现几十毫秒的异常整车就会偏离赛道或漏识别。在实际备赛过程中我们最容易犯的错误是把它当成“视觉识别项目”来做花大量时间训练模板忽略了机械、供电、控制周期这些基础因素。等到实车测试时才发现实验室里准确的识别在赛道上完全不稳。赛后复盘得出的第一个结论是走马观碑是一个系统工程问题不是单点算法问题。1.2 技术主线图像采集、识别、决策、控制的闭环整车的软件任务可以拆成一条链路图像采集摄像头按固定帧率输出图像主控读取并完成预处理。赛道与标识识别从图像中提取赛道边界、中线并在识别区找到目标字符或图形。决策根据当前赛道状态和目标识别结果决定是否减速、直行、转向或停车。控制将决策转换成舵机角度和电机转速。这条链路上的每部分都会消耗时间。我们在赛前用逻辑分析仪大致测过各环节耗时摄像头曝光一般在几毫秒到十几毫秒图像预处理和数据传输约几毫秒到几十毫秒识别算法则可能从几毫秒到上百毫秒取决于模板数量和图像尺寸。最终整个循环如果超过控制周期车模就会“一步一顿”出现转弯滞后。走马观碑对实时性的要求比普通电磁循迹高很多因为既要“看得见”又要“记得住”还要“反应得过来”。所以本文后面的所有内容都是围绕同一个目标在保证识别可靠的前提下把整车闭环延迟压到可控范围并把现场不稳定因素提前排除。1.3 复盘范围与阅读建议这篇文章不是官方赛题解析而是工程复盘。我们使用的主控是竞赛中常见的单片机平台传感器以灰度摄像头、编码器和 IMU 为主。如果你用的是其他平台代码不能直接拷贝但调试流程、参数影响和踩坑点可以复用。建议正在备赛的队伍重点关注硬件装配、参数整定和现场排查三章这三个部分最容易让一支看起来配置很高的车模在赛场上跑出意外成绩。2. 赛前方案选型为什么我们选择了“摄像头编码器陀螺仪”组合2.1 常用感知方案对比智能车竞赛里常见的感知方案可以归为几类。做选型时不能只看检测精度还要看实时性、开发成本和赛项匹配度。方案优势劣势适用场景电磁巡线抗光照干扰计算量小实时性高只能感知路径无法识别字符或图形纯循迹、基础竞速灰度摄像头分辨率低但帧率高适合高速场景对曝光敏感需要处理光照变化赛道边界提取、简单符号识别彩色摄像头颜色信息丰富便于识别色块和图形数据量大处理耗时受光线影响更明显需要颜色区分的视觉组摄像头辅助定位通过二维码、色标或反射点辅助定位需要额外布置标记赛道改动后要重标定固定场景下的精确停车、识别区定位我们最终选择“灰度摄像头编码器IMU”的组合原因很直接赛项要求在行进过程中识别目标信息电磁方案天然不具备能力彩色摄像头在当时的算力条件下很难稳定跑满帧率灰度摄像头虽然信息量少但帧率高、处理快配合固定安装角度和良好曝光足以完成赛道边界和标识识别。2.2 主控与传感器选型注意点主控选择主要看三点图像处理能力、外设接口数量和团队熟悉度。常见的 TC264、TC377、STM32H7 和 i.MX RT 系列都可以完成这类任务。我们队对 TC264 比较熟悉所以最后使用它作为主控。要注意的是单片机的图像处理能力和 PC 完全不同不要在 PC 上写完识别算法就直接移植要提前确认内存占用和单帧耗时。传感器方面我们使用了灰度摄像头分辨率设置为 188×120 左右这个分辨率对赛道边界足够也能降低二值化耗时。编码器安装在驱动电机输出轴上用于测速和速度闭环。IMU用于陀螺仪角速度辅助转向特别是在高速进入弯道时可以提前感知车模姿态变化。选型时还有一个容易忽略的点传感器之间的时间对齐。摄像头图像是“某一瞬间”的画面编码器和 IMU 数据是“持续更新”的值如果直接用当前编码器值去匹配上一帧图像控制误差会增大。我们的做法是在摄像头场中断触发时锁存一次编码器计数并读取一次 IMU 数据让这一帧图像和同一时刻的速度、姿态绑定。2.3 为什么不能只靠“离线识别”备赛初期我们尝试过在 PC 上对图像做高精度识别效果很好但移植到单片机后速度直线下降。原因是模板匹配和特征提取的耗时没有控制住。走马观碑场景要求在车辆运动过程中识别目标也就意味着每一帧图像只有一次机会错过之后车可能已经越过识别区。赛前我们最终确定了一条原则识别算法必须保证单帧处理时间低于控制周期如果做不到就把识别区目标速度降低用多帧投票换取可靠率。这个取舍很重要。与其追求“每一帧都识别”不如把识别区变成一个小状态机车辆识别到进入区域后主动将目标速度从高速降到低速连续抓取几帧进行识别然后对结果做投票。这样虽然平均速度降低了但整场比赛的稳定性更高。很多队伍就是因为不愿意在识别区减速导致漏识别后被判失败。3. 硬件与机械装配的细节复盘3.1 摄像头安装高度、俯仰角与前瞻距离摄像头安装是最能体现“差之毫厘谬以千里”的地方。如果摄像头装得太低视野只能覆盖车头前方很近的区域高速时转向根本来不及如果装得太高虽然看得远但图像抖动会被放大弯道时容易丢失近处的赛道边界。我们最终把摄像头高度控制在 20cm 到 30cm 之间俯仰角在 15 度到 20 度之间让视野近端和远端都有赛道信息。这里的具体数值不是标准不同车模和赛道比例需要重新标定。前瞻距离是另一个核心参数。它的含义是摄像头图像远端对应的实际地面距离。相同分辨率下前瞻越远车辆能越早看到弯道但远端图像放大倍数大像素对应的实际距离变大边界识别精度会下降。我们的做法是先用一块白板在地面标记出 0.5m、1.0m、1.5m 三条线通过调整俯仰角让图像中的远端边界落在目标位置。高速方案可以选择 1.2m 到 1.5m但前提是机械刚性足够摄像头不会在急加速时抖动。3.2 重心、轮胎与机械刚性的影响视觉车的重心位置比电磁车更敏感。车模加速和急弯时重心如果偏前容易造成前轮负载过大如果偏后加速时车头会明显抬起导致摄像头视野突然变远。我们把电池尽量靠近车模中心偏后位置并使用扎带和魔术贴固定避免电池在急刹时移动。轮胎方面要检查胎面是否均匀着地。很多车模使用软质轮胎但新胎和旧胎的抓地力差别很大。比赛前一定要用酒精清洁胎面赛道上的灰尘和轮胎脱落的橡胶颗粒会让抓地力在几分钟内持续变化。机械结构上的螺丝也要定期检查摄像头支架使用尼龙件时尤其容易在长时间震动后松动。我们在现场发现过两次摄像头角度偏移都是支架螺丝松动导致后续直接用螺丝胶固定关键位置。3.3 供电、共地与图像干扰电机和大功率舵机是图像干扰的主要来源。现象是图像上出现周期性横纹或雪花点有时还伴随舵机抖动。这类问题不是算法能解决的需要从供电走线入手。我们的处理方式包括电机电源和主控电源分开走线避免大电流回流路径穿越摄像头信号线。摄像头、主控、电机驱动之间保证“可靠共地”地线不要形成环路。在电机两端并联吸收电容舵机电源入口增加滤波电容。摄像头数据线尽量使用短排线并且避开电机线和舵机线。这里要强调的是用示波器观察供电电压比反复调曝光参数更有效。我们曾经花了两天时间调整二值化阈值结果发现是舵机启动瞬间把摄像头电源拉低画面整体变暗。解决好供电后原来的二值化阈值甚至不需要改。3.4 线材整备与现场维护比赛现场最怕的不是代码 bug而是线材接触不良。我们在赛前把所有排线、电源线都做了标签并在接口位置点了一点热熔胶防止震动中脱落。现场维护时第一件事不是改参数而是检查每一根线是否牢固尤其是摄像头排线、编码器线和电池插头。这个习惯看起来非常基础却能避免大量“灵异故障”。4. 软件系统架构与关键模块实现4.1 用状态机管理不同赛道阶段走马观碑的赛道不会只有一种路况一般会包含直道、弯道、十字、坡道或识别区。我们不能用同一套参数跑全程否则会出现直道速度太慢、弯道转向太猛的问题。比较稳妥的做法是设计一个驾驶状态机让不同阶段使用不同控制策略。下面是一个简化的状态机设计只用于说明思路具体状态要根据赛题定义typedef enum { STATE_START 0, // 启动等待 STATE_RUNNING, // 正常赛道行驶 STATE_APPROACH, // 接近识别区 STATE_RECOGNIZE, // 识别区低速识别 STATE_ACTION, // 根据识别结果执行动作 STATE_STOP // 停车 } CarState; CarState current_state STATE_START;状态切换条件要尽量简单避免因为识别抖动导致状态频繁跳变。比如从 RUNNING 切到 APPROACH可以同时依赖赛道元素标志和历史帧计数连续 5 帧判定进入识别区后再切换而不是看到一帧特征就切换。4.2 图像预处理与赛道中线提取图像处理的第一步是灰度化和二值化。灰度摄像头输出本身是灰度图二值化需要选择一个阈值把赛道和背景分开。固定阈值虽然在实验室好用但赛场的灯光和投影亮度变化会导致整幅图像亮度整体偏移。我们最终采用动态阈值方法统计当前帧图像的灰度直方图用大津法计算阈值。这个方法计算量不大但能明显提高光照变化下的稳定性。二值化之后需要从图像底部向上扫描找到每一行的赛道左边界和右边界然后计算中线。一个常见的坑是图像最底部几行会因为车头遮挡或视野过近出现大面积黑边直接扫描会得到错误边界。我们的做法是从图像底部向上找一个有效起始行跳过头几行和接近消失点处的不可靠区域。下面是一段示意代码用于说明中线提取的循环结构不是完整工程代码#define IMAGE_W 188 #define IMAGE_H 120 uint8_t binary[IMAGE_H][IMAGE_W]; int left_bound[IMAGE_H]; int right_bound[IMAGE_H]; int center_line[IMAGE_H]; int valid_rows 0; for (int row IMAGE_H - 20; row 20; row--) { int left -1; int right -1; for (int col 0; col IMAGE_W; col) { if (binary[row][col] 1) { left col; break; } } for (int col IMAGE_W - 1; col 0; col--) { if (binary[row][col] 1) { right col; break; } } if (left 0 right 0 right - left 5) { left_bound[row] left; right_bound[row] right; center_line[row] (left right) / 2; valid_rows; } }这段代码的实际问题是如果赛道元素不是简单黑底白线而是“白底黑线”二值化逻辑需要反过来左右边界搜索也可能把阴影当成赛道。所以在真实工程里建议先在 PC 上保存几帧图像把处理结果可视化确认边界提取的正确性再移植到单片机上。不要直接在车上调像素级逻辑效率太低。4.3 识别区目标识别多帧投票代替单帧判断走马观碑赛项最特殊的部分是车辆需要在运动过程中识别预设的目标信息。我们的方案是在识别区前提前减速然后连续采集多帧图像对目标区域做识别最后用多帧投票决定结果。这样可以避免单帧模糊、遮挡或反光造成的误判。识别流程可以分成四步定位通过赛道状态机判断已经进入识别区在图像中裁剪出目标区域。预处理对目标区域做缩放、灰度均衡或二值化统一送入识别函数。识别使用模板匹配或简单特征匹配输出候选结果和置信度。决策维护一个长度为奇数的结果队列比如 7 帧每帧得到一个候选结果最终取出现次数最多的结果作为最终输出。示意逻辑如下#define VOTE_FRAMES 7 char vote_buffer[VOTE_FRAMES]; int vote_index 0; char recognize_zone(uint8_t *roi, int w, int h) { // 返回候选结果字符例如 A、B或 0 表示无法识别 return match_template(roi, w, h); } void update_vote(char result) { vote_buffer[vote_index % VOTE_FRAMES] result; vote_index; } char get_vote_result(void) { int count[8] {0}; for (int i 0; i VOTE_FRAMES; i) { if (vote_buffer[i] ! 0) { count[vote_buffer[i] - A 1]; } } int max_count 0; char best 0; for (int i 1; i 4; i) { if (count[i] max_count) { max_count count[i]; best A i - 1; } } return best; }这段代码的关键不是模板匹配本身而是用历史帧结果对“单帧误判”做抑制。实际比赛中车辆通过识别区的时间可能只有几百毫秒7 帧投票在低速下是可行的。如果速度降不下来可以减少投票帧数改为“连续 3 帧同一结果即确认”。这里的取舍是帧数越多越稳定但需要识别区距离更长帧数越少反应越快但误判率上升。建议备赛时用录制的图像序列离线测试不同帧数下的准确率。4.4 转向控制与速度控制整车控制可以拆成两个环路转向环和速度环。转向环的输出是舵机角度速度环的输出是电机 PWM。转向控制最常用的是 PD 控制float error target_center - current_center; float diff error - last_error; float steering kp * error kd * diff; last_error error;error 可以取图像中线偏差也可以综合 IMU 角速度。实际调车时kp 决定入弯的快慢kd 决定转向的阻尼。kp 过大容易出现高频抖动kd 过大会导致转向反应迟钝。我们的初值一般设 kp 在 0.3 到 0.8kd 在 0.5 到 1.5但不同车模的舵机行程和机械结构差异很大必须以实车表现为准。速度控制使用 PI 控制float speed_error target_speed - current_speed; integral speed_error; if (integral INTEGRAL_LIMIT) integral INTEGRAL_LIMIT; if (integral -INTEGRAL_LIMIT) integral -INTEGRAL_LIMIT; float motor_pwm kp_speed * speed_error ki_speed * integral;在识别区和坡道目标速度要单独设定。我们会在进入识别区前将目标速度从 2.5m/s 降到 1.2m/s 左右具体值取决于识别区长度和摄像头帧率。速度降低后识别成功率明显提升代价是平均速度下降。这里不要贪快因为一次漏识别造成的罚时或判负远大于降速损失的时间。4.5 数据记录让系统可回溯赛后复盘最有价值的基础设施是数据记录。我们在车上加入了 SD 卡记录功能每 50ms 记录一帧包含时间戳、车速、舵机开度、当前状态、图像二值化结果摘要和识别结果的数据块。现场出问题时直接下载日志定位是哪一帧开始偏离、识别是否丢帧、控制量是否饱和。没有日志一切失败原因都只能靠猜。5. 参数整定与调试方法总结5.1 分级调试从静态到动态我们最开始的错误是直接在赛道上全速调试出了问题不知道是机械、图像还是控制。后来改成四级调试流程静态检查车模静止时检查摄像头画面是否清晰、二值化是否稳定、舵机左右行程是否一致。低速直道验证图像中线提取和速度闭环是否正常观察车模是否能沿直线稳定行驶。低速弯道逐步增加转向 PD观察入弯和出弯是否流畅。高速组合加入识别区、坡道等组合元素验证状态机切换和识别流程。每一级通过后再进入下一级。如果低速直道都走不稳就不应该继续调高速。很多队伍在紧张备赛时试图“一步到位”结果反而浪费更多时间。5.2 关键参数速查表下面这张表整理了我们调试过程中影响最大的参数以及参数调大调小时的表现。参数默认值只是参考不同车模和赛道环境需要重新标定。参数作用参考初值调大表现调小表现二值化阈值区分赛道和背景动态阈值赛道变细或断线背景噪点增多摄像头前瞻距离决定图像远端视野1.2m提前发现弯道但远端抖动大入弯反应慢转向比例系数 Kp决定转向强度0.5入弯快容易抖动弯道转不过去转向微分系数 Kd决定转向阻尼1.0转向钝高频抖动速度比例系数决定加速响应经验值响应快容易超调加速慢速度积分上限限制积分项略小于最大 PWM消除稳态误差但易过冲低速爬行无力识别区目标速度决定识别时的车速1.2m/s识别时间短通过时间过长投票帧数决定识别稳定度7更稳但需要更远识别区反应快但误判多这里的每一项都不能单独死调。例如调大前瞻距离后转向 Kp 可能需要同时调整因为远端误差变化率不同。调试时每次只改一个参数记录前后行车表现不要同时改三个参数否则无法判断是哪个改动产生了影响。5.3 用“失败样本库”复现问题智能车调试最怕“时好时坏”。如果同一段赛道有时能跑过有时会冲出说明存在未覆盖的不稳定因素。我们后来建立了一个失败样本库每次调试时只要车模出现异常就保存当时的图像序列、速度和控制量。修改算法后再跑同一段赛道用旧样本回归验证。这个习惯大大减少了“改完 A 问题出现 B 问题”的情况。建立失败样本库的关键是“能稳定复现”。如果一个问题没有固定触发条件就先收集多组数据找共同点。比如我们发现高速右弯时经常丢线回放图像后发现是远端灯光在弯道出口形成高亮区域导致二值化把赛道和背景混在一起。后来在图像处理里增加了针对高亮区域的抑制逻辑问题消失。如果没有日志回放这个原因可能永远找不到。5.4 学习环境与赛场环境的差异实验室和赛场环境的差异远比想象中大。赛场的灯光通常是顶部大面积灯带光线方向、亮度和色温都和实验室不同赛道表面可能有反光现场其他队伍的无线设备可能造成干扰。所以最晚在赛前一周就要按照预计赛场条件调整图像曝光和阈值策略。一个实用的做法是让程序支持两种模式实验室标定模式和赛场快速标定模式。赛场快速标定模式下发车前先让车模在起跑线上静止拍摄几帧图像程序自动计算当前环境的动态阈值和亮度补偿参数。这样即使现场光照变化也不用手动改代码。6. 现场犯规、掉线与失误的排查清单6.1 高频失误现象和处理方案比赛现场出现的问题通常集中在几个固定场景。下面这张表是我们队伍和其他队伍交流后整理的常见问题处理方式偏工程经验。问题现象可能原因检查方式处理建议发车后立刻冲出赛道摄像头没对准、阈值错误、舵机中值漂移检查图像画面和舵机行程重新标定中值确认二值化正常识别区没有识别结果车速太快、图像模糊、提前减速不够回放日志中识别帧降低识别区目标速度增加投票帧数运行中突然复位电压跌落、看门狗超时、程序异常检查供电波形和日志增加滤波电容检查死循环或内存越界图像花屏或横纹供电干扰、排线松动示波器看电源晃动线材重新走线短排线固定舵机左右抖动转向 Kp 过大、机械间隙大低速直道观察降低 Kp检查舵机连杆无线串口断开现场干扰、串口线松动重插线或更换模块备用有线串口重要数据走 SD 卡这些问题的共同特点是它们都不需要在算法层面做复杂修改大部分是工程约束没做好。6.2 现场三分钟调试顺序正式比赛前可以按固定顺序检查车辆避免遗漏。我们最终固定的顺序是检查电池电压和插头是否牢固。上电后确认指示灯状态和无线串口连接。查看摄像头图像是否清晰二值化是否正常。手工推动车模观察舵机是否跟随转向。缓慢加速测试观察速度闭环和转向是否稳定。检查所有螺丝、排线和轮胎清洁度。这个顺序总共大约三分钟。如果三分钟内发现问题优先解决机械和供电问题不要急着调识别参数。很多队伍在赛场上一紧张就反复修改 Kp 和阈值实际上车模本身已经存在机械松动。6.3 规则确认与备份比赛规则要提前确认尤其关于识别区、允许使用的传感器和停车方式。不要因为规则理解偏差导致被判无效。现场需要准备至少两套程序备份一套是“常用参数”另一套是“保守参数”当常用参数在赛场上不稳定时可以临时切换保守参数保证完赛率。保守参数的核心是降低目标速度、提高投票帧数、减少激进控制。7. 赛后复盘建议下届队伍优先做的五件事7.1 尽早搭建数据回放系统这是所有建议里投入产出比最高的一项。哪怕只是一个能保存图像和关键参数的简单模块也能让调试从“碰运气”变成“查证据”。建议在备赛第一周就把日志功能写入工程而不是等到比赛前才补。7.2 固定机械状态再调代码机械结构要尽量固定下来不要每天既改摄像头角度又改 PID。机械参数一旦变化之前所有调试结论都会失效。每周固定一天做机械检查和标定其他时间保持同一状态。7.3 把识别失败当成正常分支来设计走马观碑赛项里识别算法不可能保证 100% 成功。程序必须默认“这一帧可能识别失败”并设计回退方案。比如投票结果不明确时让车辆按保守策略继续行驶或停车而不是产生一个未定义状态。7.4 参数配置外置化把二值化阈值、转向 PID、目标速度、投票帧数等参数放到外部配置文件中而不是写死在代码里。现场调参时可以快速修改不需要重新编译。这个工程习惯能让调试效率提高不少。7.5 学会做减法比赛比的不是谁代码花哨而是谁在规定赛道内稳定完赛并更快。如果高速方案经常冲出赛道就降低目标速度如果识别模块过于复杂就简化识别流程。先把一个稳定方案跑完再逐步提升速度是最稳妥的备赛路线。这篇文章里的所有参数和代码都是工程思路不是标准答案。真正的走马观碑调试需要结合自己的车模、传感器和赛道环境重新验证。希望这篇赛后总结能帮助下一届队伍少走一些弯路把精力花在真正影响成绩的环节上。