Yuseung Kim
Projects

트랙터 자율주행 경로 설계

Robotics2026.08종합 3위
  • Python
  • 커버리지 경로계획
  • Pure Pursuit
  • Claude Code

대회 소개

2026년 여름에 서울대학교에서 열린 스마트농산업 SW기술 역량 강화 교육에 참가했다. 전국 지방 거점 국립대의 생물산업기계과, 스마트팜 학과에서 학교당 한두 팀씩 뽑아 2박 3일 동안 교육을 받고 해커톤을 하는 프로그램이다.

과제는 가상 트랙터로 지정된 밭을 최단시간에 최대한 많이 경작하는 것. 서울대에서 창업한 농업 AI 스타트업 agmo 의 시뮬레이터 SeamOS World 위에서 돌아갔고, 생성형 AI 를 쓰는 게 전제였다.

첫날은 교육 소개랑 일정 공지, 환경 셋업을 하고 바로 해커톤에 들어갔다. 11개 팀이 참가했고 우리 팀 이름은 '복현동 트랙터' 였다.

제출물은 두 가지다.

  1. 경로 파일(RDDF). 트랙터가 따라갈 웨이포인트 목록이다. 각 점에 좌표와 목표 속도, 작업기를 내렸는지 여부가 들어간다.
  2. 경로 추종 파라미터. 제어기를 처음부터 짜지는 않는다. 주최측 스켈레톤이 제공하는 추종 알고리즘 중 하나를 고르고, 에뮬레이터에서 돌려 가며 맞춘다.

트랙터와 작업기를 먼저 골라야 했다. 회전반경과 속도, 작업폭이 경로 설계를 통째로 좌우한다. 맵마다 답도 달라서, 뒤에 나올 m4 에서는 작업기를 medium 에서 large 로 바꾸는 것만으로 2.3점이 올랐다. 좁은 작업기는 레인을 촘촘히 깔 수 있는 대신 레인 수가 늘어 총 주행거리가 길어진다. 대신 견인 부담이 줄어 속도로 일부 갚는다. 맵마다 어느 쪽이 이기는지가 달랐다.

점수판부터 읽었다

맵은 네 개였고 최종 점수는 네 맵 점수의 합이었다.

맵이름면적
M01Open Fields1,500 m²
M02Sloped Acres1,948 m²
M03Patchwork Plots1,355 m²
M04Crooked Bottoms1,881 m²

채점식은 공개돼 있었다.

score = Σ 배점 × 달성도³      (6개 항목, 배점 합 100)

  cov   45   목표 면적 중 작업 비율
  dup   17   중복 경작    — 5% 이하 만점, 25% 이상 0점
  time  16   유효속도     — 3.5 km/h 만점, 0.5 km/h 이하 0점
  ret    9   재밟음       — 5% 이하 만점, 20% 이상 0점
  out    7   구역 이탈    — 0% 만점, 10% 이상 0점
  outt   6   이탈 시간    — 0% 만점, 10% 이상 0점

INVALID = 커버리지 50% 미만 · 수동주행 · 키입력 · 작업기 교체 · 마감 후 시작

중복 5% 이하, 재밟음 5% 이하처럼 만점 조건이 느슨한 항목은 조건만 지키면 배점을 통째로 가져간다. 그리고 커버리지를 뺀 다섯 항목이 합쳐 55점이다.

이를 토대로 전략을 세웠다

커버리지를 뺀 다섯 항목(Duplicate, Time, Retread, Outside, OutTime)을 만점으로 붙들어 놓고, 그 조건을 깨지 않는 선에서 커버리지를 올린다. 단 커버리지 50% 미만이면 INVALID 라 그 아래로는 못 내려간다.

우리가 돌린 루프

Claude Code 에 자료를 다 넣었다. 주최측 스켈레톤 저장소(master-of-plowing), 교육 자료 전부, 우리가 고른 트랙터와 작업기 제원. 그 위에서 세 단계를 반복했다.

  1. 맵 스크린샷에 경로를 손으로 그려 방향성을 제시한다.
  2. 생성된 .rddf 를 확인하고 필요하면 손본다.
  3. 에뮬레이터에서 주행하며 추종 파라미터를 튜닝하고 제출한다.

SeamOS World 에뮬레이터 화면. 왼쪽은 관제·조향 패널, 오른쪽은 주행 뷰 3번을 하는 화면. 왼쪽 패널에서 횡편차와 조향각 지령 대 실측을 보며 맞춘다.

처음엔 "밭 모양을 주면 알아서 최적 경로를 짜 줘" 쪽으로 접근했는데 잘 안 됐다. 임의로 작성된 경로는 트랙터의 선회 반경을 고려하지 않았거나 운용 경계를 넘는 것들이었다.

그래서 순서를 뒤집었다. 사람이 먼저 그리고, AI 가 그걸 좌표로 옮기게 했다.

시뮬레이터 화면 위에 파란 펜으로 그린 나선 루트 화면을 캡처해 위에 직접 그린 계획.

화면 픽셀과 월드 좌표는 빨간 작업영역 외곽선 폴리곤에 직접 적합시켰다. 8방향 후보 중 어느 회전이 외곽선과 가장 겹치는지 보는 방식인데 screen_x = −z · 13.908 + 880.2 가 98.5% 일치했다. 손그림 선은 색 마스크로 뽑아 중심선을 추적해 좌표열로 바꿨다.

생성된 나선 경로가 밭 경계 안쪽을 돌고 있다 손그림에서 만들어 낸 경로. 흰 화살표가 진행 방향, 초록 띠가 작업 구간.

도랑이 없는 맵과 있는 맵

M01 Open Fields 는 경계 바깥에 도랑이 없었다. 밖으로 나가도 안 빠지니까 C턴을 통째로 밭 밖에서 하는 경로를 썼다.

M01 의 지그재그 레인. C턴이 전부 빨간 경계선 바깥에서 이뤄진다 빨간 선이 작업영역. C턴이 전부 선 바깥에 나가 있다.

여기서는 Outside 와 OutTime 페널티(합 13점)를 일부러 먹는 게 이득이었다. M01 우승 팀은 아예 커버리지 93%를 가져가는 대신 OutTime 을 0점으로 버렸다.

M02, M03, M04 는 달랐다.

빨간 경계선과 그 바깥을 두르는 도랑 빨간 선 바깥의 어두운 띠가 도랑이다. 나가면 빠진다.

선회를 전부 밭 안에서 해야 하고, 여기서부터는 최소선회반경 6.5 m 가 계속 발목을 잡았다.

헤드랜드 턴은 네 가지 중에서 고른다. C형은 반원 하나로 옆 레인에 붙고, Ω형과 R형은 반원을 넘겨 고리를 그렸다가 되감아 들어가고, X형은 짧게 돌고 후진했다가 다시 도는 스위치백이다. C·Ω·R 은 회전반경만큼의 헤드랜드 깊이를 요구하고, X 는 얕은 헤드랜드에서도 되는 대신 후진 구간이 생긴다. 인접 레인이 안 되면 두세 열씩 건너뛰어 간격을 벌리고 건너뛴 열은 나중에 채운다.

경로 추종

제출물의 나머지 절반이다. 경로 파일이 어디로 갈지를 정한다면 추종은 그걸 어떻게 따라갈지를 정하고, 최종 점수는 둘의 곱으로 나온다. GPS 가 정확해도 트랙터는 관성과 조향각 한계, 타이어 슬립 때문에 계획선에서 벗어난다.

스켈레톤이 주는 선택지는 Pure Pursuit 와 Stanley 둘이었다.

Pure Pursuit 는 뒤축에서 경로 위로 전방주시거리 Ld 만큼 앞선 목표점을 찍고, 그 점으로 가는 원호의 곡률을 조향각으로 바꾼다.

κ = 2·y / Ld²        δ = atan(L·κ)

y  목표점의 차량 기준 좌측 오프셋 · L  휠베이스

Stanley 는 앞축에서 횡오차 e 와 헤딩오차 θe 를 재서 바로 더한다.

δ = θe − atan( k·e / (|v| + ε) )

k  게인 · ε  저속 보호항

스켈레톤의 Pure Pursuit 는 이 식 그대로는 아니었다. 곡률 피드포워드, 헤딩오차(HDE), 횡오차(LTD), 남은 PP 항을 0.7 로 섞고 yaw rate 오차를 감쇠하는 다섯 항의 가중합이다. HDE 와 LTD 는 항마다 ±0.22 rad 제한이 걸려 있어 게인을 계속 키워도 어느 지점부터는 궤적이 안 변한다.

둘을 번갈아 가며 같은 경로로 주행해 봤다. Pure Pursuit 는 큰 C형 선회에서 의도한 경로보다 일찍 돌아 들어갔다. Stanley 는 그 현상이 덜한 대신 주행 자체가 안정적이지 않았다. 잘라 도는 건 전방주시로 줄일 수 있어서 Pure Pursuit 로 갔다.

실제로 움직인 건 전방주시 하나

튜닝 탭에 파라미터가 일곱 개 있었는데 점수에 가장 영향이 큰 파라미터는 전방주시 시간이었다.

전방주시거리 = clamp( ppLookaheadTimeS × 속도, 5.0, 20.0 )

코드 기본값은 9.0 초다. 대형 트랙터에 3.6 m 쟁기를 달면 레인 지령을 5.5 km/h 로 줘도 견인 한계에 걸려 실측이 4.14 km/h 에서 멈춘다. 9.0 이면 10.35 m 앞을 보고 달리는 셈이고, Ld 가 길면 선회 진입이 늦어 원호 안쪽을 잘라 간다.

경로를 그대로 둔 채 이 값만 5.5 로 내렸다. 전방주시 6.32 m, M01 커버리지 90.3 → 93.2%, 총점 56.6 → 58.4. 4.5 초 아래로는 사행이 돌아온다고 코드 주석에 적혀 있어 거기서 멈췄다.

오차가 나는 자리

로그에서 계획 경로 대비 오차를 구간별로 갈라 봤다.

구간평균중앙p90최대
레인0.520.381.221.69 m
선회0.560.511.111.88 m

선회 오차는 원호가 시작되는 지점을 0 으로 놓고 다시 그리면 위치가 분명하다. 음수가 아직 직선 구간, 양수가 원호 안이다.

원호 시작 기준−6 m−4 m0+4 m+6 m+10 m+16 m
평균 오차0.410.540.851.451.521.270.65 m

직선에서 원호로 들어가는 순간 곡률이 0 에서 1/R 로 계단처럼 걸린다. 최소선회반경 6.5 m 는 조향을 끝까지 감았을 때 나오는 반경이라, 그 코너를 돌려면 조향각이 최대치인 25.5도까지 가 있어야 한다. 조향은 13.2도/s 로 움직여서 다 감는 데 1.93초가 걸리고 그동안 차는 2.4 m 를 간다. 원호 앞부분은 덜 감긴 조향으로 지나간다.

전방주시 5.3 m 가 같은 자리에 겹친다. 목표점이 차보다 5.3 m 앞이라 차가 아직 직선에 있을 때 목표점은 이미 원호 안이고, 원호에 들어선 뒤에도 한동안 앞의 점을 쫓아간다. 오차가 직선 수준으로 돌아오는 건 원호로 16 m 쯤 들어간 뒤다.

직선과 원호 사이에 곡률이 0 에서 1/R 까지 일정한 비율로 올라가는 구간을 끼우면 조향을 미리 나눠서 감을 수 있다. 클로소이드 전이다. 전이 길이 3.2 m 를 선회 속도 4.5 km/h 로 지나면 2.6초가 생기고, 그 안에 25.5도를 감는 데 필요한 속도는 9.9도/s 다. 조향 한계 13.2도/s 의 75% 라 여유가 있다.

대신 선회가 커진다. 완전히 감기기 전에 더 나아가므로 코너가 밭 안쪽으로 더 깊이 들어가고 길어진다. R 6.5 기준 헤드랜드 깊이 6.51 → 8.16 m, 코너 하나 길이 10.2 → 13.4 m. 헤드랜드를 1.65 m 더 내주면 작업 면적이 그만큼 줄고, 선회마다 3.2 m 를 더 달리면 Time 이 깎인다. 도랑이 있는 맵에는 그 여유가 없었다.

추종 오차는 커버리지로 들어온다

M01 은 레인 간격 3.382 m 에 작업폭 3.6 m 라 설계 겹침이 0.218 m 였다. 레인 샘플의 93%가 그 절반을 넘는 횡오차를 냈고, 미경지 193 m² 중 121 m² 가 레인과 레인 사이에서 나왔다.

인접 레인의 상대 횡변위를 N(0, 0.9²) 로 두고 이음매 미경지를 예측하니 12레인에서 109 m², 실측은 102 m² 였다. 레인을 13개로 늘려 겹침을 0.5 m 로 벌리면 75 m² 까지 내려가지만 늘어난 레인만큼 Time 을 낸다. 전방주시를 줄이는 쪽은 시간을 안 먹었다.

시행착오

시뮬레이터의 트랙터는 실제 트랙터와 달랐다. 현실의 트랙터는 거의 다 제자리 회전이 되는데 SeamOS World 의 트랙터는 안 됐다. 이는 회전반경보다 좁은 구역은 경작을 못하는 문제를 일으켠다.

그래서 후진을 포함한 경로를 만들어 에뮬레이터에 올렸다. 계획은 선회 없이 18.5 m 곧게 후진하면서 방위는 −20.2도만 감는 것이었는데, 실제로는 −169도를 감았다. 8.5배다.

 t=50.5  (-15.6,-28.3) hdg 221.8   여유 3.42   ← 후진 시작, 계획대로 북동진
 t=62.2  (-12.4,-23.6) hdg 168.2   여유 4.86   ← 11.7초 만에 54도 감김
 t=81.2  (-20.4,-17.5) hdg  52.9   여유 -0.00  ← 도랑

118초 만에 중단. 진행률 9.1%, 경계 밖 체류 40.8초.

m4 맵의 손그림 3단 루트

원인은 경로가 아니었다.

앱의 경로추종 알고리즘 문제였다. Pure Pursuit 는 뒤축 앞에 목표점을 찍고 거기로 가는 원호를 그리는데, 후진에서는 차가 그 원호를 반대로 되짚는다. 전진에서 횡오차를 줄이던 조향이 후진에서는 키우는 쪽으로 걸린다. 커스프에 남아 있던 곡률과 각도 오차가 그대로 커진다.

증폭될 조건이 셋 있었다. 지령 자체에 −20도짜리 에르미트 호가 들어 있었고, 커스프가 정확히 180도가 아니라 6.2도 어긋나 있었고(전진 225.5 대 후진 39.2), 조향동결 보정이 후진 속도를 2.14 km/h 까지 낮춰 놨다. 느리게 갈수록 미터당 더 감긴다.

곡률 0.38도에 커스프 180.00도인 직선 후진 판을 다시 만들어 검증은 통과시켰지만, 15 m 를 발산 위험에 노출시키는 건 그대로였다.

여기에 시간과 토큰을 꽤 많이 썼는데, 결론은 우리가 고칠 수 있는 문제가 아니라는 거였다. 경로 파일이 아니라 앱 쪽 한계였고 손대려면 앱 안의 C++ 코드를 수정해야 했다.

팀에서 논의한 뒤 후진을 선택지에서 아예 뺐다. X형 선회를 버리고 C형 선회를 주력으로 경로 생성을 요청했다.

채울수록 점수가 떨어진다

미경작 구역 세 곳을 표시한 커버리지 분석 남은 미경작 구역 ①②③.

중앙에 남은 미경작 179 m² 는 한 덩어리가 아니라 흩어져 있었다. 직선으로 가로지르면 쟁기가 지나는 108 m² 중 새로 덮는 건 53 m² 뿐이고 나머지 55 m² 는 이미 간 땅을 다시 간다.

Coverage 는 58% 구간에서 1%p 당 0.45점이 오른다. Duplicate 는 4.1%에서 13.1%로 가면 17점 중 14점이 날아간다. 계산해 보니 보충 패스 1개는 −1.1점, 3개는 −10.0점이었다. 중앙 채우기는 화면 진척률만 올리고 점수는 깎는 짓이었다.

경로 간격도 비슷했다. 작업폭이 2.2 m 니까 그 간격으로 촘촘히 붙이는 게 당연해 보이는데, 2.45 m 가 5.5점 더 높았다. Retread 만점 조건이 간격 − 1.863 ≥ 0.58 이라 2.45가 정확히 그 문턱이다. 2.2로 좁히면 커버리지가 1.8%p 오르는 대신 Duplicate 가 7.7에서 11.2%로, Retread 가 3.9에서 5.6%로 같이 무너진다.

M02 전략별 결과

커버리지커버리지 점수나머지 다섯 항목총점
커버리지 우선 경로78%21.127.248.3
최종 제출 경로67%13.555.068.3

커버리지를 11%p 포기하고 20점을 벌었다. 아래 판이 M02 맵 1위 기록이 됐다. 주행 8분 14초, 작업 1,297 m².

결과, 11팀 중 3위

3일차 오전까지 선두였다. 최종은 네 맵 합산 250.7점으로 3위, 장려상을 받았다. 1위가 254.2점이었다.

SeamOS 리더보드 최종 순위
맵별 순위와 최종 종합 순위.
스마트농산업 SW 실습 프로그램 장려상 상장
맵우리 점수순위
M01 Open Fields68.1—
M02 Sloped Acres68.31위
M03 Patchwork Plots53.8—
M04 Crooked Bottoms60.51위
합계250.73위

두 경작지에서 1위를 했지만 종합 3위로 마무리되었다. M03 이 53.8점으로 경쟁자들보다 뒤쳐졌었고 벌어진 그 3점이 종합 3.5점 차와 거의 같다.

M04 의 경우에도 461 m² 중 325 m² 가 경계에서 6 m 이내의 띠였는데, 최소선회반경 6.5 m 로는 경계의 뾰족한 돌출부를 따라갈 수 없었다.

아쉬웠던 점

순위를 놓친 원인은 경로 설계도 파라미터 튜닝도 아니었다. Claude Code 의 컨텍스트 분량을 조절하지 못한 게 컸다. 자료를 통째로 밀어 넣고 긴 대화를 계속 이어 가다 보니 요청을 몇 번 하지도 않았는데 5시간 사용 한도에 먼저 닿았다.

그 토큰의 상당량을 고칠 수 없는 문제에 썼다는 게 더 아프다. 후진 급선회를 쫓아다닌 시간이 그렇다. 결국 앱 한계라는 결론에 닿긴 했는데, 거기 도달하는 데 쓴 비용이 다른 맵을 다듬을 기회를 줄였다. 끝내 못 살린 M03 이 정확히 그 자리에 있다.

방법이 없던 것도 아니다. 확정된 결론을 짧은 문서로 정리해 두고 새 세션을 그 문서로 시작하면 됐다. 대회 후반에 만든 주행 기록 문서가 실제로 그 역할을 했는데 더 일찍 그렇게 했어야 했다.