주요 콘텐츠

프로그래밍 방식으로 주행 시나리오 만들기

이 예제에서는 합성 센서 데이터 및 추적 알고리즘을 위한 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를 통해 타깃 차량의 위치 정보를 추출하여 조감도 플롯에 표시합니다. 마찬가지로, 에고 차량에서 roadBoundariestargetOutlines를 직접 호출하여 도로 경계와 액터의 윤곽선을 추출할 수도 있습니다. 조감도 플롯은 이러한 함수의 결과를 직접 표시할 수 있습니다.

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 항목을 참조하십시오.

참고 항목

객체

함수

도움말 항목