Post

Global Trajectory Optimization: IQP·SP 레이스라인과 속도 프로파일

Global Trajectory Optimization: IQP·SP 레이스라인과 속도 프로파일

레이싱에서 성능을 가르는 핵심 지표는 랩타임이며, 같은 트랙이라도 어떤 라인으로 주행하느냐에 따라 랩타임은 크게 달라집니다. 맵 정보에서 추출한 Centerline은 트랙의 한가운데를 지나는 선일 뿐, 어떻게 하면 빠르게 통과할 수 있는지에 대한 정보는 담고 있지 않습니다. 랩타임을 줄이려면 코너의 회전 반경을 키워 곡률을 낮추거나 이동 거리를 줄여야 합니다. 본 포스트에서는 추출된 트랙 데이터(센터라인 + 좌/우 폭) 위에서 실제로 주행할 global trajectory를 생성하는 과정을 다룹니다.

개요

Centerline과 Wall boundary 추출이 끝나면, 같은 노드에서 성격이 다른 두 가지 global line을 생성합니다.

두 가지 global line

  • IQP (최소 곡률, 메인 경로): 전체 경로의 곡률을 최소화하여 코너를 넓고 부드럽게 통과하며, 보통 가장 짧은 랩타임을 보입니다. 이 스택에서 메인으로 추종하는 라인입니다.
  • SP (최단 거리, 보조 경로): 이동 거리를 최소화하여 안쪽 벽에 가까운 라인으로, 추월용 단축 경로로 활용됩니다.

IQP와 Centerline 비교 IQP와 Centerline 비교

최적화 기법: alpha 파라미터화

alpha 파라미터화

경로를 x·y 좌표로 직접 다루면 최적화 변수가 점마다 2개씩 늘어나고, 경로가 트랙 안에 머물러야 한다는 제약도 복잡해집니다. 이를 단순화하기 위해 경로를 Centerline 위의 각 지점에서 법선 방향으로 얼마나 벗어났는지를 나타내는 변위 $\alpha(s)$ 하나로 표현합니다.

\[p_{race}(s) = p_{center}(s) + \alpha(s)\, n(s), \quad -w_r \le \alpha \le w_l\]

이렇게 표현하면 최적화가 두 가지 측면에서 단순해집니다.

  • 변수 축소: 각 지점의 최적화 변수가 $(x, y)$ 2개에서 $\alpha$ 하나로 줄어듭니다.
  • 제약 단순화: 트랙을 벗어나지 않아야 한다는 조건이 좌/우 폭 범위 안에 $\alpha$ 를 두는 단순한 부등식 제약($-w_r \le \alpha \le w_l$)이 됩니다.

두 경로를 푸는 방식

최적화 파이프라인

트랙 데이터는 prep_track(리샘플·스플라인·법선 계산)을 거친 뒤 IQP와 SP 두 경로로 각각 최적화되며, 두 결과가 함께 global_waypoints.json에 저장됩니다. 두 경로는 같은 $\alpha$ 표현을 공유하되, 서로 다른 목적함수를 최소화합니다.

  • IQP (최소 곡률): 전체 곡률의 제곱합을 최소화합니다.
    • 곡률 항이 $\alpha$ 에 대해 비선형이므로, 매 반복마다 현재 해 주변에서 선형화해 QP로 푸는 과정을 반복합니다(Iterative QP).
  • SP (최단 거리): 인접 점 사이 거리의 제곱합을 최소화합니다.
    • 목적함수가 $\alpha$ 에 대해 선형이므로 QP를 한 번만 풀어 해를 얻습니다.
\[\min_{\alpha} \sum_i \kappa_i(\alpha)^2 \qquad \min_{\alpha} \sum_i \lVert p_{i+1} - p_i \rVert^2\]

IQP 라인 IQP 라인

SP 라인 SP 라인

최적화 후 dist_to_bounds로 각 점의 좌/우 트랙 폭(d_left/d_right)을 실제 벽(watershed 경계) 기준으로 다시 계산해 함께 저장합니다.

생성된 경로에 속도 입히기 (Velocity profile)

관련 코드: race_utils/f110_utils/libs/vel_planner/

IQP·SP 최적화는 속도를 고려하지 않고 곡률이나 이동 거리 같은 기하학적 기준만으로 경로의 모양을 결정합니다. 따라서 최적화 결과는 “어디로 주행할지”만 담고 있을 뿐, “각 지점에서 얼마나 빠르게 달릴지”는 정해져 있지 않습니다. 실제 주행을 위해서는 이 경로 위에 각 지점의 목표 속도를 지정하는 Velocity Profile을 추가로 입히는 과정이 필요하며, 이는 IQP·SP 경로 최적화 직후 같은 노드에서 함께 수행됩니다.

각 지점의 목표 속도는 임의로 정할 수 없고, 차량이 실제로 낼 수 있는 물리적 한계 안에서 결정됩니다. 이 한계는 크게 두 가지입니다.

  • 코너 (마찰원): 곡률이 클수록 필요한 횡가속이 커지므로, 타이어 접지 한계 안에서 낼 수 있는 속도가 낮아집니다.
    • ggv.csv: 속도별 횡·종 가속 한계를 담은 마찰원 정보
  • 가감속 (엔진): 직선에서 언제 감속을 시작하고 어디서 다시 가속할지를 결정하며, 가속과 감속의 한계가 각각 분리되어 있습니다.
    • ax_max_machines.csv: 속도별 최대 가속 한계
    • b_ax_max_machines.csv: 속도별 최대 감속(제동) 한계

각 지점의 곡률에 마찰원 한계를 적용해 코너 속도를 정하고 여기에 엔진의 가감속 한계를 반영해 감속·가속 구간을 연결하면 경로 전체의 속도 프로파일이 완성됩니다. 프로파일을 계산하는 구체적인 방법은 별도의 Velocity Planner 포스트에서 자세히 다룹니다.

위 세 파일은 stack_master/config/<racecar_version>/veh_dyn_info/ 아래에 위치하며, 시뮬레이션은 SIM, 실차는 CAR 폴더의 값을 사용합니다. 동일한 파일들은 추후 Velocity Planner 튜닝에도 활용됩니다.

속도 프로파일까지 계산되면, 각 웨이포인트는 다음 필드를 갖추어 global_waypoints.json에 저장됩니다. 위치(x_m, y_m), 진행거리·횡변위(s_m, d_m), 좌/우 폭(d_right, d_left), 자세·곡률(psi_rad, kappa_radpm), 그리고 속도 프로파일(vx_mps, ax_mps2)입니다.

1
[id, s_m, d_m, x_m, y_m, d_right, d_left, psi_rad, kappa_radpm, vx_mps, ax_mps2]

속도를 트랙 전체에 적분하면 추정 랩타임(로그의 Estimated laptime)이 계산됩니다. f맵에서는 IQP 17.12s, SP 19.77s로, SP는 이동 거리가 짧아도 코너 곡률이 커 속도가 낮아지므로 더 느린 랩타임을 보임을 알 수 있습니다.

IQP raceline과 속도 마커, Wall boundary (RViz 3D) IQP raceline과 속도 마커, Wall boundary (RViz 3D)

위 그림의 노란색 마커 높이가 종방향 속도(vx)입니다. 코너 정점에서 마커 높이가 가장 낮아지고, 코너 진입 전부터 미리 감속했다가 코너를 벗어나며 다시 가속하는 양상을 확인할 수 있습니다.

ggv.csv는 실측값에 가까울수록 더 현실적인 가감속 프로파일을 만들지만, UNICORN Racing 팀은 이 파일을 실측값 그대로 두기보다 주요 튜닝 파라미터로 삼아 조정하며 활용하고 있습니다.

실행 (UNICORN Racing Stack)

IQP·SP 최적화는 Centerline·Wall boundary 추출과 같은 노드에서 이어서 수행되며, raceline_generator.launch.xml을 실행하여 추출부터 최적화까지 한 번에 진행할 수 있습니다.

1
2
3
4
5
ros2 launch stack_master raceline_generator.launch.xml map:=f sim:=true
# map:=맵 이름
# 실차일 경우 sim:=false (CAR config) / reverse:=true / show_plots:=true
# enable_mintime:=true 로 주면 메인 라인을 최소곡률 대신 최소시간(mintime)으로 최적화
# → maps/f/global_waypoints.json (IQP 메인 + SP 보조)

기본적으로 최소곡률(mincurv_iqp) 방식으로 최적화하지만, enable_mintime 인자를 켜면 차량 동역학을 반영한 최소시간(Mintime Optimization) 최적화로 변경이 가능합니다.

실행 결과

map:=f로 실행하면 Centerline·Wall boundary 추출에 이어 IQP·SP 최적화가 수행되며, 그 과정이 아래와 같은 로그로 출력됩니다. 최적화는 메인 라인인 IQP를 먼저 계산한 뒤 보조 라인인 SP를 이어서 계산하는 순서로 진행됩니다.

1
2
3
4
5
[GB Planner]: Start Global Trajectory optimization (mincurv_iqp)...
[GB Planner]: Lap Completed now publishing global waypoints
[GB Planner]: Start reverse Global Trajectory optimization with shortest path...
[GB Planner]: Done with shortest path optimization
[GB Planner]: Lap Completed now publishing shortest path global waypoints

함께 출력되는 요약에는 각 라인의 추정 랩타임과 최고 속도가 담깁니다. 예를 들어 f맵에서는 IQP estimated lap time: 10.5857s; IQP maximum speed: 6.6802m/s; SP estimated lap time: 14.8311s; SP maximum speed: 9.6614m/s와 같이 출력되며, IQP의 랩타임이 더 짧음을 알 수 있습니다.

IQP 라인 확인

최적화된 메인 raceline(주황)이 Wall boundary, Centerline, 차폭과 함께 출력됩니다. 코너를 크게 돌아 곡률을 낮추며, 차폭(안전폭 포함)이 벽 내부에 위치하는지를 확인할 수 있습니다.

IQP 궤적과 차폭 IQP 궤적과 차폭

주황은 IQP raceline, 점선은 Centerline, 바깥 실선은 Wall boundary, 안쪽 두 선은 차폭(실제/안전)을 나타냅니다. safety_width는 실제 차량 폭에 안전 여유를 더한, 최적화가 확보하려는 유효 폭입니다. 이 값을 키우면 라인이 벽에서 더 큰 여유를 확보합니다.

safety_width가 트랙 폭을 넘어설 만큼 커지면 차량이 지나갈 경로 자체가 존재하지 않아 최적화가 실패하고 노드가 종료됩니다. 따라서 값을 조정할 때는 트랙 폭이 허용하는 범위 안에서 조금씩 키워야 합니다.

SP 라인 확인

SP는 IQP와 달리 라인을 최대한 직선으로 유지하며 전체 경로의 길이를 최소화합니다.

SP 궤적과 차폭 SP 궤적과 차폭

Sector 지정

최적화 직후 sector_slicer·ot_sector_slicer가 실행되어 트랙을 구간별로 나눌 수 있습니다. 슬라이더로 S 위치를 옮기고 Select S(일반 섹터)·Select Overtaking S(추월 섹터)로 경계 점을 지정한 뒤 Done으로 저장합니다.

추월 섹터 창에서는 IQP(빨강)·SP(파랑)·경계(초록)가 함께 보여, 추월이 유리한 구간(직선·넓은 코너)을 보면서 경계를 정할 수 있습니다. 결과는 speed_scaling.yaml·ot_sectors.yaml로 저장됩니다.

생성물

maps/f/에 저장되는 결과물은 다음과 같습니다.

  • global_waypoints.json: IQP line, SP line, Centerline, Wall boundary, 추정 랩타임
  • speed_scaling.yaml · ot_sectors.yaml: 섹터 지정 결과

주요 파라미터 설정: global_planner_params.yaml

IQP·SP 최적화의 동작은 대부분 stack_master/config/global_planner_params.yaml의 파라미터로 조정되며, 이 값들은 런치 파일을 통해 노드로 전달됩니다.

1
2
3
4
5
6
7
# stack_master/config/global_planner_params.yaml
global_planner:
  ros__parameters:
    required_laps: 1               # 최적화 전 주행 랩수
    safety_width: 1.0              # IQP 메인 raceline 안전폭 [m] (차폭 포함)
    safety_width_sp: 1.0           # SP 단축경로 안전폭 [m]
    occupancy_grid_threshold: 10   # 자유공간 판정 임계값 (맵 이진화)

raceline의 진행 방향을 반전하는 reverse_mapping은 런치 인자(reverse:=true)로 전달됩니다.

각 파라미터가 최적화에 미치는 영향은 다음과 같습니다.

파라미터역할키우면 / 줄이면
safety_width차량 폭에 안전 여유를 더한 유효 폭. 이만큼 트랙 폭을 좁혀 alpha 제약에 반영키우면 벽에서 더 멀어져 안전하지만 코너 회전 반경이 작아져 랩타임 증가(트랙 폭을 넘으면 최적화 실패) / 줄이면 벽에 근접
safety_width_spSP 단축경로의 유효 폭. 동일한 차량이므로 safety_width와 같은 값으로 설정위와 동일, SP 라인에만 적용
reverse_mappingraceline의 s 진행 방향을 반전방향이 반대로 설정됐을 때 true

라인이 코너에 지나치게 가깝거나 실행 로그의 Minimum distance to boundaries 경고가 너무 작게 나오면 벽과의 충돌 위험이 높아지므로, safety_width를 조금씩 높여 다시 최적화하는 것이 좋습니다. 다만 값을 한 번에 너무 크게 잡으면 차량이 지나갈 경로가 사라져 최적화가 실패하고 노드가 종료되므로, 트랙 폭이 허용하는 범위 안에서 조금씩 조정합니다. 반대로 라인이 지나치게 보수적이라면 safety_width를 조금 낮춰 코너에 더 가까운 라인을 얻어 랩타임을 낮출 수 있습니다.

(선택) raceline 수동 편집

관련 런치: raceline_tuner.launch.xml

RViz 인터랙티브 튜너 뷰 RViz 인터랙티브 튜너 뷰

raceline_tuner.launch.xml은 최적화된 경로를 특정 구간에 대해서만 변화를 주고 싶을 때 사용할 수 있는 노드입니다. 코너 라인을 옮기거나 구간 속도를 낮추고, 들쭉날쭉한 부분을 정리하는 등 경로와 속도를 인터랙티브하게 편집합니다. 이 런치는 트랙 튜너와 함께 RViz 배경용 맵 발행, 섹터 슬라이서(speed_scaling / ot_sectors)를 한 번에 실행합니다.

UNICORN Racing 팀은 마찰 타원과 곡률에 기반한 vel planner를 사용하고 있어, 해당 노드는 잘 사용하지 않습니다.

이 구성 요소들이 하나로 묶여 있는 이유는 파이프라인이 global_waypoints.json에 강하게 의존하기 때문입니다. 튜너로 궤적을 수정하면 기존 궤적을 기준으로 계산해 둔 speed_scaling.yaml·ot_sectors.yaml도 더 이상 유효하지 않으므로, 함께 실행된 섹터 슬라이서가 저장 시점에 섹터를 다시 계산합니다.

실행

1
2
3
ros2 launch stack_master raceline_tuner.launch.xml map:=f
# 튜너 + 맵 발행 + 섹터 슬라이서 + RViz가 한 번에 실행됩니다.
# 로그에 "Initialize NNN waypoints as interactive markers" 가 떠야 정상입니다.
1
2
3
[INFO] READ_GLOBAL_WAYPOINTS: Reading global waypoints from .../maps/f/global_waypoints.json
[global_traj_tuner_node]: Reading parameters from f
[global_traj_tuner_node]: Initialize 762 waypoints as interactive markers.

주의: 튜너는 /global_waypoints를 발행하며, 주행 스택(race.launch.xml / base_system.launch.xml)도 같은 토픽을 사용합니다. 두 스택을 동시에 실행하면 같은 토픽을 두고 충돌할 수 있으니, 이 점을 인지하고 기존 주행 런치를 종료한 뒤 raceline_tuner.launch.xml만 단독으로 실행합니다.

RViz 화면

RViz는 전용 설정(raceline_tuner.rviz)으로 함께 실행되므로 수동 설정 없이 바로 편집 화면이 표시됩니다. 속도가 마커 높이(z)로 표현되므로 시점은 Orbit(3D)으로 두며, 인터랙티브 마커는 /track_info_interactive 네임스페이스로 발행됩니다.

화면에는 보라색 구(웨이포인트)와 일정 간격마다 놓인 수직 드래그 핸들, 그리고 속도 라벨(예: v : 6.68)이 표시됩니다.

조작: 드래그 후 우클릭 메뉴

편집은 마커를 드래그해 위치나 속도를 바꾼 뒤 우클릭 메뉴로 명령을 적용하는 방식으로 이루어집니다. 속도는 핸들을 위/아래로 드래그해 조정하며(높이가 속도에 해당), 위치와 구간 단위 명령은 우클릭 메뉴에서 실행합니다.

메뉴동작
pose_smooth ▸ 10/20/30/idx우클릭한 점 주변 위치를 cubic spline으로 평활화 (psi/kappa/좌우폭 자동 재계산)
vel_smooth ▸ 10/20/30/idx속도 평활화 (idx = 직접 입력)
Anchor1 / Anchor2구간 시작/끝 앵커 지정 (아래 명령들이 두 앵커 사이에 적용)
Pos_Straighten두 앵커 사이 위치를 직선으로 정렬
Vel_Straighten두 앵커 사이 속도를 선형 보간
Vel_Set팝업 입력값으로 두 앵커 사이 속도를 일괄 설정
entire_translation / entire_rotation전체 경로 평행이동 / 회전
int_idx핸들·라벨 표시 간격(sampling_step) 변경
Control-Z실행 취소 (최대 5단계)
Update편집본을 /global_waypoints로 발행 (디스크 저장 없이 확인용)
Save편집본을 발행하면서 global_waypoints.json에 저장. 함께 실행 중인 섹터 슬라이서가 이를 받아 speed_scaling.yaml·ot_sectors.yaml도 다시 계산해 저장

예시: 특정 구간 속도 높이기

  1. 파란색 화살표를 드래그해 해당 점의 속도를 높입니다.
  2. 해당 waypoint를 우클릭한 뒤 vel_smooth를 선택합니다.
  3. smoothing 파라미터를 선택합니다.
  4. 해당 waypoint 주변의 velocity profile이 완만해집니다.
  5. 결과에 만족하면 우클릭 → Save (확인만 할 경우 Update).

편집 내용은 Save를 눌러야 디스크에 반영되며, 누르지 않으면 노드 종료 시 편집 내용이 사라집니다. Save를 누르면 수정된 궤적이 global_waypoints.json에 저장되는 동시에 함께 실행 중인 섹터 슬라이서로 업데이트된 /global_waypoints가 전달되어, 바뀐 궤적에 맞춰 섹터가 새로 나뉩니다. 편집 전에는 기존 라인을 백업해 두는 것이 안전합니다(cp maps/f/global_waypoints.json maps/f/global_waypoints.json.bak). /global_waypoints/markers(노란 raceline)는 Update 또는 Save를 실행해야 채워집니다.

마무리

전체 워크플로는 다음과 같습니다. 추출된 트랙 데이터가 prep_track을 거쳐 IQP·SP로 최적화되고, dist_to_bounds로 좌/우 폭이 더해진 뒤 global_waypoints.json으로 저장됩니다. 필요에 따라 수동 튜닝으로 특정 구간을 보정하는 것도 가능합니다.

전체 워크플로 (mapping → raceline_generator → raceline_tuner(선택) → race) 전체 워크플로 (mapping → raceline_generator → raceline_tuner(선택) → race)

이렇게 생성된 global_waypoints.json은 런타임에 global_trajectory_publisher를 통해 global waypoints 토픽으로 재발행되며, 주행 시 state machine과 각종 플래너로 전달되어 레이싱의 중심 경로로 작용합니다.

This post is licensed under CC BY 4.0 by the author.