资讯动态

Unity仿真环境下基于CNN的自动驾驶系统设计与实现

发布时间:2026/9/11 6:57:14 来源:尧图企业网站定制
简介面向自动驾驶算法学习者这份基于卷积神经网络的自动驾驶系统设计与实现源码包覆盖目标检测、路径规划、障碍物识别等核心模块适合高校学生与算法工程师结合代码理解系统原理。资源共一百二十四个文件以Python算法脚本、C#交互脚本、Unity场景与Prefab资源、材质文件、PNG图像及3D模型为主压缩包总大小约七十一点三五MB目录结构清晰可快速定位配置、数据与仿真场景。目前已有一百四十四人次浏览学习可用于课程设计或毕业设计参考。资源内提供了衔接训练与仿真环节的数据、配置和素材支撑从感知到决策的完整算法复现让学习者能够借助Unity仿真环境验证模型效果并调优。系统本身涵盖感知、决策、执行等自动驾驶核心层次对理解CNN在工程中的落地方式很有帮助。1. 从 Unity 工程配置看 CNN 自动驾驶仿真项目的切入点解压这份工程文件最先看到的是 ProjectSettings.asset、InputManager.asset、QualitySettings.asset 这一摞 Unity 配置文件而不是常见的train.py或requirements.txt。这说明《基于卷积神经网络的自动驾驶系统的设计与实现》并不是一个只在服务器上训练模型的项目而是把 CNN 放进 Unity 仿真环境里跑完整的感知-决策-执行闭环虚拟摄像头采集画面卷积网络输出转向和油门控制物理引擎反馈车辆姿态。对 5 年以上经验的工程师来说这类工程的价值在于它把深度学习和实时系统耦合在一起需要处理数据采集、模型训练、推理部署、仿真验证多个环节的边界问题。下面直接拆工程配置与代码讨论如何在 Unity 里把 CNN 自动驾驶落地。2. 自动驾驶系统在 Unity 仿真中的感知-决策-执行闭环设计自动驾驶系统架构通常分三层感知层采集环境信息决策层做路径规划和行为决策执行层把决策变成实际动作。在这个工程里这三层分别由 Unity 的 Camera、CNN 推理和物理轮组组件完成。而 ProjectSettings 里的那些 .asset 文件决定了这三层之间如何配置和衔接先把它们的作用理清后面调整模型才有据可依。2.1 ProjectSettings 下的 .asset 文件分别决定什么一个 Unity 工程的 ProjectSettings 文件夹里保存的是全局配置虚拟车辆的行为边界基本都在这里限定。InputManager.asset 管理键盘或方向盘的输入轴映射在采集训练数据阶段要把油门、刹车、转向分别绑定到 AxisCNN 的标签就是这些轴的实时值。TagManager.asset 为行人、车辆、路标定义 Tag 和 Layer目标检测训练用的标注框在仿真里可以直接引用这些 Tag 生成省去人工标注。NavMeshAreas.asset 用于全局路径规划定义可通行区域和移动代价相当于给决策层一个哪些路面能走的先验。QualitySettings.asset 则直接控制渲染质量阴影、抗锯齿、后处理等级越高模型推理能分到的 GPU 时间就越少。下面是我在同类 Unity 自动驾驶工程里常用的配置对应表。文件对应模块具体作用ProjectSettings.asset通用设置脚本运行时版本、产品名称、活动输入处理InputManager.asset数据采集定义转向、油门、刹车 Axis 与按键映射QualitySettings.asset渲染性能控制阴影、抗锯齿、后处理决定推理帧率余量GraphicsSettings.asset渲染管线决定内置管线还是 URP影响相机纹理格式Physics2DSettings.asset执行层物理2D 冲突检测和刚体参数简化场景下使用NavMeshAreas.asset路径规划定义可行区域、路径代价、跳跃限制TagManager.asset感知标注为行人、车辆、交通标志分配 Tag 和 LayerUnityConnectSettings.asset联调关闭编辑器联网检查避免后台调用干扰运行这些配置里最容易忽略的是 InputManager.asset。训练数据有没有漂移、刹车响应是否线性都要先回来看轴映射。如果发现模型输出油门没有渐变先检查键盘轴是不是默认的数字量而不是慌忙改网络结构问题可能只出在输入映射上。2.2 感知层用 Camera 组件模拟车载传感器感知层在 Unity 里由多个 Camera 组件组成主摄像头一般放在挡风玻璃中部偏上LocalPosition 大约在(0, 1.4, 0)俯仰角向下 3°5°这样能同时看到前向道路和一部分路面边缘。FOV 建议设在 60°90°过窄看不到弯道出口过宽则画面两边畸变严重卷积网络需要花费额外的卷积核去抵消透视变形。渲染目标设为 RenderTexture分辨率根据后续模型输入决定常见做法是 640x320 或 320x160。如果要做雷达、激光雷达点云融合还需要加深度 Camera但这套工程从配置文件看是纯视觉方案。2.3 决策层CNN 输出连续控制量而不是分类标签很多实现把 CNN 最后的输出接成一个 softmax 分类直行、左转、右转、停车。实际跑起来会出现两个问题一是类别边界处的置信度抖动方向盘来回摆动二是分类结果没有量的含义无法表达弯道曲率大小。因此这套工程更适合做连续回归输出三个实数转向角Steer、油门Throttle、刹车Brake。转向角范围 [-1,1]油门范围 [0,1]刹车范围 [0,1]。这个设计直接对应 InputManager 里的 Axis也能和后面要写的 WheelCollider 参数一一绑定。2.4 执行层从控制量到轮速转角决策层的控制量不能直接改 Transform 的 rotation那样等于无视物理约束。常见做法是把车辆做成刚体加 WheelCollider 结构根据控制量设置轮子的转向角和驱动力矩。转向角需要乘以最大转向角油门乘以最大扭矩再交给物理引擎计算。下面这段脚本接收推理结果并作用到轮组。// 车辆控制脚本接收 CNN 推理结果并作用到物理轮组 using UnityEngine; public class CarControl : MonoBehaviour { public WheelCollider leftWheel; public WheelCollider rightWheel; public float maxSteerAngle 30f; public float maxTorque 500f; // 从推理模块拿到的归一化控制量 public void ApplyControl(float steer, float throttle, float brake) { // 转向角映射到 [-maxSteerAngle, maxSteerAngle] leftWheel.steerAngle steer * maxSteerAngle; rightWheel.steerAngle steer * maxSteerAngle; // 油门与刹车合并为扭矩刹车通过反向扭矩模拟 float torque Mathf.Clamp(throttle * maxTorque - brake * maxTorque, -maxTorque, maxTorque); leftWheel.motorTorque torque; rightWheel.motorTorque torque; } }代码里 steerAngle 的单位是角度motorTorque 的单位是牛顿·米。先乘 maxSteerAngle 再赋给轮子避免小幅度转向被物理引擎置零。刹车用负扭矩模拟优点是省去独立刹车状态缺点是急刹车时轮子容易抱死后续要做防抱死可以在 WheelCollider.rpm 低于阈值时将扭矩置零。这段脚本没有处理底盘重心实际车辆发飘时需要通过 Rigidbody.centerOfMass 把重心降低到车轮轴下方。3. CNN 模型设计卷积、池化、步长与填充在驾驶任务中的选择3.1 输入张量设计从相机纹理到训练样本Unity 实时渲染出的画面是 1920x1080 甚至更高直接作为网络输入会导致显存占用过高也不利于实时推理。我通常先把 RenderTexture 设置成 640x320再在预处理阶段裁剪为 320x160。为什么要保留 160 的高度车道线在纵向上的信息密度从近到远差异很大压缩到 80 像素高之后远端弯道信息会丢失。画面顶部约 15% 是天空底部约 5% 是引擎盖在录制前应从 RenderTexture 的 Viewport 裁掉。数据录制成 PNG 还是 Tensor 的二进制文件都可以关键是每帧图像需要和当时的控制标签同步保存常见做法是每帧导出一个 JSON包含 steer、throttle、brake 和时间戳。3.2 卷积核、池化、步长与填充的组合策略CNN 在图像处理上的核心是卷积核在局部区域提取模式池化层压缩空间尺寸步长决定每次滑动的距离填充决定边缘像素是否参与计算。在驾驶任务中我的选型逻辑是第一层用 5x5 卷积核、步长 2、padding2这样空间尺寸直接从 160x320 降到 80x160相当于做了一次内置下采样减少了后续计算量第二层继续用 5x5、步长 2第三层改用 3x3、步长 1、padding1保留小目标的细节。池化层放在第三层之后使用 2x2 最大池化。如果所有卷积层的步长都设为 1特征图过大训练慢且容易过拟合如果步长全部设为 2边缘车道线信息又丢得太快。下面是一组和常见端到端驾驶模型接近的卷积参数表。层核步长填充输入通道输出通道输出尺寸Conv15x52232480x160Conv25x522243640x80Conv33x311364840x80Pool2x220484820x40为什么没有全程使用池化层降采样最大池化只保留单点最大值对小物体的位置信息损耗明显卷积配合步长 2 能用多个卷积核综合判断边缘和纹理保留得更完整。在自动驾驶这种对位置敏感的场景优先用带步长的卷积降采样而不是一上来就接 MaxPool。3.3 端到端驾驶模型的 PyTorch 骨架与训练配置下面的定义可以直接跑通端到端驾驶模型的训练。import torch import torch.nn as nn class DrivingCNN(nn.Module): 输入 3x160x320 的 RGB 摄像头图像输出 3 个连续控制量 def __init__(self): super().__init__() # 使用 卷积步长填充 组合逐步压缩空间特征 self.conv1 nn.Conv2d(3, 24, kernel_size5, stride2, padding2) self.conv2 nn.Conv2d(24, 36, kernel_size5, stride2, padding2) self.conv3 nn.Conv2d(36, 48, kernel_size3, stride1, padding1) self.pool nn.MaxPool2d(kernel_size2, stride2) # 全连接层把空间特征映射到转向、油门、刹车 self.fc1 nn.Linear(48 * 20 * 40, 64) self.fc2 nn.Linear(64, 3) def forward(self, x): x torch.relu(self.conv1(x)) # 输出 (24, 80, 160) x torch.relu(self.conv2(x)) # 输出 (36, 40, 80) x torch.relu(self.conv3(x)) # 输出 (48, 40, 80) x self.pool(x) # 输出 (48, 20, 40) x x.reshape(x.size(0), -1) x torch.relu(self.fc1(x)) out torch.tanh(self.fc2(x)) # 将输出限制到 [-1, 1] return outforward 中每一步的尺寸变化已经注释在上方。tanh适合生成包含正负的转向角但油门和刹车如果记录的是 [0,1] 数值直接使用 tanh 会让模型强行学习负区间没有物理意义。更合理的做法是拆分激活转向用 tanh油门和刹车用 sigmoid。训练时 loss 用 MSE 做控制量回归给三个控制量设置不同权重会更稳定转向权重 1.0、油门权重 0.5、刹车权重 0.5。原因是转向误差直接决定车辆是否压线油门误差的影响存在约 0.3 秒的滞后。训练循环简化如下。for epoch in range(30): for rgb, ctrl in dataloader: # rgb: (B,3,160,320), ctrl: (B,3) pred model(rgb) loss nn.MSELoss()(pred, ctrl) opt.zero_grad() loss.backward() opt.step()实际项目中不要直接拿普通MSELoss用到底。如果数据集里直线样本占 70% 以上模型会一直学直行弯道全靠惯性过弯。我一般会统计每个帧的转向角绝对值按照直行和弯道分桶采样保证弯道样本占比不低于 40%。3.4 目标检测分支的取舍YOLO 与 Faster R-CNN 在仿真中的选择如果要加入障碍物识别目标检测是 CNN 在自动驾驶里的核心应用。Faster R-CNN 在仿真地图上精度足够但实现复杂、推理慢不适合每个物理步进都调用的情况YOLO 系列速度快但需要配置 anchor 参数对小目标行人容易漏检。这套工程更建议在主干网络后加一个轻量目标分支用 1x1 卷积输出置信度和边界框回归与驾驶控制共享卷积层。目标检测结果可以和油门联动比如行人置信度超过 0.5 时把 throttle 乘以 0.3起到避障降速的效果。4. 模型部署与实时推理将 PyTorch 模型移植到 Unity Barracuda 引擎4.1 导出 ONNX 的准备工作PyTorch 模型不能直接进 Unity常用路径是先导出 ONNX再用 Barracuda 加载。导出前要固定输入张量形状Unity 侧拿到的是 (160, 320, 3) 的纹理数据但 PyTorch 期望的 batch 维需要写在前面。导出代码如下。import torch model DrivingCNN() model.load_state_dict(torch.load(driving_cnn.pth)) model.eval() dummy torch.randn(1, 3, 160, 320) # 固定 batch1 torch.onnx.export( model, dummy, driving_cnn.onnx, input_names[rgb_input], output_names[control_output], opset_version11 )opset_version选 11是因为 Barracuda 对更高版本算子的支持不完整。导出后不要直接丢给 Unity先用 onnxruntime 跑一次比较输出和 PyTorch 是否一致。常见偏差来自 BatchNorm 层在训练和推理模式下的统计量差异model.eval()必须提前调用。4.2 Unity 侧用 Barracuda 执行推理在 Unity 中加载 ONNX 的方式是把.onnx导入工程得到NNModel资源再写一个推理脚本。脚本需要引用Unity.Barracuda命名空间。using UnityEngine; using Unity.Barracuda; public class CNNInference : MonoBehaviour { public NNModel modelAsset; public CarControl carControl; private IWorker worker; void Start() { var model ModelLoader.Load(modelAsset); worker WorkerFactory.CreateWorker(WorkerFactory.Type.ComputePrecompiled, model); } public void RunInference(Texture texture) { // texture 是 RenderTexturechannels:3 表示按 RGB 处理 using var input new Tensor(texture, channels: 3); var output worker.Execute(input).PeekOutput(); // 输出形状是 [1, 3]取 batch 0 的维度 float steer output[0]; float throttle output[1]; float brake output[2]; carControl.ApplyControl(steer, throttle, brake); } }new Tensor会把 RGBA 纹理转为 NCHW 排布的[1, 3, 160, 320]张量。channels: 3表示丢弃 Alpha如果输入纹理尺寸和导出模型不一致Barracuda 在部分后端会抛错因此建议在创建 RenderTexture 时就设置成 320x160。ComputePrecompiled使用 GPU 计算适合独立显卡如果机器没有独立显卡可以改用CSharp后端但推理帧率会下降一个量级。4.3 性能优化QualitySettings 与输入分辨率如何权衡实时推理的瓶颈往往不在模型本身而是 Unity 渲染和模型推理抢同一块 GPU。QualitySettings.asset 里的阴影距离、反射探针、后处理对帧率影响很大。我一般把阴影距离控制在 30 米以内抗锯齿用 FXAA关闭 HDR 后处理这样能给卷积计算留出约 30% 的 GPU 余量。不同分辨率下的帧率表现可以参考下面这组经验数据。输入分辨率GPU 推理延迟 (RTX 3060)全场景 FPS适用阶段640x3208 ms90离线数据回放320x1604 ms150模型训练与验证160x802 ms240嵌入式/实时避障降低分辨率牺牲的是远端小目标识别率。在高速场景行人可能只占 35 个像素160x80 下很容易漏检所以安全关键场景仍建议使用 320x160。5. 仿真验证与边界场景下的 CNN 鲁棒性调参5.1 用光照和天气扰动扩展训练分布Unity 里可以动态改变 Directional Light 的强度和旋转两行代码就能生成不同时段的驾驶场景。light.intensity Random.Range(0.2f, 1.0f); light.transform.rotation Quaternion.Euler( Random.Range(15f, 50f), Random.Range(0f, 360f), 0f );这两行放在训练数据采集场景的 Update 里每隔一帧执行一次画面中的阴影角度、路面反光都会变化。CNN 对光照非常敏感如果训练集全固定在正午强光下黄昏时模型倾向于把阴影当作障碍物。扰动强度不要过大否则场景纹理完全失真模型会把渲染噪声当特征。5.2 碰撞检测与失败重置验证模型好坏不能只看 loss更直接的方式是把模型放到验证赛道里连续跑记录压线次数和碰撞次数。Unity 中用标签判断碰撞体类型。void OnCollisionEnter(Collision collision) { if (collision.collider.CompareTag(Obstacle)) { failCount; ResetToCheckpoint(); } }ResetToCheckpoint把车辆刚体速度置零位置放回当前检查点而不是回发车点这样验证一整条赛道不需要从头重跑。碰撞本身是强负样本加入训练数据时要把场景里碰到的图像和控制标签一并写回让模型知道接近障碍物时该减速。5.3 损失定位与样本加权如果模型在同一个弯道反复压线最有效的调试手段不是无脑加深网络而是把这个弯道的训练样本复制加权或者降低直线样本的采样比例。还可以把全连接层前的特征图可视化看模型是否真正关注了车道线边缘。如果特征图高亮集中在天空说明预处理裁剪范围不够。这套工程里典型的结果是前 5 轮训练依然会撞墙迭代到 20 轮后只要数据里包含足够的循环弯道CNN 的转向输出就会变得平滑此时再回头调低油门权重收益更明显。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价