프로그래밍 방식으로 주행 시나리오 만들기
이 예제에서는 합성 센서 데이터 및 추적 알고리즘을 위한 ground truth를 생성하는 방법을 보여줍니다. 개루프 시뮬레이션과 폐루프 시뮬레이션에서 액터 자세를 업데이트하는 방법도 보여줍니다. 마지막에는 주행 시나리오를 사용하여 좌표 변환을 수행하고 이를 조감도 플롯에 통합하는 방법을 보여줍니다.
이 예제에서는 MATLAB® 명령줄에서 프로그래밍 방식으로 주행 시나리오를 생성합니다. 또는 Driving Scenario Designer 앱을 사용하여 대화형 방식으로 시나리오를 생성할 수도 있습니다. 이에 대한 예제는 Create Driving Scenario Interactively and Generate Synthetic Sensor Data 항목을 참조하십시오.
소개
주행 시나리오의 목표 중 하나는 특정 차량에서 사용되는 센서 검출 및 추적 알고리즘에 사용할 "ground truth" 테스트 케이스를 생성하는 것입니다.
이 ground truth는 일반적으로 전역 좌표계로 정의되지만, 센서는 대개 이동하는 차량에 장착되므로 이 데이터는 차량을 따라 움직이는 기준 프레임으로 변환되어야 합니다. 주행 시나리오는 이 변환의 자동 수행을 지원하므로, 사용자가 도로와 객체의 궤적을 전역 좌표로 지정하면 이 정보를 시나리오 내 액터의 기준 프레임으로 변환하고 시각화하는 툴을 제공합니다.
자세 정보를 액터의 기준 프레임으로 변환하기
drivingScenario
는 도로 그리고 액터라고 불리는 움직이는 물체들로 구성된 모델입니다. 액터를 사용하여 시나리오 내의 보행자, 주차 요금 징수기, 소화전 및 기타 다른 객체를 모델링할 수 있습니다. 액터는 길이, 너비, 높이, 레이다 반사 면적(RCS)을 가진 직육면체로 구성됩니다. 액터의 위치와 방향은 하단 면 중심의 단일 점을 기준으로 지정됩니다.
바퀴로 이동하는 특별한 종류의 액터는 차량이며, 차량의 위치와 방향은 뒤 차축의 중심 바로 아래 지면을 기준으로 합니다(이 지점이 더 자연스러운 회전 중심임).
모든 액터(차량 포함)는 Position
속성, Roll
속성, Pitch
속성, Yaw
속성, Velocity
속성, AngularVelocity
속성을 각각 지정하여 시나리오 내 어디에나 배치할 수 있습니다.
다음은 두 대의 차량이 10미터의 간격으로 각각 초당 3미터와 4미터의 속도로 원점을 향해 주행하는 시나리오의 예제입니다.
scenario = drivingScenario; v1 = vehicle(scenario,'ClassID',1','Position',[6 0 0],'Velocity',[-3 0 0],'Yaw',180)
v1 = Vehicle with properties: FrontOverhang: 0.9000 RearOverhang: 1 Wheelbase: 2.8000 EntryTime: 0 ExitTime: Inf ActorID: 1 ClassID: 1 Name: "" AssetType: "Unspecified" AssetPath: "" PlotColor: [0.0660 0.4430 0.7450] Position: [6 0 0] Velocity: [-3 0 0] Yaw: 180 Pitch: 0 Roll: 0 AngularVelocity: [0 0 0] Length: 4.7000 Width: 1.8000 Height: 1.4000 Mesh: [1×1 extendedObjectMesh] RCSPattern: [2×2 double] RCSAzimuthAngles: [-180 180] RCSElevationAngles: [-90 90]
v2 = vehicle(scenario,'ClassID',1,'Position',[0 10 0],'Velocity',[0 -4 0],'Yaw',-90)
v2 = Vehicle with properties: FrontOverhang: 0.9000 RearOverhang: 1 Wheelbase: 2.8000 EntryTime: 0 ExitTime: Inf ActorID: 2 ClassID: 1 Name: "" AssetType: "Unspecified" AssetPath: "" PlotColor: [0.8660 0.3290 0] Position: [0 10 0] Velocity: [0 -4 0] Yaw: -90 Pitch: 0 Roll: 0 AngularVelocity: [0 0 0] Length: 4.7000 Width: 1.8000 Height: 1.4000 Mesh: [1×1 extendedObjectMesh] RCSPattern: [2×2 double] RCSAzimuthAngles: [-180 180] RCSElevationAngles: [-90 90]
시나리오를 시각화하기 위해, 해당 시나리오에 대해 plot
함수를 호출합니다.
plot(scenario); set(gcf,'Name','Scenario Plot') xlim([-20 20]); ylim([-20 20]);
시나리오에 모든 액터를 생성한 후에는 각 액터의 Position
, Roll
, Pitch
, Yaw
, Velocity
, AngularVelocity
속성을 검사하면 모든 액터의 자세 정보를 시나리오 좌표에서 검사할 수 있습니다. 혹은 시나리오에 대해 actorPoses
함수를 호출하면 모든 정보를 편리한 구조체 형태로 얻을 수 있습니다.
ap = actorPoses(scenario)
ap = 2×1 struct array with fields: ActorID Position Velocity Roll Pitch Yaw AngularVelocity
특정 액터가 자신의 기준 프레임에서 바라보는 모든 다른 객체(즉, 타깃)의 자세 정보를 얻으려면, 이 액터에 대해 targetPoses
함수를 호출하면 됩니다.
v2TargetPoses = targetPoses(v2)
v2TargetPoses = struct with fields: ActorID: 1 ClassID: 1 Position: [10 6.0000 0] Velocity: [-4 -3.0000 0] Roll: 0 Pitch: 0 Yaw: -90.0000 AngularVelocity: [0 0 0]
차량에 대한 추적 플롯(chase plot)을 추가하여 상대적인 차량 배치를 정성적으로 확인할 수 있습니다. 기본적으로 추적 플롯은 차량 뒤 일정한 거리에서 바라본 투영 시점의 뷰를 표시합니다.
이 그림은 두 번째 차량(빨간색)의 바로 뒤에서 바라본 시점입니다. 두 번째 차량에서 바라본 타깃 자세는 다른 차량(파란색)이 이 두 번째 차량 기준으로 6m 전방, 10m 좌측에 위치해 있음을 보여줍니다. 추적 플롯에서는 이를 정성적으로 볼 수 있습니다.
chasePlot(v2) set(gcf,'Name','Chase Plot')
일반적으로 주행 시나리오와 연결된 모든 플롯은 시뮬레이션 과정에서 advance
함수를 호출할 때 업데이트됩니다. 다른 액터의 위치 속성을 수동으로 업데이트한 경우 updatePlots
를 호출하여 결과를 바로 확인할 수 있습니다.
v1.Yaw = 135; updatePlots(scenario);
도로 경계를 액터의 기준 프레임으로 변환하기
주행 시나리오를 사용하여 시나리오에 정의된 도로의 경계를 가져올 수도 있습니다.
여기서는 Define Road Layouts Programmatically에서 설명된 간단한 타원형 트랙을 사용하겠습니다. 이 트랙은 길이가 약 200미터이고 너비가 약 100미터인 면적을 가지며 곡선의 뱅크각이 9도입니다.
scenario = drivingScenario; roadCenters = ... [ 0 40 49 50 100 50 49 40 -40 -49 -50 -100 -50 -49 -40 0 -50 -50 -50 -50 0 50 50 50 50 50 50 0 -50 -50 -50 -50 0 0 .45 .45 .45 .45 .45 0 0 .45 .45 .45 .45 .45 0 0]'; bankAngles = ... [ 0 0 9 9 9 9 9 0 0 9 9 9 9 9 0 0]; road(scenario, roadCenters, bankAngles, 'lanes', lanespec(2)); plot(scenario);
도로의 테두리를 정의하는 선을 구하려면 주행 시나리에 대해 roadBoundaries
함수를 사용하면 됩니다. 이 함수는 도로 테두리(위의 시나리오 플롯에서는 검은색 실선으로 표시됨)가 포함된 셀형 배열을 반환합니다.
rb = roadBoundaries(scenario)
rb = 1×2 cell array {258×3 double} {258×3 double}
위의 예제에는 두 개의 도로 경계(외부 경계와 내부 경계)가 있습니다. 다음과 같이 직접 플로팅할 수 있습니다.
figure outerBoundary = rb{1}; innerBoundary = rb{2}; plot3(innerBoundary(:,1),innerBoundary(:,2),innerBoundary(:,3),'r', ... outerBoundary(:,1),outerBoundary(:,2),outerBoundary(:,3),'g') axis equal
액터의 좌표에서 도로 경계를 얻으려면 액터에 대해 roadBoundaries
함수를 사용할 수 있습니다. 이를 수행하려면 시나리오 대신 액터를 첫 번째 인수로 전달하면 됩니다.
이를 표시하기 위해, "에고 차량"을 추가하고 트랙에 배치합니다.
egoCar = vehicle(scenario,'ClassID',1,'Position',[80 -40 0.45],'Yaw',30);
그런 다음 차량에 대해 roadBoundaries
함수를 호출하고 이전과 동일하게 플로팅합니다. 그러면 차량의 좌표를 기준으로 렌더링됩니다.
figure rb = roadBoundaries(egoCar) outerBoundary = rb{1}; innerBoundary = rb{2}; plot3(innerBoundary(:,1),innerBoundary(:,2),innerBoundary(:,3),'r', ... outerBoundary(:,1),outerBoundary(:,2),outerBoundary(:,3),'g') axis equal
rb = 1×2 cell array {258×3 double} {258×3 double}
액터 궤적 지정하기
미리 정의된 3차원 경로를 따라 특정 액터를 배치하고 플로팅할 수 있습니다.
다음은 두 차량이 각자의 차선에서 30m/s의 속도와 50m/s의 속도로 경주로를 따라가는 예제입니다. 차량의 위치를 도로 중심에서 차선 너비의 절반인 2.7미터만큼 오프셋하고 트랙의 뱅크각 구간에서는 양측에 수직 높이의 절반으로 설정합니다.
chasePlot(egoCar); fastCar = vehicle(scenario,'ClassID',1); d = 2.7/2; h = .45/2; roadOffset = [ 0 0 0 0 d 0 0 0 0 0 0 -d 0 0 0 0 -d -d -d -d 0 d d d d d d 0 -d -d -d -d 0 0 h h h h h 0 0 h h h h h 0 0]'; rWayPoints = roadCenters + roadOffset; lWayPoints = roadCenters - roadOffset; % loop around the track four times rWayPoints = [repmat(rWayPoints(1:end-1,:),5,1); rWayPoints(1,:)]; lWayPoints = [repmat(lWayPoints(1:end-1,:),5,1); lWayPoints(1,:)]; smoothTrajectory(egoCar,rWayPoints(:,:), 30); smoothTrajectory(fastCar,lWayPoints(:,:), 50);
시뮬레이션 진행하기
주행 시나리오에 대해 advance
를 호출하여 궤적을 따르는 액터를 업데이트합니다. advance
가 호출되면 궤적을 따르는 각 액터가 앞으로 이동하고 해당 플롯이 업데이트됩니다. 궤적을 정의한 액터만 실제로 업데이트됩니다. 이렇게 하는 이유는 시뮬레이션이 실행되는 동안 사용자의 자체 로직을 제공하기 위해서입니다.
시나리오의 SampleTime
속성은 업데이트 간의 시간 간격을 제어합니다. 기본적으로 이 속성은 10밀리초이지만, 임의의 분해능으로 지정할 수 있습니다.
scenario.SampleTime = 0.02
scenario = drivingScenario with properties: SampleTime: 0.0200 StopTime: Inf SimulationTime: 0 IsRunning: 1 Actors: [1×2 driving.scenario.Vehicle] Barriers: [0×0 driving.scenario.Barrier] ParkingLots: [0×0 driving.scenario.ParkingLot]
while 루프의 조건문에서 advance
를 호출하고, 시나리오를 검사하거나 수정하는 명령을 루프 본문 내에 배치하여 시뮬레이션을 실행할 수 있습니다.
차량의 궤적이 완료되거나 선택적 StopTime
에 도달하면 while 루프는 자동으로 종료됩니다.
scenario.StopTime = 4; while advance(scenario) pause(0.001) end
시나리오 기록하기
모든 액터의 궤적을 미리 알고 있는 경우 편의를 위해 시나리오에서 record
함수를 호출하여 각 시간 스텝별로 각 액터의 자세 정보가 포함된 구조체를 반환할 수 있습니다.
예를 들어, 시뮬레이션의 첫 100밀리초 동안 각 액터의 자세 정보를 검사하고 다섯 번째로 기록된 샘플을 검사할 수 있습니다.
close all
scenario.StopTime = 0.100;
poseRecord = record(scenario)
r = poseRecord(5)
r.ActorPoses(1)
r.ActorPoses(2)
poseRecord = 1×5 struct array with fields: SimulationTime ActorPoses r = struct with fields: SimulationTime: 0.0800 ActorPoses: [2×1 struct] ans = struct with fields: ActorID: 1 Position: [2.4000 -51.3502 0] Velocity: [30.0000 -0.0038 0] Roll: 0 Pitch: 0 Yaw: -0.0073 AngularVelocity: [0 0 -0.0823] ans = struct with fields: ActorID: 2 Position: [4.0000 -48.6504 0] Velocity: [50.0000 -0.0105 0] Roll: 0 Pitch: 0 Yaw: -0.0120 AngularVelocity: [0 0 -0.1235]
여러 뷰를 조감도 플롯에 통합하기
시뮬레이션을 디버그할 때, 시나리오에 의해 생성된 플롯을 보면서 동시에 특정 액터의 조감도 플롯에서 "ground truth" 데이터를 보고 싶을 수 있습니다. 이를 위해서는 먼저 좌표축 배치를 사용자 지정한 Figure를 생성합니다.
close all; hFigure = figure; hFigure.Position(3) = 900; hPanel1 = uipanel(hFigure,'Units','Normalized','Position',[0 1/4 1/2 3/4],'Title','Scenario Plot'); hPanel2 = uipanel(hFigure,'Units','Normalized','Position',[0 0 1/2 1/4],'Title','Chase Plot'); hPanel3 = uipanel(hFigure,'Units','Normalized','Position',[1/2 0 1/2 1],'Title','Bird''s-Eye Plot'); hAxes1 = axes('Parent',hPanel1); hAxes2 = axes('Parent',hPanel2); hAxes3 = axes('Parent',hPanel3);
좌표축을 정의했으면, 플롯을 생성할 때 이들 좌표축을 Parent
속성을 사용하여 지정합니다.
% assign scenario plot to first axes and add indicators for ActorIDs 1 and 2 plot(scenario, 'Parent', hAxes1,'ActorIndicators',[1 2]); % assign chase plot to second axes chasePlot(egoCar, 'Parent', hAxes2); % assign bird's-eye plot to third axes egoCarBEP = birdsEyePlot('Parent',hAxes3,'XLimits',[-200 200],'YLimits',[-240 240]); fastTrackPlotter = trackPlotter(egoCarBEP,'MarkerEdgeColor','red','DisplayName','target','VelocityScaling',.5); egoTrackPlotter = trackPlotter(egoCarBEP,'MarkerEdgeColor','blue','DisplayName','ego','VelocityScaling',.5); egoLanePlotter = laneBoundaryPlotter(egoCarBEP); plotTrack(egoTrackPlotter, [0 0]); egoOutlinePlotter = outlinePlotter(egoCarBEP);
이제 시뮬레이션을 다시 시작하여 끝까지 실행하되, 이번에는 targetPoses
를 통해 타깃 차량의 위치 정보를 추출하여 조감도 플롯에 표시합니다. 마찬가지로, 에고 차량에서 roadBoundaries
와 targetOutlines
를 직접 호출하여 도로 경계와 액터의 윤곽선을 추출할 수도 있습니다. 조감도 플롯은 이러한 함수의 결과를 직접 표시할 수 있습니다.
restart(scenario) scenario.StopTime = Inf; while advance(scenario) t = targetPoses(egoCar); plotTrack(fastTrackPlotter, t.Position, t.Velocity); rbs = roadBoundaries(egoCar); plotLaneBoundary(egoLanePlotter, rbs); [position, yaw, length, width, originOffset, color] = targetOutlines(egoCar); plotOutline(egoOutlinePlotter, position, yaw, length, width, 'OriginOffset', originOffset, 'Color', color); end
다음 단계
이 예제에서는 drivingScenario
객체를 사용하여 합성 센서 데이터 및 추적 알고리즘을 위한 ground truth를 생성하고 시각화하는 방법을 살펴봤습니다. 대화형 환경에서 이 주행 시나리오를 시뮬레이션하거나 시각화하거나 수정하려면 drivingScenario
객체를 주행 시나리오 디자이너 앱으로 가져와 보십시오.
drivingScenarioDesigner(scenario)
추가 정보
액터와 도로를 정의하는 방법에 대한 자세한 내용은 Define Road Layouts Programmatically 항목과 Create Actor and Vehicle Trajectories Programmatically 항목을 참조하십시오.
검출 및 트랙에 조감도 플롯을 사용하는 방법에 대한 자세한 예제는 Visualize Sensor Coverage, Detections, and Tracks 항목을 참조하십시오.
합성 데이터 생성에 주행 시나리오를 활용하는 예제는 Model Radar Sensor Detections 항목, Model Vision Sensor Detections 항목, Sensor Fusion Using Synthetic Radar and Vision Data 항목을 참조하십시오.
참고 항목
앱
객체
함수
vehicle
|actorPoses
|targetPoses
|road
|roadBoundaries
|updatePlots
|chasePlot
|record