
简介这份资源是全国大学生智能汽车大赛百度智慧交通组的国二获奖方案完整资料包面向计算机、人工智能、自动化、通信工程、电子信息等专业的高校学生与科研从业者可用于竞赛复盘、课程设计、毕业设计或项目立项演示。压缩包共220个文件约174.67MB以66个Python脚本、58个C头文件、17个模型文件与17个参数文件为核心辅以CMake构建脚本、Markdown说明文档、XML配置及Shell运行脚本覆盖感知、决策、控制等模块的代码与模型权重。方案含完整源码、项目说明与设计文档代码经过测试可正常运行能帮助读者理解智慧交通赛题的整体架构与实现思路也可在此基础上修改扩展功能。目前已有43人学习关注适合具备一定编程基础、希望借鉴国赛级方案或进阶学习智能车开发的同学参考使用。1. 智能车竞赛百度智慧交通组从国二方案里拆出可复现的工程骨架很多人第一次接触全国大学生智能汽车大赛的百度智慧交通组是被“国二方案”这四个字吸引的——觉得拿到一份获奖源码照着跑一遍就能出成绩。实际拆开看真正值钱的不是某个模型权重而是整套工程骨架感知怎么切、决策怎么兜底、控制怎么调参、日志怎么复盘。这份方案能拿国二靠的是每个环节都留了可解释的余量而不是某一处玄学调参。这篇文章面向准备参赛或正在搭车的同学把方案里的模块边界、参数含义、复现路径和踩坑点讲清楚让你拿到源码后知道先动哪里、后动哪里而不是一上来就改模型。2. 方案整体架构感知、决策、控制三层怎么切2.1 三层架构的职责边界与数据流百度智慧交通组的赛道通常包含车道线、交通标志、障碍物和路口场景车模平台是阿克曼转向的缩比车。方案把系统切成三层感知层负责从摄像头图像里提取车道线、标志和障碍物决策层根据感知结果和当前任务状态决定直行、转向、停车还是绕行控制层把决策输出转成舵机角度和电机占空比。三层之间用固定频率的消息传递感知跑在 20~30 FPS决策和控制跑在 50 Hz。这个频率差是刻意设计的感知不需要太快因为图像处理本身有延迟控制必须快否则舵机响应跟不上。常见做法是用一个共享内存或轻量消息队列把感知结果缓存起来控制层每次取最新一帧而不是等感知同步返回。提示三层频率不要设成一样。感知和控制同频会导致控制层被图像处理阻塞车在高速段会画龙。2.2 目录结构与模块划分拿到源码后先看目录典型的划分是这样的smartcar_baidu/ ├── perception/ # 感知模块 │ ├── lane_detect.py # 车道线检测 │ ├── sign_detect.py # 交通标志识别 │ └── obstacle.py # 障碍物检测 ├── decision/ # 决策模块 │ ├── state_machine.py # 任务状态机 │ └── planner.py # 局部路径规划 ├── control/ # 控制模块 │ ├── steering.py # 舵机控制 │ └── throttle.py # 速度控制 ├── config/ # 参数配置 │ └── params.yaml # 所有可调参数 ├── tools/ # 调试工具 │ ├── record.py # 数据录制 │ └── replay.py # 离线回放 └── main.py # 主入口这个划分的好处是每个模块可以单独跑。调试车道线时只启动 perception用录好的视频回放不用每次都把车推上赛道。我一般会先把 tools/replay.py 跑通确认能加载录制数据并驱动感知模块再动其他部分。2.3 主循环的启动顺序与线程模型主入口的启动顺序很关键。正确顺序是先加载配置再初始化硬件接口然后启动感知线程最后启动控制线程。如果先启动控制线程它会在一开始读到空的感知结果可能触发急停或乱转。# main.py 主循环骨架 import threading from config.loader import load_params from perception.lane_detect import LaneDetector from control.steering import SteeringController def main(): params load_params(config/params.yaml) lane_detector LaneDetector(params[perception]) steering SteeringController(params[control]) # 感知线程持续更新共享结果 perception_thread threading.Thread( targetlane_detector.run_loop, daemonTrue ) perception_thread.start() # 控制线程以固定频率读取最新感知结果 steering.run_loop(freq50) if __name__ __main__: main()逻辑说明感知线程用 daemon 模式启动主线程退出时自动结束。控制线程用固定频率循环每次读取感知模块的最新结果。参数说明freq50是控制频率对应 20ms 周期params[perception]里包含图像尺寸、ROI 区域、阈值等params[control]里包含舵机中值、比例系数、积分限幅等。3. 感知层落地车道线与标志识别的可复现步骤3.1 车道线检测的预处理与 ROI 裁剪车道线检测最容易翻车的地方是预处理。赛道光照变化大直接对整张图做阈值分割阴影和反光会被当成车道线。方案里的做法是先转灰度再做自适应直方图均衡然后裁剪 ROI。import cv2 import numpy as np def preprocess(frame, roi_top0.4, roi_bottom0.9): 车道线预处理灰度 - 均衡 - ROI 裁剪 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 自适应均衡应对光照不均 clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) equalized clahe.apply(gray) # 裁剪 ROI只保留赛道区域 h, w equalized.shape roi equalized[int(h * roi_top):int(h * roi_bottom), :] return roi逻辑说明clipLimit2.0控制对比度增强幅度太大噪声会被放大tileGridSize(8,8)是分块大小图像分辨率高时可以调大。roi_top0.4表示从图像 40% 高度开始裁剪去掉天空和远处干扰roi_bottom0.9去掉车头遮挡部分。这两个参数要根据摄像头安装高度和俯仰角调装得低就往下调。3.2 阈值分割与车道线拟合预处理之后用颜色阈值或边缘检测提取车道线像素再用透视变换做鸟瞰图最后用多项式拟合。方案里用的是 HSV 颜色阈值加滑动窗口拟合。def detect_lane(roi): HSV 阈值 滑动窗口拟合车道线 hsv cv2.cvtColor(roi, cv2.COLOR_GRAY2BGR) hsv cv2.cvtColor(hsv, cv2.COLOR_BGR2HSV) # 白色车道线阈值范围 lower np.array([0, 0, 200]) upper np.array([180, 30, 255]) mask cv2.inRange(hsv, lower, upper) # 滑动窗口拟合 histogram np.sum(mask[mask.shape[0]//2:, :], axis0) midpoint histogram.shape[0] // 2 left_base np.argmax(histogram[:midpoint]) right_base np.argmax(histogram[midpoint:]) midpoint return left_base, right_base, mask逻辑说明HSV 阈值里lower[0,0,200]到upper[180,30,255]是白色范围饱和度低、亮度高。如果赛道线是黄色要把色相范围改到 20~35。滑动窗口的直方图取图像下半部分统计因为远处车道线像素少容易被噪声淹没。left_base和right_base是左右车道线的起始横坐标后续拟合以此为起点。3.3 交通标志识别的模型选型与推理交通标志识别方案里用的是轻量分类网络输入 32x32 或 48x48输出类别数根据赛道标志种类定。选型理由是缩比车算力有限大模型跑不动而且标志识别不需要太高的空间分辨率。import numpy as np def classify_sign(model, roi_img, input_size32): 交通标志分类推理 img cv2.resize(roi_img, (input_size, input_size)) img img.astype(np.float32) / 255.0 img np.expand_dims(img, axis0) # 模型输出为各类别概率 probs model.run(img)[0] class_id int(np.argmax(probs)) confidence float(probs[class_id]) return class_id, confidence逻辑说明输入归一化到 0~1模型输出用 softmax 后的概率。confidence低于阈值时应该丢弃不要硬分类。常见阈值设 0.6~0.7太低会误检太高会漏检。如果模型是 ONNX 格式用 onnxruntime 加载如果是 TFLite用 interpreter。方案里两种都支持通过配置切换。4. 决策与控制状态机与舵机参数的调试方法4.1 任务状态机的状态划分与跳转条件决策层用状态机管理任务流程典型状态包括巡线、识别标志、路口等待、绕行障碍、停车。状态跳转条件要写得明确不能靠模糊判断。当前状态触发条件下一状态动作巡线检测到停止标志停车减速至 0巡线检测到障碍物绕行偏移路径巡线到达路口路口等待减速观察路口等待等待超时或绿灯巡线恢复巡线绕行障碍物消失巡线回正路径状态机的实现用字典映射每个状态对应一个处理函数。跳转条件用感知结果和计时器组合判断。比如路口等待状态要设超时防止感知不到信号时卡死。4.2 舵机控制的 PD 参数整定舵机控制用 PD 控制P 负责响应偏差D 负责抑制震荡。参数整定顺序是先调 P 再调 DP 从小往大加加到车能跟上车道线但开始轻微震荡然后加 D 抑制震荡。class SteeringController: def __init__(self, params): self.kp params[kp] # 比例系数 self.kd params[kd] # 微分系数 self.center params[center] # 舵机中值 self.prev_error 0.0 def compute(self, lane_center, target_center): error target_center - lane_center derivative error - self.prev_error output self.kp * error self.kd * derivative self.prev_error error # 限幅防止舵机打死 output max(-1.0, min(1.0, output)) return self.center output * 45 # 映射到舵机角度逻辑说明error是车道中心与目标中心的偏差derivative是偏差变化率。kp典型值 0.3~0.8kd典型值 0.05~0.2具体看车速和赛道曲率。center是舵机中值一般在 90 度附近装车时用调试工具测。输出限幅到 ±1.0 再映射到 ±45 度防止舵机堵转。4.3 速度控制的实现与弯道减速策略速度控制用增量式 PI直道加速弯道减速。弯道减速的触发条件是车道线曲率或舵机输出幅度。class ThrottleController: def __init__(self, params): self.kp params[kp] self.ki params[ki] self.base_speed params[base_speed] self.integral 0.0 def compute(self, target_speed, current_speed, steering_output): # 弯道减速舵机输出越大目标速度越低 curve_factor 1.0 - min(abs(steering_output) / 45.0, 0.6) adjusted_target target_speed * curve_factor error adjusted_target - current_speed self.integral error self.integral max(-10, min(10, self.integral)) output self.kp * error self.ki * self.integral return max(0.0, min(1.0, output))逻辑说明curve_factor根据舵机输出幅度降低目标速度最大降 60%。integral限幅防止积分饱和。kp和ki典型值 0.5 和 0.1先调 kp 让速度能跟上再加 ki 消除稳态误差。如果车在直道抖动说明 kp 太大或 ki 太小。5. 避坑与排查国二方案复现时最容易翻车的 5 个点5.1 摄像头曝光导致车道线时有时无现象车在赛道某一段能巡线换一段就丢线回放视频发现图像过曝或过暗。原因是摄像头自动曝光在光照变化时响应慢或者曝光值被锁死在错误档位。解决方法是关掉自动曝光手动设固定值或者用自适应均衡补偿。我一般会在赛前用灰卡测一次曝光把值写进配置。5.2 舵机中值偏移导致直道画龙现象车在直道左右摆幅度不大但频率高。原因是舵机中值没校准或者机械安装有偏差。解决方法是把车放在直道上手动给舵机中值信号看车轮是否回正。如果回不正先调机械连杆再调软件中值。中值偏差超过 5 度PD 参数再准也救不回来。5.3 感知和控制频率不匹配导致延迟累积现象车在高速段反应迟钝看到弯道时已经冲出去。原因是感知帧率低控制层每次读到的都是旧数据。解决方法是把感知结果加时间戳控制层只取最近 50ms 内的结果超时就用预测值兜底。另外感知 ROI 不要裁太小否则远处车道线看不到等看到时已经来不及。5.4 状态机跳转条件太敏感导致状态抖动现象车在路口反复切换等待和巡线状态或者绕行时来回摆。原因是跳转条件用了瞬时感知结果没有做滤波。解决方法是对感知结果做连续 N 帧确认比如连续 3 帧检测到停止标志才跳转。N 取 3~5太小会抖动太大会漏检。5.5 参数配置散落在代码里导致调参混乱现象改了一个参数忘了另一个地方还有同名参数结果行为不一致。原因是参数硬编码在多个文件里。解决方法是所有可调参数集中到 params.yaml代码里只读配置不写默认值。调参时只改 yaml不改代码。我习惯在 yaml 里给每个参数加注释写清楚单位和典型范围。6. 进阶技巧用离线回放把调参效率提上去6.1 数据录制与回放的工具链实车调参最大的问题是每次改完都要推车上赛道一天跑不了几轮。方案里带了 tools/record.py 和 tools/replay.py可以把摄像头画面、感知结果、控制输出一起录下来离线回放。回放时感知模块读录制的图像控制模块正常跑但舵机和电机不输出只记录结果。这样改一次参数几秒钟就能看完整段赛道的表现。# tools/replay.py 回放骨架 import cv2 import yaml from perception.lane_detect import LaneDetector from control.steering import SteeringController def replay(record_path, params_path): params yaml.safe_load(open(params_path)) detector LaneDetector(params[perception]) steering SteeringController(params[control]) cap cv2.VideoCapture(record_path) while cap.isOpened(): ret, frame cap.read() if not ret: break lane_result detector.process(frame) steer_angle steering.compute( lane_result[center], params[control][target_center] ) # 只记录不输出到硬件 print(fsteer{steer_angle:.2f}) if __name__ __main__: replay(data/record_01.mp4, config/params.yaml)逻辑说明回放时用 VideoCapture 逐帧读取感知和控制逻辑与实车一致只是不驱动硬件。print可以换成写日志文件方便对比不同参数下的输出曲线。参数说明record_path是录制视频路径params_path是参数文件路径。回放速度可以调用cap.set(cv2.CAP_PROP_POS_FRAMES, frame_id)跳帧。6.2 用回放对比不同参数的效果回放的价值在于可以快速对比。我一般会准备三组参数保守组、激进组、当前组分别回放同一段录制看舵机输出曲线和车道线偏差。保守组 kp 小、kd 大车稳但反应慢激进组 kp 大、kd 小反应快但容易震荡。对比之后取中间值再实车微调。参数组kpkd直道表现弯道表现保守0.30.2稳轻微滞后入弯慢出弯稳激进0.80.05轻微震荡入弯快出弯摆折中0.50.12稳入弯适中出弯稳6.3 把回放结果转成可量化的指标光看曲线还不够要把表现转成数字。常用指标有两个横向偏差均方根和舵机输出变化率。横向偏差均方根越小巡线越准舵机输出变化率越小车越稳。回放时把这两个指标算出来不同参数组直接比数字。import numpy as np def evaluate(errors, steer_outputs): 计算巡线精度和舵机平滑度 rmse np.sqrt(np.mean(np.square(errors))) steer_jerk np.mean(np.abs(np.diff(steer_outputs))) return {rmse: rmse, steer_jerk: steer_jerk}逻辑说明errors是每帧的横向偏差steer_outputs是每帧舵机输出。rmse反映整体精度steer_jerk反映平滑度。两个指标要一起看rmse 小但 jerk 大说明车在抖jerk 小但 rmse 大说明车反应慢。目标是把 rmse 控制在 5 像素以内jerk 控制在 2 度以内。6.4 我踩过的坑和现在的习惯最早调参时我只看实车表现改一个参数跑一圈一天下来跑不了 20 圈效率极低。后来把回放工具用起来先在离线数据上筛参数筛到差不多再实车验证效率翻了三四倍。现在的习惯是每次实车跑之前先录一段数据跑完之后回放分析确认问题出在感知还是控制再决定改哪里。这个习惯让我少走了很多弯路也避免了在赛场上临时改参数翻车。希望帮到你。本文还有配套的精品资源点击获取