Devin.KR

궤적과 실패 영상

100분 안팎

학습 목표

표준 라이브러리로 시각·로봇·장애물·상태를 담은 SVG 프레임을 생성합니다.

개념

실패를 보여 주는 프레임은 기록의 또 다른 표현입니다

성공률 숫자만 있으면 로봇이 어디에서 어떤 상태로 멈췄는지 파악하기 어렵습니다. 반대로 매끄러운 영상만 있으면 조건과 원본 기록이 빠지기 쉽습니다. 이 레슨에서는 상태 로그를 다시 읽어 SVG 연속 프레임을 만들고 각 프레임을 시각과 해시로 연결합니다. 그림을 생성하기 위해 주행을 다시 계산하지 않으며 이미 저장한 상태를 표현합니다.

이번 렌더러는 표준 라이브러리의 json, pathlib, html, XML 파서를 사용합니다. 영상 코덱이나 그래프 패키지를 설치하지 않습니다. SVG는 이미지 파일이므로 연속 프레임 생성과 영상 제출은 다른 완료 항목입니다. 자동 검사에서는 좌표와 필드, 파일을 확인하고 실제 실패 영상의 설명은 사람이 검토합니다. 제출 기준의 이 구분을 보고서에 명시합니다.

시각과 좌표의 의미를 화면에 넣습니다

앞 모듈 상태 로그의 at_us는 이벤트를 처리한 가상 시각입니다. tick 처리 뒤에는 자세가 한 제어 주기만큼 적분되어 pose_us가 더 클 수 있습니다. 그림에 둘을 함께 적어 sensor 이벤트의 수신 시각과 이동 후 자세 시각을 혼동하지 않게 합니다. 모든 프레임 제목에는 상태와 최종 결과의 reason도 넣되 reason이 해당 순간의 새 오류라는 뜻이 아님을 표시합니다.

파란 원은 truth_pose의 위치이며 원의 반경은 0.15m입니다. 주황 점은 estimated_pose이고 파란 선은 yaw 방향입니다. 회색 사각형은 지도 점유 셀입니다. 참값과 추정값을 같은 색으로 겹쳐 그리면 추정 오차가 작아진 것처럼 보일 수 있습니다. 범례는 색뿐 아니라 역할을 문자로 설명하여 정지 사진에서도 읽을 수 있게 합니다.

SVG 화면의 y는 아래로 증가하지만 지도 y는 위로 증가합니다. xy 함수는 지도 원점 -0.35m와 240px/m 배율을 반영하고 화면 높이를 기준으로 y를 뒤집습니다. 위치 (0m,0m)는 화면 (124px,476px)이며 x가 0.1m 증가하면 화면 x가 24px 증가합니다. 배율이나 원점을 바꿀 때 로봇과 장애물에 같은 함수를 적용합니다.

격자 장애물의 사각형 y는 셀의 위쪽 경계를 변환해 구합니다. 화면 사각형은 왼쪽 위에서 아래로 그려지기 때문입니다. 행과 열은 y와 x에 대응하며 장애물 한 칸은 24px 폭과 높이입니다. 로봇 원의 크기가 장애물 칸 크기에 비해 지나치게 작다면 반경을 미터에서 픽셀로 바꾼 계산이 빠졌는지 먼저 살펴봅니다.

프레임을 고르는 정책을 공개합니다

전체 로그에는 같은 시각에 goal, sensor, tick이 여러 줄로 존재할 수 있습니다. 모든 줄을 이미지로 만들면 움직이지 않는 비슷한 화면이 반복됩니다. select_frames는 tick만 고르고 그중 다섯 개 간격으로 선택합니다. 보통 0.25초 간격이지만 마지막 상태를 추가할 때는 그보다 짧아질 수 있으므로 프레임 번호를 시간으로 대신하지 않습니다.

마지막 상태는 사건의 결론입니다. 센서 누락은 tick 이전 sensor 이벤트에서 ABORTED가 될 수 있으므로 마지막 tick만 저장하면 정지 상태가 빠집니다. chosen의 마지막 객체가 전체 states의 마지막과 다르면 그 상태를 덧붙입니다. tick이 하나도 없는 초기 경로 실패도 마지막 goal 상태를 프레임으로 남깁니다. 실패가 빠른 실행을 빈 영상으로 만들지 않습니다.

빈 states는 정상 그림으로 포장하지 않고 EMPTY_STATES로 거절합니다. stride가 0이거나 bool이면 STRIDE를 냅니다. 파이썬 슬라이스의 간격 0 오류를 그대로 내기보다 입력 계약 오류를 먼저 드러내는 방식입니다. 한 프레임만 있는 실행도 인덱스와 이미지 파일을 만들어야 합니다. 마지막 상태를 두 번 넣지 않는 조건도 함께 확인합니다.

문자열과 파일의 관계를 지킵니다

상태와 reason에 꺾쇠나 앰퍼샌드가 들어갈 수 있으므로 html.escape로 텍스트를 이스케이프합니다. XML 문법을 깨는 문자열을 SVG에 직접 붙이지 않습니다. 테스트는 특수문자 상태를 넣은 뒤 ElementTree로 다시 파싱하여 원래 문자가 텍스트로 복원되는지 확인합니다. 단순히 파일 확장자가 svg라는 이유로 정상 이미지를 만들었다고 판단하지 않습니다.

파일명은 0000.svg처럼 고정 폭 번호를 사용하여 이름 정렬과 시간 순서가 일치하게 합니다. index.json에는 file, at_us, pose_us, state, sha256을 저장합니다. 파일 이름만 있으면 다른 실행에서 같은 번호가 생겼을 때 구분하기 어렵습니다. 인덱스의 시각과 원본 states 파일, manifest를 함께 보관하여 그림이 어느 실행에서 나왔는지 따라갈 수 있게 합니다.

index.html은 같은 폴더의 SVG를 순서대로 보여 주는 정적 페이지입니다. 브라우저에서 열어 프레임 사이를 이동하면서 화면 녹화할 수 있습니다. 네트워크 서비스나 별도 서버는 필요하지 않습니다. 프레임 파일이 비어 보이면 먼저 XML 파싱, 파일의 상대 경로, viewBox 밖 좌표 여부를 확인합니다. 주행 알고리즘을 바꾸기 전에 표현 단계의 문제를 분리합니다.

관찰과 개선의 증거를 연결합니다

missing/frames-legacy는 누락 보호를 우회한 실행, missing/frames-safe는 센서 누락으로 멈춘 실행을 보여 줍니다. 두 정책은 같은 목표와 seed, 같은 입력 파일을 사용합니다. before-after.json에 두 결과를 함께 저장합니다. 비교 설명에는 legacy가 도착했지만 보호 요구를 위반했다는 사실을 넣습니다. 단순한 도달률 상승을 개선의 유일한 기준으로 삼지 않습니다.

최종 PWM은 결과 파일에서 읽어 표시합니다. 초기 프레임에도 최종 결과 PWM이 보이므로 화면 문구에 final을 붙입니다. 특정 순간의 모터 명령은 해당 상태의 pwm을 원본 로그에서 확인합니다. 최종 0을 화면에서 봤다고 이전 모든 구간이 정지였다고 설명하면 틀립니다. 표현이 압축한 정보와 시간별 원본 정보의 역할을 구별합니다.

프레임을 드물게 뽑으면 짧은 결함을 놓칠 수 있습니다. 5 tick 간격 영상은 인계용 요약이며 충돌 판정의 근거를 대체하지 않습니다. 충돌 평가는 앞 모듈의 이동 구간 검사를 유지하고 상세 상태는 JSONL로 남깁니다. 보고서에 영상의 표본 간격과 원본 로그 경로를 적어 검토자가 필요한 부분을 더 촘촘하게 확인할 수 있게 합니다.

테스트가 설명하는 오류를 읽습니다

test_final_retained의 기대 시각 목록이 [0,5,6]인데 [0,5]가 나오면 마지막 상태 추가가 빠진 것입니다. test_early_abort가 실패하면 tick 목록이 빈 경우의 분기를 봅니다. test_origin_y_axis에서 cy가 기대와 다르면 y축 반전과 원점 이동의 순서를 확인합니다. test_xml_escape의 파싱 오류는 상태의 의미가 아니라 XML 텍스트 처리 문제입니다.

테스트 정답을 렌더러의 xy 함수로 다시 계산하면 같은 오류를 공유할 수 있습니다. 위치 0의 화면 좌표는 직접 계산한 124와 476을 기대값으로 고정합니다. 장애물 한 칸의 수, 원의 반경, 최종 상태 보존은 각각 독립적으로 검사합니다. 화면 전체가 예쁘게 보인다는 감상보다 이런 작은 계약 검사가 수정의 영향을 빠르게 드러냅니다.

제출할 영상과 자동 생성물을 나눕니다

실패 영상은 해당 폴더의 index 순서로 프레임을 보여 주고 시각, 상태, 원인, 정지 결과를 말로 설명합니다. 녹화 파일 경로와 원본 인덱스 경로를 HANDOFF에 적습니다. 자동 검사 통과만으로 영상 제출을 완료 처리하지 않습니다. 브라우저 화면 녹화는 이 환경에서 실행하지 않으므로 따라하기의 출력 칸을 비우고 직접 확인할 기준을 제공합니다.

완성된 실습은 렌더러 코드, 테스트 통과, 프레임 인덱스와 실패 설명을 포함합니다. 더 읽기의 로봇 프로젝트 장은 다른 모델의 통합 사례를 비교하는 자료로 사용합니다. 그 장의 코드나 지표를 이 프로젝트의 결과처럼 가져오지 않습니다. 자신의 기록에서 만든 프레임으로 어떤 정보가 보이고 어떤 정보는 원본 로그가 필요한지 설명하는 것이 이번 목표입니다.

따라하기

화면 좌표를 계산합니다

새 파일로 실행합니다. y축의 방향과 미터 대 픽셀 배율을 확인합니다.

def xy(x,y): return 40+(x+.35)*240,600-(40+(y+.35)*240)
for x,y in ((0,0),(.1,0),(0,.1)):
    a,b=xy(x,y)
    print(f'{x:.1f},{y:.1f} -> {a:.2f},{b:.2f}')

실행 결과

0.0,0.0 -> 124.00,476.00
0.1,0.0 -> 148.00,476.00
0.0,0.1 -> 124.00,452.00

특수문자를 안전하게 표현합니다

표준 라이브러리로 만든 XML이 원래 텍스트를 보존하는지 확인합니다.

from html import escape
import xml.etree.ElementTree as ET
text='ABORTED < sensor & stop'
xml='<svg><text>'+escape(text)+'</text></svg>'
print(xml)
print(ET.fromstring(xml).find('text').text)

실행 결과

<svg><text>ABORTED &lt; sensor &amp; stop</text></svg>
ABORTED < sensor & stop

마지막 상태를 남기고 녹화합니다

evidence.py의 select_frames TODO를 완성하고 bash check.sh를 실행합니다. artifacts/missing/frames-safe/index.json의 마지막 상태와 원본 마지막 줄을 비교합니다. index.html을 브라우저에서 열고 legacy와 safe의 시간·상태·정지 결과를 설명하며 화면을 녹화합니다. 영상 파일을 저장하고 경로를 HANDOFF에 적습니다. 녹화는 직접 수행하는 확인 단계입니다.

확인 문제

실습

evidence.py의 select_frames에서 마지막 상태를 보존합니다. tick 없는 초기 실패, 빈 로그, 마지막 sensor 중단, XML 특수문자와 y축 반전을 확인합니다. bash check.sh는 필요한 로그부터 만들어 SVG와 인덱스를 저장합니다. 프레임과 원본 마지막 상태를 비교하고 브라우저 녹화 영상은 사람이 따로 검토합니다.

시작 코드·테스트 내려받기

실행 명령

bash check.sh

기대 결과

SVG 검사 7개 통과, 정상 지도 2개 도달·충돌 0·센서 누락 정지

모범 답안모범 답안 내려받기

더 읽기

면접 질문

  • 센서 잡음을 추가한 시뮬레이션을 어떻게 평가하는지 설명해 주시면 됩니다.
  • 실험 기록을 재생할 때 함께 남겨야 할 정보를 설명해 주시면 됩니다.