OpenSCENARIO에 대해 알기 전 먼저 OpenX에 대해 알아야 한다. OpenX는 독일의 비영리 조직 ASAM(Association for Standardiztion of Automation and Measuring Systems)이 자동차 개발 및 테스트에서 툴체인(Tool Chain)의 표준화를 촉진하고자 만들었다. 여기서 툴체인은 소프트웨어 개발에 사용되는 프로그래밍 도구의 집합을 의미한다. 즉, OpenX는 자율주행 또는 운전자 보조 기능의 가상 개발, 테스트 및 검증에 사용되는 표준과 같다.
OpenSCENARIO는 그 중 시나리오를 위한 데이터 모델을 정의하는 내용이다. 시나리오는 운전 시뮬레이션에서 차량, 보행자 및 기타 교통 참여자와 같은 여러 개체가 관련된 복잡하고 동기화 된 상황을 설명하는 것으로 자율주행 알고리즘 검증에 중요한 역할을 한다.
ASAM OpenSCNEARIO : User Guide
OpenSCNEARIO는 계층 구조로 시나리오를 설명하기에 전체 구조와 각 항목의 의미를 정확히 이해하고 있어야 한다. 항목에 대한 설명은 아래 링크에서 참고할 수 있다.
ASAM OpenSCENARIO : ALL Classes
OpenSCENARIO에서 가장 상위 항목은 위와 같다. FileHeader에는 시나리오 파일의 저자, 작성 일자, 버전 등 시나리오의 전반적인 정보를 포함하고 있다. RoadNetwork는 시뮬레이션이 수행될 도로에 대한 정보를 포함한다. Entities는 시나리오에 등장하는 객체에 대한 정보를 포함하며, Storyboard는 Entities들이 수행할 행동들에 대한 정보를 나타낸다.
Entities는 Vehicle, Pedestrian, MiscObject로 총 3가지 유형의 ScenarioObject를 가질 수 있다. 각 유형은 하나의 객체를 설명하기 위한 속성(Axles, BoudingBox 등)을 설정할 수 있다.
Storyboard는 전체 시나리오에서 '누가', '무엇을', '언제', '어떻게'라는 설명을 다룬다.
Init은 Entity의 초기 위치, 속도 등 초기 상태를 설정하는 요소이다. Story는 다양한 시나리오를 상위 계층 구조로 그룹화 한 요소이다. StopTrigger는 시나리오의 끝을 결정하는 요소이다.
Story 의 하위 요소인 Act는 특정 시작 시점(StartTrigger)를 기준으로 한 여러 ManeuverGroup의 집합이다.
ManeuverGroup은 Actors와 Maneuver로 구성된다. 여기서 Actors는 Maneuver를 수행할 Entities를 의미하며, Maneuver는 Actors가 수행할 행동인 Event의 집합을 의미한다.
Event는 하위 요소로 Action과 StartTrigger를 갖는다. Action은 실질적인 행동이며, StartTrigger는 이 행동을 언제 수행할지에 대한 조건이다. 즉. StartTrigger가 충족되면 Actors는 Action을 수행한다. 이때, 각 Event마다 priority를 정해 실행 우선 순위를 결정할 수 있다.
Action은 3가지 유형이 있다. 시간, 날씨, 신호 등 Entity와 관련되지 않은 환경에 대한 행동을 결정할 수 있는 Global Action, Entity 개인의 행동을 결정할 수 있는 Private Action, 사용자가 직접 정의할 수 있는 User-defined Action이 있다.
지금까지 시나리오의 전체 구성 요소에 대해 간략하게 알아보았다. 하지만 실제 시나리오는 각 요소마다 가지고 있는 하위 클래스가 다양하기 때문에 위의 ALL Classes 링크를 참고하며 각 클래스의 의미를 이해할 수 있어야 한다.
위에서 살펴본 구성을 토대로 시나리오 예제를 한번 살펴보자.
위 그림은 예제 시나리오의 구성을 보여주고 있다. Ego 차량은 100km/h의 속도로 주행하고 있다. Ego 차량의 좌측 후방에서 달리는 TV 차량은 120km/h의 속도로 주행하고 있다. TV 차량이 Ego 차량을 추월하게 되면 차선 변경을 하고자 한다. 이때 두 차량 간의 간격은 Ego 차량이 TV 차량의 위치에 도달하는데까지 0.4초의 시간이 걸리는 거리이다. 차선 변경이 끝나면 5초 후에 시나리오를 종료한다.
이를 OpenSCENARIO 형식으로 나타내면 다음과 같다.
<?xml version="1.0" encoding="UTF-8"?>
<OpenSCENARIO>
<FileHeader revMajor="1" revMinor="1" date="2023-04-28T22:00:00" description="cut-in" author="seunghyun"/> <!-- 파일 정보 -->
<ParameterDeclarations> <!-- 변수를 선언하여 중복되는 값을 쉽게 사용할 수 있다. $name으로 사용한다. -->
<ParameterDeclaration name="WhiteCar" parameterType="string" value="car_white"/>
<ParameterDeclaration name="RedCar" parameterType="string" value="car_red"/>
</ParameterDeclarations>
<CatalogLocations> <!-- 미리 정의해둔 카탈로그를 불러와 사용할 수 있다. -->
<VehicleCatalog>
<Directory path="../xosc/Catalogs/Vehicles"/>
</VehicleCatalog>
</CatalogLocations>
<RoadNetwork> <!-- OpenDRIVE 형식으로 저장된 도로 정보를 불러온다. -->
<LogicFile filepath="../xodr/e6mini.xodr"/>
<SceneGraphFile filepath="../models/e6mini.osgb"/>
</RoadNetwork>
<Entities> <!-- 시나리오에 등장하는 객체를 정의한다. -->
<ScenarioObject name="Ego">
<CatalogReference catalogName="VehicleCatalog" entryName="$WhiteCar"/>
</ScenarioObject>
<ScenarioObject name="TargetVehicle">
<CatalogReference catalogName="VehicleCatalog" entryName="$RedCar"/>
</ScenarioObject>
</Entities>
<Storyboard>
<Init> <!-- Entities의 초기 상태를 정의한다. -->
<Actions>
<Private entityRef="Ego">
<PrivateAction>
<TeleportAction>
<Position> <!-- 위치를 정의한다. -->
<LanePosition roadId="0" laneId="-3" offset="0" s="50"/> <!-- 위에서 불러온 도로 정보를 기반으로 정의한다. -->
</Position>
</TeleportAction>
</PrivateAction>
<PrivateAction>
<LongitudinalAction>
<SpeedAction> <!-- 속도를 정의한다. -->
<SpeedActionDynamics dynamicsShape="step" dynamicsDimension="time" value="0.0"/>
<SpeedActionTarget>
<AbsoluteTargetSpeed value="${100 / 3.6}"/>
</SpeedActionTarget>
</SpeedAction>
</LongitudinalAction>
</PrivateAction>
</Private>
<Private entityRef="TargetVehicle">
<PrivateAction>
<TeleportAction>
<Position>
<LanePosition roadId="0" laneId="-2" offset="0" s="25"/>
</Position>
</TeleportAction>
</PrivateAction>
<PrivateAction>
<LongitudinalAction>
<SpeedAction>
<SpeedActionDynamics dynamicsShape="step" dynamicsDimension="time" value="0.0"/>
<SpeedActionTarget>
<AbsoluteTargetSpeed value="${100 / 3.6 * 1.2}"/>
</SpeedActionTarget>
</SpeedAction>
</LongitudinalAction>
</PrivateAction>
</Private>
</Actions>
</Init>
<Story name="CutInStory"> <!-- Entities가 수행할 행동을 정의한다. -->
<Act name="CutInAct">
<ManeuverGroup maximumExecutionCount="1" name="CutInSequence">
<Actors selectTriggeringEntities="false">
<EntityRef entityRef="TargetVehicle"/>
</Actors>
<Maneuver name="CutInManeuver">
<Event name="CutInEvent" priority="overwrite">
<Action name="CutInAction"> <!-- EntityRef에서 정의된 Entities가 수행할 행동을 정의한다. -->
<PrivateAction>
<LateralAction>
<LaneChangeAction> <!-- 횡방향 행동 중 차선 변경 행동을 의미힌다. -->
<LaneChangeActionDynamics dynamicsShape="sinusoidal" value="3" dynamicsDimension="time"/>
<LaneChangeTarget>
<RelativeTargetLane entityRef="Ego" value="0"/> <!-- Ego 차량과 동일한 차선을 의미한다. -->
</LaneChangeTarget>
</LaneChangeAction>
</LateralAction>
</PrivateAction>
</Action>
<StartTrigger> <!-- 위에서 정의한 Action을 수행할 시기를 정의한다. -->
<ConditionGroup>
<Condition name="CutInStartCondition" delay="0" conditionEdge="rising">
<ByEntityCondition>
<TriggeringEntities triggeringEntitiesRule="any">
<EntityRef entityRef="Ego"/>
</TriggeringEntities>
<EntityCondition> <!-- TimeHeadwayCondition은 트리거 Entity가 참조 Entity 위치에 도달하는 데 걸리는 시간으로 조건을 정의한다. -->
<TimeHeadwayCondition entityRef="TargetVehicle" value="0.4" freespace="false" coordinateSystem="road" relativeDistanceType="longitudinal" rule="greaterThan"/>
</EntityCondition>
</ByEntityCondition>
</Condition>
</ConditionGroup>
</StartTrigger>
</Event>
</Maneuver>
</ManeuverGroup>
<StartTrigger> <!-- Act가 시작되는 시기를 정의한다. -->
<ConditionGroup>
<Condition name="CutInActStart" delay="0" conditionEdge="none">
<ByValueCondition>
<SimulationTimeCondition value="0" rule="greaterThan"/> <!-- Act는 시뮬레이션이 시작되고 0초 이상에서 시작된다. -->
</ByValueCondition>
</Condition>
</ConditionGroup>
</StartTrigger>
<StopTrigger> <!-- Act가 끝나는 시기를 정의한다. -->
<ConditionGroup>
<Condition name="ActStopCondition" delay="5" conditionEdge="rising">
<ByValueCondition> <!-- 위에서 정의한 CutInManeuver가 끝나고 delay 5초 후에 종료된다. -->
<StoryboardElementStateCondition storyboardElementType="maneuver" storyboardElementRef="CutInManeuver" state="endTransition"/>
</ByValueCondition>
</Condition>
</ConditionGroup>
</StopTrigger>
</Act>
</Story>
<StopTrigger/>
</Storyboard>
</OpenSCENARIO>
이제 위에서 예제로 만든 시나리오 파일이 정상적으로 작동하는지 확인이 필요하다. 하지만 이렇게 xml만 봐서는 정확하게 동작하는지 확인하기가 어렵다. 이때 우리는 esmini라는 오픈 소스를 사용하여 직접 시나리오를 재생해보고 쉽게 확인해볼 수 있다. esmini란 OpenSCENARIO 형식의 파일을 재생할 수 있는 소프트웨어 툴이다. 다운 및 빌드는 아래 링크를 참고하면 된다.
Esmini
다운로드가 끝나면 esmini root 폴더로 이동한다. 여기서 resources/xosc로 이동해 새로운 시나리오 파일을 삽입한다. 다시 root 폴더로 돌아와 아래 명령어를 통해 시나리오를 재생한다.
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/file_name.xosc
이렇게 시나리오 파일이 정상적으로 재생되는 것을 확인할 수 있다.
안녕하세요! 해당 자료 참고하여 공부하고 있습니다. Esmini 본문 내용 중
다운로드가 끝나면 esmini root 폴더로 이동한다. 여기서 resources/xosc로 이동해 새로운 시나리오 파일을 삽입한다.
새로운 시나리오 파일을 삽입하는 방법을 조금 더 서술해주실 수 있으실까요...? ㅜㅜ 본문 따라서 코드 작성은 했는데 실행에 어려움을 겪고 있습니다,, 감사합니다!