首页 / 资讯中心 / 文章详情

MATLAB驾驶场景设计器弯道场景搭建:曲率半径、采样密度与闭环验证

MATLAB驾驶场景设计器弯道场景搭建:曲率半径、采样密度与闭环验证 ★ FEATURED ARTICLE
第一次用 MATLAB 的驾驶场景设计器搭弯道我在屏幕前坐了整整一个下午最后卡住我的不是代码语法也不是工具箱版本而是一个特别朴素的问题这条弯的曲率半径到底该填多少。那时候我以为自己在做自动驾驶弯道仿真实际上是在做一件更基础的事——把一条现实里的路翻译成机器能理解的几何。翻译错了后面挂多少传感器、跑多复杂的控制算法全是在错误的地基上盖楼。这篇东西想聊的就是这件事MATLAB 驾驶场景设计器Driving Scenario Designer到底怎么在几分钟内搭出一条能跑通闭环的弯道场景哪些参数是必须先算的哪些步骤是可以直接抄的以及我在这个过程中反复踩到的那些坑。它不是一篇功能说明书工具箱的官方文档已经把这活儿干得挺好我更想讲清楚的是一个真正要用它做自动驾驶算法验证的人会怎么下手、会在哪里停下来想一想。适合谁看如果你刚拿到 Automated Driving Toolbox想快速搞出一个弯道场景来验证横向控制或者车道线检测或者你已经在用场景设计器但总觉得搭出来的路看着像那么回事跑起来不对劲——那这篇里的参数推导和踩坑记录应该能省你不少时间。代码和步骤都是可以复现的参数我也尽量给全你改个半径就能生成自己的弯。1. 弯道场景的难点不在建模而在曲率怎么给1.1 直线场景跑通弯道上却开始画龙几乎所有横向控制的算法在直线场景里都是表现良好的。你随便写个 PID横向误差曲线平滑得像教科书插图看着特别有成就感。然后你把场景一换成弯道同一套参数车开始画龙——入弯时切内侧出弯时甩到外车道误差曲线像心电图。这时候很多人的第一反应是调增益PID 参数调了一轮又一轮最后发现根本问题不在控制器而在场景本身曲率是突变的。现实中的道路在直线和圆弧之间一定有一段缓和曲线曲率从 0 连续过渡到 1/R。而手搓场景的人往往直接拿两段圆弧或者直线圆弧硬拼曲率在拼接点上一秒钟从 0 跳到 0.02是个阶跃信号。控制器看到一个阶跃曲率它的响应必然是一个过冲你再怎么调增益也压不平——这是激励本身的问题不是系统的问题。所以弯道场景设计的第一原则是先想清楚这条弯的曲率在空间上是怎么分布的而不是先拖鼠标画一条看起来像弯的线。这个顺序颠倒了后面全是返工。1.2 场景设计器把道路定义压缩成了一根中心线理解 MATLAB 这个工具的工作模型比记住按钮位置重要得多。整个驾驶场景设计器里道路的核心定义只有一个东西道路中心线road centers一串有序的三维坐标点。车道边界不是独立定义的对象而是从中心线按车道宽度向两侧长出来的。这个设计带来两个直接后果都是好事也是坑。好事是建模快——你只要给出中心线的点列和车道宽度双向两车道、单向三车道一行lanespec就出来了左右边界自动生成曲率也是从点列里算出来的。坑在于曲率的精度完全取决于你给的点的密度。给点太稀两点之间被当成直线段处理你画出来的圆弧实际上是个多边形给点太密场景文件变大、仿真变慢而且很多点在同一条直线上纯属浪费。所以这里的核心矛盾就清楚了中心线采样密度是弯道场景设计里唯一真正需要动脑子的参数。其余的车道数、车道宽度、坡度都是查规范和量尺寸的事。另外一点值得提前说清楚场景设计器里的道路是一个独立的几何对象车辆是另一个独立的参与者对象两者之间的关联是通过车辆被放在道路网格上这个动作隐含建立的。所以一条路搭完车放上去动不了或者报错说位置不在路上八成不是车的参数问题是道路中心线哪里断开了或者重叠了。我在第 6 节会细说这个坑。2. 曲率半径、速度与采样密度三个必须先算清楚的数2.1 用横向加速度反推这条弯最多能跑多快搭场景之前先在纸上或者命令行里把这三个数算出来能省掉后面 80% 的反复调试。第一个数是曲率半径 R。它决定的不是弯看起来够不够弯而是这条弯上我的车最多能开多快。物理关系很干净a_lat v² / R其中a_lat是横向加速度。乘客舒适性的经验阈值通常在 2.5 m/s² 左右稍激进一点到 3.5 m/s²再往上普通乘员就会明显感到被甩。反过来解出速度v sqrt(a_lat × R)就有了下面这张表曲率半径 R横向加速度 2.5 m/s² 对应车速横向加速度 3.5 m/s² 对应车速典型对应场景20 m7.1 m/s约 25 km/h8.4 m/s约 30 km/h停车场、园区道路50 m11.2 m/s约 40 km/h13.2 m/s约 47 km/h城市次干道125 m17.7 m/s约 64 km/h20.9 m/s约 75 km/h城市快速路300 m27.4 m/s约 99 km/h32.4 m/s约 117 km/h高速匝道、平缓弯这张表怎么用如果你的场景是城市道路右转R 取 20 到 30 米比较真实速度就按 25 km/h 左右设如果你想验证 80 km/h 下的车道保持能力那 R 至少得给到 200 米以上否则车在物理上就不可能贴着车道走仿真结果没有意义。我见过有人用 R30 的弯去测 100 km/h 的车道保持车飞出去之后怪算法不行这就属于场景设计上的错误归因。反过来说如果你的控制算法在物理上不可能的场景里表现差这不叫 bug这叫正确行为。先算出边界再判断结果是否合理。2.2 采样点密度按弦高误差算别凭感觉第二个数是采样密度。很多人是看着顺眼就行在弧上均匀撒几十个点。这样做的风险是小半径弯可能采样够密大半径弯反而采样太稀——因为同样的角度步长半径越大弧长越长弦和弧之间的偏差越大。正确的算法是按弦高误差sagitta来定步长。用弦替代弧最大偏差出现在弧的中点公式是e R × (1 - cos(Δθ / 2))反解出可接受的角步长Δθ 2 × arccos(1 - e / R)举个具体的数。设 R 50 m要求弦高误差不超过 1 厘米这个精度对车道线检测评估足够了cos(Δθ/2) 1 - 0.01/50 0.9998 Δθ/2 ≈ 0.02 rad Δθ ≈ 0.04 rad ≈ 2.29°一个 90° 的弯π/2 ≈ 1.5708 rad需要1.5708 / 0.04 ≈ 40段也就是 41 个点。有意思的是如果把 R 换成 300 m同样的 1 厘米误差要求下Δθ ≈ 0.0163 rad ≈ 0.93°90° 弯需要接近 96 段。半径越大需要的点反而越多——这跟直觉相反但道理很简单绝对误差要求固定时弧的平缓程度按半径放大弦需要在更长的弧上贴住它。这个结论直接推翻了很多人的默认做法。所以我在脚本里从来不是手写点列而是写一个小函数按误差目标自动生成function pts arcSamples(R, thetaTotal, chordTol, varargin) % ARCSAMPLES 按弦高误差目标生成圆弧采样点局部坐标起点在原点 % R : 曲率半径 (m) % thetaTotal : 圆弧总转角 (rad) % chordTol : 允许的最大弦高误差 (m) dTheta 2 * acos(max(-1, min(1, 1 - chordTol / R))); n max(3, ceil(thetaTotal / dTheta) 1); th linspace(0, thetaTotal, n); pts [R * sin(th), R * (1 - cos(th)), zeros(n, 1)]; end注意这里用的是起点在原点、初始朝向沿 X、向左转的局部坐标系y R(1-cosθ)保证起点 y 坐标为 0。这样生成的弧段可以直接平移旋转拼到别的路段上拼接的时候不容易算错。2.3 缓和曲线缺失时会发生什么刚才提到曲率阶跃的问题。严格来说真实道路在直线和圆曲线之间有一段回旋线clothoid曲率随弧长线性变化。在场景设计器里精确构造回旋线是可以做的但很多人会觉得麻烦而跳过。跳过之后会怎样如果你的目的是验证感知算法车道线检测、曲率估计跳过缓和曲线问题不大因为感知模块看的是图像或点云曲率的微小阶跃它未必敏感。但如果你的目的是验证横向控制跳过缓和曲线会让你得到一个偏悲观的结论控制器在入弯点的超调量会比真实情况大你可能因此把增益调得过软反而牺牲了弯中的跟踪精度。一个折中的做法是用一小段低阶多项式做过渡。比如在直线末端和圆弧起点之间插 15 到 20 米曲率从 0 线性增长到 1/R用参数方程近似function pts clothoidApprox(R, L, n) % CLOTHOIDAPPROX 用曲率线性增长近似一段缓和曲线 % R : 目标曲率半径 (m) % L : 缓和曲线长度 (m) % n : 采样点数 s linspace(0, L, n); kappa s / (R * L); % 曲率从 0 线性增到 1/R theta cumtrapz(s, kappa); % 朝向角积分 x cumtrapz(s, cos(theta)); y cumtrapz(s, sin(theta)); pts [x, y, zeros(n, 1)]; end这段代码用累积梯形积分把曲率积成朝向、再积成坐标是很经典的数值做法。精度足够做场景用缺点是cumtrapz在点数很少时误差偏大建议 n 给到 50 以上。有了这个函数直线—缓和曲线—圆弧—缓和曲线—直线的标准弯道结构就能拼出来了曲率连续控制器的响应也会正常得多。3. 五分钟搭完一条 90 度弯界面操作与等价脚本3.1 界面上的七步操作说五分钟搞定前提是参数已经在第 2 节算好了。真正在界面里操作流程是这样的命令行输入drivingScenarioDesigner打开应用新建一个空白场景。左侧工具栏选Road在画布上依次点击中心线的关键点。这里的关键技巧是不要靠手点去凑圆弧先点三个点大致定出直线段、弯心、出弯方向后面再用脚本替换成精确点列。选中道路在右侧属性面板把Lanes设成[1 1]左右各一条Width设成[3.5 3.5]。宽度数组的顺序是从最左车道到最右车道这个顺序搞反了在不对称场景里会很难查。切到Actors页添加一辆车ClassID 通常用 1 表示轿车把它拖到道路起点附近。给这辆车加轨迹在轨迹编辑里沿中心线点几个途经点或者直接在属性里填 waypoints。途经点不要只放起终点两个弯道至少要放 5 个以上否则中间会被当成直线插值。设置速度。图形界面里可以给每个途经点单独设速度也可以给一个恒定值。点Run预览。如果车在弯里明显切内道或者出界回到第 2 节检查半径和速度是否匹配。界面操作的好处是所见即所得缺点是点出来的中心线精度有限而且不可复现——换个同事打开点出来的点就不一样。所以我的习惯是界面只用来快速验证想法最终交付一律用脚本。3.2 一份可以直接跑的完整脚本下面这份脚本输入是半径和转角输出是一个可以直接plot、可以跑、可以导出的场景对象。参数按 R50 m、90° 弯、双向两车道来设你可以直接改前几行。%% 弯道场景快速构建脚本 clear; clc; close all; % ---------------- 可调参数 ---------------- R 50; % 弯道曲率半径 (m) thetaDeg 90; % 圆弧转角 (度) laneWidth 3.5; % 单车道宽度 (m) entryLen 40; % 入弯前直线段长度 (m) exitLen 40; % 出弯后直线段长度 (m) chordTol 0.01; % 弦高误差容限 (m) vTarget 11; % 车辆目标速度 (m/s) sampleTime 0.05; % 场景采样周期 (s) % ----------------------------------------- thetaTotal deg2rad(thetaDeg); % 1) 分段生成中心线局部坐标 entryPts [linspace(-entryLen, 0, 20), zeros(20, 1), zeros(20, 1)]; arcPts arcSamples(R, thetaTotal, chordTol); % 圆弧局部坐标 - 世界坐标旋转使终点朝向正确 % arcSamples 生成时起点在原点、初始朝向 X需平移到 entryPts 末端 arcPts(:,1) arcPts(:,1) entryPts(end,1); arcPts(:,2) arcPts(:,2) entryPts(end,2); % 出弯段方向圆弧终点切向 endHeading thetaTotal; % 逆时针转 thetaTotal 后的朝向 exitDir [cos(endHeading), sin(endHeading)]; exitStart arcPts(end, 1:2); exitPts exitStart (0:19)/19 * exitLen * exitDir; exitPts [exitPts, zeros(20, 1)]; % 2) 拼接并去重避免零长度段 roadCenters [entryPts; arcPts(2:end,:); exitPts(2:end,:)]; % 3) 创建场景与道路 scenario drivingScenario(SampleTime, sampleTime, StopTime, 25); road(scenario, roadCenters, ... Lanes, lanespec([1 1], Width, [laneWidth laneWidth]), ... Name, Curve_R50); % 4) 车辆与轨迹 ego vehicle(scenario, ClassID, 1, ... Length, 4.5, Width, 1.8, Height, 1.5, ... Position, roadCenters(1,:) [0 0 0], ... Name, EgoCar); % 用中心线上的点作为途经点速度给恒定值 wp roadCenters(1:5:end, 1:2); % 抽稀避免途经点过密 smoothTrajectory(ego, wp, vTarget); % 5) 绘制与回放 figure(Name, Curve Scenario); plot(scenario, Waypoints, on); title(sprintf(R %d m, %d deg curve, R, thetaDeg)); % 6) 可选回放动画 % record(scenario);几点说明。lanespec([1 1], Width, [3.5 3.5])里的[1 1]表示中心线左侧 1 条、右侧 1 条总共两条车道。smoothTrajectory第三参数既可以是标量恒定速度也可以是和途经点等长的速度向量后者在第 4 节会用来做速度剖面。arcSamples是这个脚本依赖的辅助函数就是第 2.2 节给的那一段放到同目录或者脚本末尾都行。这种主脚本 几个小工具函数的组织方式比把所有东西塞进一个巨型脚本要可靠得多尤其是当你需要同时维护好几个不同半径的弯道时。3.3 脚本和界面之间怎么来回切换这两者不是二选一的关系可以来回倒。脚本里创建好scenario对象之后直接运行drivingScenarioDesigner(scenario);就能带着已有的道路和车辆打开图形界面继续在界面上加东西。反过来界面上调好之后用Export → Export MATLAB Function可以导出成一个返回scenario对象的函数。这个功能我特别推荐原因有两个一是它把界面上的操作固化成了可版本管理的代码二是这个函数可以直接在测试脚本里被调用做成回归测试的一部分。我自己的常规流程是这样的先用脚本生成一个参数化的基础场景扔进界面里肉眼看一眼形状对不对如果不对就改脚本里的参数重新生成——而不是在界面上手调。界面只做检查器不做编辑器。这样每次改的都是可追溯的参数不会出现上次那条弯挺好的但我想不起来怎么点出来的这种尴尬。4. 让车真正沿着弯道走轨迹与速度剖面的耦合4.1 起点朝向错一度弯心就偏出好几米场景搭好之后第一个要确认的是车辆的初始位置和朝向。vehicle的Position是三维坐标Yaw是绕 Z 轴的朝向角单位是度不是弧度。这个细节坑过太多人你填pi/2以为是 90°实际被当成 1.57°车头几乎还是朝前结果整条轨迹都歪着走。更隐蔽的问题是位置本身。如果车辆初始位置没有严格落在道路中心线上比如手动填了个[0 0 0]而道路起点在[-40 0 0]smoothTrajectory会先生成一段从当前位置到第一个途经点的过渡这段过渡可能直接穿过路肩甚至穿出道路范围看着就像车从草地里开出来。一个稳妥的做法是直接用道路中心线的第一个点作为初始位置朝向用前两个点的连线方向算p0 roadCenters(1, :); p1 roadCenters(2, :); yawDeg atan2d(p1(2) - p0(2), p1(1) - p0(1)); ego vehicle(scenario, ClassID, 1, ... Position, p0, Yaw, yawDeg, Name, EgoCar);atan2d返回的就是角度制和Yaw的单位直接对齐省掉一次rad2deg的心算。这个习惯我保持了很久因为凡是涉及角度单位的地方人脑出错的概率远高于编译器。顺带说一句错一度差多少。R 50 m 的弯上车头方向偏差 1°开出去 20 米后横向偏差大约是20 × tan(1°) ≈ 0.35 m——已经超过半个车道了。所以初始朝向不是差不多就行的参数得按公式算。4.2 速度剖面别用阶跃smoothTrajectory(ego, wp, vTarget)用标量速度时整条轨迹是匀速的。匀速过弯在物理上就要求横向加速度恒定也就是v²/R恒定这对场景本身没毛病但它会让你的控制器处在永远稳态的状态看不出瞬态响应。更贴近真实的做法是给一条梯形或 S 形速度剖面入弯前减速弯中保持出弯加速。做法是把速度向量传进去长度和途经点数一致nWp size(wp, 1); vProfile ones(nWp, 1) * vTarget; % 前 20% 路程线性降到弯中速度后 20% 线性升回 vCorner sqrt(2.5 * R); % 舒适横向加速度对应的弯中速度 nRamp max(2, round(0.2 * nWp)); vProfile(1:nRamp) linspace(vTarget, vCorner, nRamp); vProfile(end-nRamp1:end) linspace(vCorner, vTarget, nRamp); vProfile(nRamp1 : end-nRamp) vCorner; smoothTrajectory(ego, wp, vProfile);这里vCorner sqrt(2.5 × R)就是第 2.1 节那个公式R50 时约 11.2 m/s。这个值不只是个合理数字它是场景的物理约束边界——车在弯中以超过这个速度行驶横向加速度就超出舒适区你的控制器再优秀也只能靠不跟车道来化解。有个小陷阱smoothTrajectory对速度向量做平滑如果你的速度剖面在弯中段是常数、两端是斜坡平滑后斜坡会略微延长导致实际的减速点比预期早一点。差异不大但如果你的测试对减速时机特别敏感比如测 AEB 与转向的协同建议把采样点抽稀的间距调小一点让平滑的影响相对变小。4.3 横向偏置与车道关联默认情况下车辆沿中心线走。但很多测试场景需要车在车道中心而不是道路中心开——双向两车道的道路中心线是黄色虚线车应该走在右侧车道中心也就是相对中心线偏右laneWidth/2。在场景设计器里这个偏置可以通过给途经点加偏移量实现。注意方向右侧车道对应负偏移假设 y 轴指向左。我一般写个小工具函数处理这件事避免每次手动算偏移方向function wpOffset offsetAlongRight(wp, d) % OFFSETALONGRIGHT 把途经点沿路径法向右侧平移 d 米 dx gradient(wp(:,1)); dy gradient(wp(:,2)); len hypot(dx, dy); nx dy ./ len; % 左法向 ny -dx ./ len; wpOffset wp - d * [nx, ny]; % 右偏 d 米 end用中心差分gradient算切向再做 90° 旋转得到法向这是处理离散点列的标准手法。要注意首尾点的gradient是单边差分精度略低如果偏置量不大1.75 米这种量级影响可以忽略。还有个更省事的路径直接用lanespec定义好车道之后让车辆关联到某条车道上场景设计器会自动把它约束在车道内。但我不太推荐在弯道场景里完全依赖这个约束原因是它会让你的场景太理想——车永远不越线你就测不出压线、跨车道这些真实场景下的边界行为。人工给途经点虽然麻烦但可控性高得多。5. 加传感器、看鸟瞰图、导出仿真5.1 传感器安装位姿的三个参数弯道场景最典型的用途之一是验证车道线检测在曲率变化下的表现。这就需要给车挂一个前视摄像头。在场景设计器的Sensors页里添加摄像头本质上要填三个东西安装位置、安装高度、朝向。安装位置一般取车头前保险杠后方一点比如[2.5, 0]相对车辆后轴中心的纵向、横向偏移高度取1.2到1.4米接近真实乘用车的前摄像头高度。朝向通常正前方俯仰角略微向下 1 到 3 度这样能在有限画幅里多看到一点近处的车道线——这对弯道特别重要因为弯道中远端的车道线已经弯出视野了靠的是近端线来拟合曲率。导出的代码大致长这样具体字段随版本略有差异以界面导出为准camera monoCamera( ... SensorLocation, [2.5 0], ... Height, 1.3, ... Yaw, 0, ... Pitch, -2, ... Roll, 0);我要强调的一点是传感器的位姿必须在场景坐标系和车辆坐标系之间想清楚。场景设计器里的SensorLocation是相对车辆本体的不是绝对坐标。我曾经把这个偏移按世界坐标填了一次结果摄像头挂在车后面 2.5 米的位置画面里全是天空排查了很久才反应过来。雷达的坑不太一样。雷达的探测距离和视场角决定了它在弯道里能不能看见前方车辆——R50 的弯上前方车辆如果在对向车道很可能已经超出视场角了。这不是 bug是真实的物理限制。如果你的算法依赖雷达做前车检测弯道场景里就要接受间歇性丢失目标这个事实并且验证算法在这段时间里的行为是否安全。5.2 鸟瞰图和追随视角的快速自检场景跑起来之后别急着看算法输出先肉眼确认几何对不对。两个视图特别有用% 鸟瞰全局看道路形状和整条轨迹 figure; plot(scenario, Waypoints, on); % 跟随自车视角看车道关系 chasePlot(ego, Centerline, on);chasePlot画出来的是车后方跟随视角带车道边界。这个视角最适合判断车是不是真的走在车道中间。我在调横向偏置的时候基本上就是盯着这个图看车身和左右边界的距离是否目测相等。再进一步可以用鸟瞰图对象做轨迹叠加检查bep birdsEyePlot(XLimits, [-20 80], YLimits, [-20 100]); plotter laneBoundaryPlotter(bep, Color, [0.5 0.5 0.5]); plotLaneBoundary(plotter, ego.LaneBoundaries);ego.LaneBoundaries这个属性会随车辆移动实时更新把当前感知到的车道边界画出来。如果这条线和道路的实际边界在弯中明显分离说明车道关联出了问题多半是车辆位置离中心线太远超出了关联阈值。这里有个经验弯道场景的自检最好在三个位置各看一次——入弯前、弯中、出弯后。入弯前看的是减速是否及时弯中看的是横向偏差最大值出弯后看的是有没有超调。三个位置都没问题这个场景才算搭对了。5.3 导出的三种形态与选择依据场景搭好后有三种导出方式用途完全不一样导出形态操作位置适用场景主要限制MATLAB Function界面 Export 菜单固化场景、做回归测试、参数化重生成只含道路和参与者不含传感器检测逻辑Simulink 模型界面 Export 菜单闭环控制验证、接入自己的算法模块需要 Scenario Reader 模块正确指向场景文件直接在脚本中调用无需导出快速验证、批量生成多组参数每次都要跑一遍构建脚本第一种是我用得最多的。把界面上调好的场景导出成函数之后我会把它和一组断言放在一起做成一个最小回归测试半径是不是 50、车道数是不是 2、自车轨迹的最大横向偏差是不是小于 0.2 米。这样以后换了工具箱版本或者改了参数跑一遍就知道有没有被破坏。第二种适合闭环。导出到 Simulink 之后模型里会有一个 Scenario Reader 模块读取场景输出车辆状态和传感器检测结果你把自己的控制器接在后面就行。这里最常见的坑是场景文件的路径——模型里存的是绝对路径换台机器就找不到文件。所以导出之后第一件事是把路径改成相对路径或者用fileparts(mfilename(fullpath))拼出来。第三种适合参数扫描。比如你想看 R 30、50、80、120 四组半径下算法的表现差异写个for循环改R重新构建场景、重新跑一次跑完四组比在界面上手动改四次快得多。6. 我在弯道场景里踩过的五个坑6.1 路段拼接点重复导致的零长度段这是最隐蔽的一个。你把入弯直线段的终点和圆弧段的起点直接拼起来两个点的坐标完全一样这在几何上没问题但在场景内部会产生一个长度为零的路段。表现出来的症状是plot(scenario)看着一切正常但车辆跑起来到那个位置会突然抖一下或者干脆报错说找不到对应车道。原因在于曲线的参数化——零长度段会让切向无法定义数值上除以零。解决办法很简单拼接时去掉重复点[segA; segB(2:end,:)]。我现在的习惯是在拼接之后加一句检查d vecnorm(diff(roadCenters), 2, 2); assert(all(d 1e-3), 中心线存在重复点或过短段检查拼接逻辑);几行断言能省掉半小时的困惑。类似的检查我还加了曲率连续性、总长度是否合理这些都放在构建脚本的末尾形成一个场景健康检查小节。6.2 车道宽度数组的顺序是从左到右lanespec的Width参数是个向量长度必须等于总车道数顺序是从最左侧车道到最右侧车道。如果你设的是lanespec([2 1], Width, [3.5 3.5 3.75])那么最左边那条 3.5 米、中间 3.5 米、右边 3.75 米——右侧车道更宽这条规则正好落在最后一个元素上。这个顺序搞反了在对称场景左右宽度相同里完全看不出来一旦做不对称场景比如带公交专用道的路、或者一侧有硬路肩车道边界就全错了而且错误很隐蔽——道路看着还是那条路只是车辆走的车道位置偏了。我现在的做法是永远用命名变量传参别在函数调用里直接写字面量数组wLeft [3.5 3.5]; % 左侧两条车道 wRight [3.75 3.5]; % 右侧两条靠外的更宽 lanespec([2 2], Width, [wLeft, fliplr(wRight)]);注意这里右侧用了fliplr因为从最左到最右的顺序右侧车道的排列是从内到外反过来。这种细节写清楚了下次改参数才不容易错。6.3 Yaw 用度不用弧度前面提过一次但值得单独拿出来说因为它是高频错误。场景设计器里所有涉及朝向、角度的地方——vehicle的Yaw、monoCamera的Yaw/Pitch/Roll——单位都是度。而 MATLAB 里绝大多数三角函数默认吃弧度。这两套体系混在一起出错的时候不会有任何报错只是结果不对。我的应对办法是凡是算角度的地方一律用带d后缀的函数atan2d、sind、cosd从源头就保持在角度制里不做中间换算。需要转弧度的地方明确写deg2rad让代码自己说明意图。这个习惯养成之后因为角度单位出错的次数基本降到零。6.4 车辆初始位置悬空场景里只有道路没有网格的时候车辆位置是自由的放哪儿都行。但一旦道路定义好了车辆位置如果偏离道路太远smoothTrajectory可能会报错或者生成一段诡异的过渡轨迹。更麻烦的情况是 z 坐标。道路中心线如果给了 z 值做坡道场景而车辆位置 z 填了 0车就会陷进路面或者悬在空中。虽然平面上看不出来但一旦接入传感器仿真摄像头高度基准就错了画面里的地平线位置会明显不对。我现在的做法是初始位置直接取roadCenters(1,:)连 z 一起拿过来不单独填。6.5 仿真步长与场景采样率不匹配drivingScenario(SampleTime, 0.05)里设的是场景的采样周期也就是 20 Hz。这个值需要和你后续接入的控制器采样率对齐或者至少成整数倍关系。如果场景跑 20 Hz、控制器跑 50 Hz中间就需要插值而插值本身会引入相位滞后在弯道这种动态场景里会让横向误差评估偏大。我的经验值是场景采样率取控制器采样率的整数倍或者干脆相等。横向控制一般 20 到 50 Hz场景给 0.02 到 0.05 秒都能接受。如果要做高频的传感器仿真比如摄像头 30 帧场景采样率至少要能给到 30 Hz 以上否则图像序列的时间戳会很别扭。还有StopTime这个不是随便给个够长就行。给太短车还没出弯仿真就结束了看不出出弯超调给太长后半段全是直线白跑。合理的算法是总路程 / 平均速度 20% 余量。我一般在脚本里根据中心线总长自动算totalLen sum(vecnorm(diff(roadCenters), 2, 2)); scenario.StopTime totalLen / (0.7 * vTarget);用 0.7 倍目标速度作为平均速度的保守估计能保证仿真覆盖整条路径还留有余量。最后一个想说的小技巧把常用的弯道参数存成一个结构体数组比如curveLib(1) struct(R,30,theta,90,v,8)写几个典型场景城市右转、高速匝道、环岛入口以后要测哪类场景直接索引不用每次重新想参数。这个习惯是从做参数扫描的时候养成的后来发现它对和同事对齐测试场景特别有用——大家用同一套库结果才有可比性。搭场景这件事看着是在做几何实际上是在定义测试的边界条件边界定得清楚后面的结论才站得住。
阅读完成 · 觉得有帮助?
咨询建站