项目档案 / 赛事已结束 · 华北赛区第 7 名
CongCar:从“能跑”到稳定高速的改进记录
第二十一届全国大学生智能汽车竞赛华北赛区第 7 名。围绕 RK3588 智能车的实时性、路径复用、场景切换与行人避让,记录赛道问题推动的一次次工程调整。
- 智能车
- RK3588
- 实时系统
- 控制
CongCar 的目标不是展示一次离线推理,而是在 RK3588 上持续完成采集、语义分割、目标检测、路径规划和底盘控制。车辆一旦开始运动,平均帧率之外的许多问题都会变得重要:旧帧是否排队、场景是否抖动切换、路径算法能否复用、速度变化是否连续,以及异常时能否安全停下。
项目的改进过程也因此不是简单增加功能,而是不断缩短“看到赛道”到“控制车辆”之间的链路。
在第二十一届全国大学生智能汽车竞赛中,CongCar 最终获得华北赛区第 7 名。比赛已经结束,这篇记录也从“持续迭代的开发日志”变成一次以赛道结果为落点的项目复盘:回看哪些设计真正影响了车辆行为,哪些问题仍值得在下一次项目里继续解决。
项目边界与个人贡献
CongCar 是团队共同迭代的整车项目,提交历史中包含多位成员的分割、检测、场景与控制工作。本文记录的是我参与和持续跟踪的整体工程过程,不把所有模块都写成个人从零完成。
我近期重点参与的是行人场景的参数与上下文逻辑、车体中心偏移对风险判断的影响,以及这些改动与公共路径跟随模块的配合。多线程流水线、NPU 推理、岔路和其他场景的演进,则以团队仓库的实现与提交记录为依据。
这一区分对项目复盘不是客套。只有明确是谁修改了什么、在哪种条件下验证,后来的人才能判断一条经验是否可复现。
先把实时任务拆开
早期把推理、控制和显示放在相近的执行路径中,任何一个阶段变慢都会拖住整条流水线。后续架构将任务拆为采集、分割、检测、控制和显示线程,并通过共享结果传递最新状态。
这里最重要的选择是:队列满时丢弃旧帧。
对录像程序而言,每一帧都处理完可能更重要;对运动中的智能车而言,迟到的完整信息通常不如当前的不完整信息。实时性优化的目标因此不是让 UI 更顺滑,而是降低控制收到感知结果时的“信息年龄”。
语义分割与目标检测被分配到独立 NPU 核心,分割侧还尝试过双引擎交替推理。测试又表明,并行并不自动等于更快:当内存搬运、运行时调度或控制消费速度成为瓶颈时,增加引擎只会让系统更复杂。因此版本中也出现过临时回到单核分割、先保证整车以 1.0 m/s 跑完的选择。
这里最容易产生误导的指标是模型推理 FPS。即使分割模型单独跑得更快,只要控制线程拿到的仍是旧帧,车辆就不会更早作出反应。端到端需要同时记录:
- 摄像头产生新帧的频率;
- 分割与检测各自完成的频率;
- 最新结果被控制线程消费时的时间差;
- 串口控制指令真正发出的频率。
因此,“多线程了吗”不能只看线程数量。更关键的是热路径上是否存在等待,旧结果是否会排队,以及控制是否永远优先读取最新状态。

一次普通巡线场景的现场界面。画面同时保留了前方感知、鸟瞰路径、速度与模型性能信息;其中模型 FPS 只能说明局部计算速度,不能替代摄像头输入频率或端到端控制时效。
从重复巡线到统一 PathFollower
随着普通巡线、车区、行人等场景加入,各场景如果各自维护一套中线提取和转向控制,参数会逐渐分叉,修复也难以同步。
项目后来抽出 PathFollower,统一处理鸟瞰路径、中线、前视点和转向控制。场景模块只负责改变约束,例如屏蔽不可通行区域、选择岔路保留侧或决定停车条件。
这次重构带来的收益不只是减少重复代码。它让场景切换可以共享同一套控制状态,从而避免刚切换场景时转向量突然跳变。配合统一缓冲和防抖,检测结果短暂闪烁不再立即触发行为翻转。
公共模块也带来新的风险:一个修改可能同时影响多个场景。重构后验证不能只跑普通巡线,还要逐个检查车区、行人和岔路场景是否继承了相同的坐标、速度与转向约定。复用降低了代码重复,却提高了回归测试的重要性。
岔路处理:一次实现、回退与重做
岔路最初通过直接修改语义分割掩码来抹除某一分支。实现很快,却连续出现提交后回退:原地修改影响了其他消费者,分支宽度变化也让固定切除不够稳定。
后续方案把岔路处理移到鸟瞰坐标,使用等宽切除并将保留方向做成配置。鸟瞰图中的距离和车体宽度更接近实际尺度,规则也不再依赖透视画面中“近处宽、远处窄”的像素形状。
这段历史很典型:回退不是开发失败,而是在真实赛道上及时否定了错误抽象。比保住一次提交更重要的是保住可控行为。
原地修改掩码还有一个共享状态问题:分割结果不仅供路径规划使用,也可能被显示与其他场景读取。当某个场景直接改写公共掩码,调试画面和后续消费者看到的就不再是模型原始输出,问题来源会变得模糊。把场景处理限制在自己的工作副本或鸟瞰路径层,虽然多了一步数据管理,却保留了可观察性。
速度提高后,原本不明显的问题会被放大
项目记录中,车辆速度从 0.7 m/s、1.0 m/s 逐步提高到 1.5 m/s。速度提升不是只修改一个上限参数。更短的决策时间会同时放大分割抖动、控制滞后、弯道超调和场景误判。
对应的改进包括:
- 将弯道降速系数做成可配置参数;
- 为盲开速度增加衰减;
- 对加速过程增加缓冲,避免速度阶跃;
- 扩展鸟瞰图宽度,给路径选择保留更多侧向信息;
- 把模型、类别、颜色和场景映射从硬编码迁移到 JSON 配置。
配置化并不是为了“看起来更工程化”,而是为了让赛道试验能形成短反馈环:修改参数、运行、观察,再回写一组可复现设置。
速度提升应被看作系统压力测试,而不是单一成绩。从 0.7 m/s 到 1.5 m/s,单位时间内可用于修正的帧数减少,同样的控制延迟会对应更长的实际行驶距离。低速时只是轻微摆动的问题,高速时可能变成越线;低速时可以忽略的一帧误检,高速时可能触发不必要的急转。
因此每次提速都应重新检查感知延迟、最大转向变化、弯道速度和失效行为,而不能沿用“低速能跑完,所以高速只要提高 PWM”的推断。
行人场景:检测到人不等于立刻停车
行人避让是我重点参与改进的部分。简单规则“检测到人就停车”在实际画面中并不够用:远处目标、相邻区域目标和短暂误检都可能触发不必要的停车;另一方面,只看单帧位置又可能在真正有风险时反应太慢。
改进后的场景引入了上下文状态,并综合考虑:
- 行人是否进入车辆规划路径;
- 目标距离与风险区域;
- 连续检测和消失的时间;
- 停车后何时允许恢复;
- 车体中心相对图像中心的实际偏移。
车体中心偏移尤其容易被忽略。摄像头安装位置、裁剪区域和鸟瞰变换会让“图像中心”不等于“车辆将经过的位置”。把偏移比例参数化后,风险判断才能与真实车体轨迹对齐。
第一次规则为什么不够
最直接的实现是设置一个检测框区域:行人进入区域就停车,离开就恢复。它的问题不是逻辑错误,而是缺少时间和路径上下文。
- 检测框在阈值附近抖动时,车辆会反复停车与启动;
- 行人虽然接近,但位于车辆不会经过的一侧;
- 短暂漏检会被误认为道路已经清空;
- 车辆已经停车后,恢复条件与触发条件完全对称,容易过早起步。
这些问题说明,“是否看到行人”与“是否存在碰撞风险”不是同一个变量。
上下文状态解决了什么
改进后的逻辑保留最近一段时间的场景状态,把进入风险、确认停车和允许恢复拆开。触发可以更敏感,恢复则要求更稳定;短时漏检不会立即清空风险,远离规划路径的目标也不会拥有同样权重。
这种不对称是安全逻辑的核心:停车多一次主要损失成绩,错误恢复一次可能造成碰撞。参数选择因此不能只优化流畅度,而要显式说明更偏向哪一类错误。
如何验证,而不是只说“看起来更好”
行人场景至少需要覆盖几组重复测试:
| 测试情形 | 期望行为 |
|---|---|
| 行人从路径侧面快速经过 | 风险区内停车,离开并稳定确认后恢复 |
| 行人在画面边缘但不进入规划路径 | 不因单纯检测到目标而停车 |
| 行人短时被遮挡或漏检 | 不立即恢复行驶 |
| 检测框在阈值附近抖动 | 场景状态不高频切换 |
| 无行人但存在偶发误检 | 通过连续性与区域条件过滤 |
目前的提交记录能证明逻辑和参数经历了多轮修改,但还缺少标准化测试次数、误停车率和恢复时间分布。把这些指标补齐,会比继续增加经验阈值更接近研究型项目。
工程上的结论
这套系统的提交历史中既有新功能,也有重构、临时关闭、回退和重新实现。它更接近真实工程的样子:性能提升会暴露控制问题,场景增加会迫使公共能力抽象,硬件并行需要用端到端延迟验证,而不是只看单个模型的推理时间。
判断一次改进是否有效,最终要回到车辆行为:
- 控制使用的是不是足够新的感知结果;
- 同一组参数能否重复跑出相近结果;
- 场景切换是否连续;
- 误检和短时失效是否会触发危险动作;
- 速度提高后,安全边界是否仍然明确。
仍需改进的地方
现在的系统已经能形成完整闭环,但“能跑完”与“可证明地稳定”仍有距离。下一阶段值得补充:
- 对关键线程统一记录时间戳,计算感知到控制的端到端延迟;
- 保存典型失败片段与对应配置,建立可回放的回归测试;
- 为场景切换建立状态图和超时约束,减少隐藏条件;
- 将速度分档测试与赛道通过率、越线次数、停车距离绑定;
- 明确软件停车、主动制动与硬件急停的能力边界。
如果这篇文章将来给导师阅读,我希望它展示的不是“项目功能很多”,而是我如何从现象建立假设、修改系统、承认失败,并把下一次验证设计得更清楚。
项目仓库:Normanchine/CongCar。