Skip to content

Repository files navigation

수상 자율주행 시뮬레이션과 캡처 뷰어

Unreal Engine 5.5.4에서 선박의 이동과 벽 회피를 실행하고, 같은 전방 시점의 컬러와 depth를 저장해 정적 웹 뷰어로 재생하는 개인 프로젝트입니다.

시작한 이유

자율주행과 시뮬레이션 기술에 관심이 있어, Unreal Engine에서 선박 AI를 학습하고 평가할 수 있는 환경이 어떻게 구성되는지 공부하려고 시작했습니다.

선박의 이동과 장애물 회피를 구현하고, 주행 중 보이는 컬러와 깊이 화면을 함께 저장했습니다. 수집한 결과를 Unreal 밖에서도 확인할 수 있도록 웹 뷰어까지 연결했습니다.

상세 구현 | Unreal 실행 안내

Play를 누르면 코드가 선박과 코스를 만들고 자율주행과 캡처를 시작합니다. 결과는 개별 PNG와 manifest.json 또는 단일 sequence.siv로 볼 수 있습니다.

실제 주행에서 함께 저장한 컬러와 depth 센서 영상

왼쪽은 전방 컬러, 오른쪽은 같은 simulation 시점의 depth입니다. 2026년 8월 23일 MainLevel에서 slide 0과 capture 활성 조건으로 실행해 201개 pair를 저장한 실제 결과입니다.

Unreal runtime과 binary writer는 C++, viewer와 SIV parser는 TypeScript로 구현했습니다. 외부 image package 없이 RGB와 grayscale PNG를 직접 만들고 manifest를 조립해, Unreal을 실행하지 않아도 viewer의 입력 계약을 반복 검증합니다.

벽 우회 경로 컬러와 depth 한 쌍
벽 이동 위치와 우회 경로 실제 컬러와 depth pair

검증 결과

검증 항목 결과
이동 Automation 12개
항법 unit Automation 19개
캡처 Automation 10개
실제 MainLevel 조건 14개
Web Node test 66개
Python test 4개

실제 MainLevel 검증은 캡처를 끈 11개와 켠 3개 조건으로 구성됩니다. 모든 조건에서 충돌 없이 완주하고 최소 벽 거리가 100cm보다 커야 통과합니다. 2026년 8월 22일 재실행에서는 14개 조건이 모두 통과했고, 가장 가까운 벽 간격은 109.697cm였습니다.

문제 해결 과정

벽 geometry를 주행 경계로 삼기

관성 이동에서 단순 waypoint만 정하면 회전하는 선박의 외곽과 벽 모서리 사이 간격을 충분히 설명하지 못합니다. 중심점이나 축 고정 상자로 판단하면 실제로 가장 가까운 순간을 놓칠 수 있습니다.

선박과 벽의 8개 모서리에 현재 transform을 적용해 수평 convex hull을 만들고, 겹침 또는 hull 사이 최단 거리로 wall clearance를 계산합니다. 위치와 거리 계산이 finite하지 않으면 runtime calculation error를 유지해 성공 판정으로 넘어가지 못하게 했습니다.

실제 Water가 있는 MainLevel을 매 조건마다 새로 열어 11개 capture-off와 3개 capture-on 조건을 검사했습니다. 14개 모두 충돌 없이 성공했고 재실행의 최소 wall clearance는 109.697cm였습니다.

상세 course와 측정값은 REPORT.md에 정리했습니다.

Unreal 빠른 실행

필요한 환경은 Unreal Engine 5.5.4와 Visual Studio 2022입니다.

$EngineRoot = Join-Path $env:ProgramFiles 'Epic Games\UE_5.5'
$Project = (Resolve-Path 'ShipAutonomySim\ShipAutonomySim.uproject').Path
& (Join-Path $EngineRoot 'Engine\Build\BatchFiles\Build.bat') `
    ShipAutonomySimEditor Win64 Development `
    "-Project=$Project" -WaitMutex

빌드가 끝나면 ShipAutonomySim/ShipAutonomySim.uproject를 열고 /Game/Maps/MainLevel에서 Play를 누릅니다. 선박, 벽, 시작점과 끝점은 실행 중 생성되므로 actor를 추가로 배치하지 않습니다.

성공한 실행은 보통 18초에서 23초 안에 끝나며 결과는 다음 경로에 저장됩니다.

ShipAutonomySim/Saved/ShipCaptures/<run>/
    color_000000.png, depth_000000.png, ...
    manifest.json
    sequence.siv

에디터와 Play 확인 순서는 SETUP.md에 있습니다.

웹 뷰어 빠른 실행

Node.js, npm, Python 3와 최신 브라우저가 필요합니다. TypeScript를 빌드하고 더미 데이터를 만든 뒤 정적 서버를 실행합니다.

npm ci
npm run build
python scripts/generate_dummy_data.py
python -m http.server 8000

브라우저에서 http://localhost:8000을 엽니다. file://로 직접 열면 브라우저 보안 정책 때문에 module과 data load가 실패합니다.

더미 생성기는 외부 패키지 없이 표준 라이브러리만 사용해 320 x 180 컬러와 depth 30프레임을 만듭니다. struct, zlib과 CRC32로 IHDR, IDAT, IEND chunk를 쓰고, 같은 움직임을 RGB와 depth에 투영한 뒤 timestamp가 있는 manifest를 생성합니다.

프레임 수, 해상도와 간격은 CLI option으로 바꿀 수 있습니다. 생성 결과는 temporary directory에서 다시 PNG chunk 단위로 파싱해 color type, pixel 변화, 파일 쌍, index 연속성과 잘못된 입력 거부를 unittest로 확인합니다.

python scripts/generate_dummy_data.py `
  --frames 30 `
  --width 640 `
  --height 360 `
  --interval-ms 100

실제 데이터 열기

기본 모드는 저장소 루트의 manifest.json과 상대 PNG 경로를 읽습니다. 컬러와 depth URL은 manifest와 같은 origin이어야 합니다.

단일 바이너리는 파일 복사 없이 실행 디렉터리에서 바로 엽니다.

http://localhost:8000/?bundle=ShipAutonomySim/Saved/ShipCaptures/<run>/sequence.siv

SIV 모드는 한 번의 HTTP 요청으로 내부 manifest와 PNG payload를 읽습니다. 접근 benchmark는 PNG decode나 canvas render가 아니라 index가 가리키는 compressed byte copy만 측정합니다.

capture 계약

컬러와 depth는 같은 6자리 index를 공유하는 512 x 512 PNG pair입니다. 목표 간격은 100ms이며 timestamp는 실제 시계를 사용합니다.

depth는 SCS_SceneDepth, PF_R32_FLOAT에서 읽습니다. 0cm를 255, 2500cm 이상과 invalid pixel을 0으로 두는 inverse-linear G8 PNG로 저장합니다.

manifest.json의 최상위 field는 다음 8개입니다.

  • capture_resolution
  • depth_far_cm
  • depth_near_cm
  • frame_count
  • frames
  • interval_ms
  • result
  • wall_slide_cm

각 프레임에는 color, depth, index, time_ms만 들어갑니다. frame array 길이는 frame_count와 같고 index는 0부터 이어져야 합니다.

sequence.siv는 SIVPACK1 magic, little-endian JSON header length, UTF-8 index와 연속 PNG payload로 구성됩니다. PNG를 다시 zlib으로 압축하지 않습니다.

color-depth를 한 frame으로 게시하기

color와 depth를 따로 쓰면 한쪽 파일만 보이는 frame을 reader가 받을 수 있습니다. 이 위험을 줄이기 위해 두 PNG를 메모리에서 만든 뒤 숨김 temporary file 두 개에 쓰고, 둘 다 유효할 때만 final name으로 옮깁니다.

두 final file이 모두 존재한 뒤에만 frame record를 추가합니다. 실행 종료에서는 그 pair들로 manifest를 finalize하고, 같은 manifest를 바탕으로 SIV index와 sequence.siv를 만듭니다. 따라서 manifest와 SIV는 완성된 pair만 참조합니다.

2026년 8월 23일 MainLevel 실행은 201 pairs와 result: success를 남겼습니다. 별도 fixture의 4개 unittest는 struct, zlib, CRC32 PNG 생성물을 다시 decompress해 RGB와 grayscale, pair index, manifest timestamp를 확인합니다.

이 검사는 viewer의 frame pair, index, timestamp와 PNG 입력 계약을 확인합니다. 상세 순서는 REPORT의 capture 절에 정리했습니다.

조작

  • 재생과 일시정지는 같은 버튼을 사용합니다.
  • 마지막 프레임에서 재생하면 처음부터 다시 시작합니다.
  • 처음, 이전, 다음과 슬라이더 이동은 재생을 멈춥니다.
  • 속도는 0.5x, 1x, 2x입니다.
  • depth는 grayscale과 가까울수록 따뜻한 colormap을 전환합니다.
  • 모든 이미지를 load하기 전에는 조작을 비활성화합니다.
  • SIV 모드에서는 순차와 고정 seed random access를 비교할 수 있습니다.

자동 검사

npm test
python -m unittest discover -s tests -p "test_*.py" -v

4개 unittest는 파일 존재 여부만 보지 않습니다. 생성한 PNG를 다시 decompress하고 RGB 및 grayscale channel, 움직이는 shape와 depth intensity, manifest frame과 timestamp를 비교합니다.

Unreal Automation과 실제 MainLevel sweep 명령은 REPORT.md의 검증 절에 있습니다.

오류 확인

  • manifest를 가져오지 못하면 서버 주소와 실행 디렉터리를 확인합니다.
  • manifest 검증 오류가 나오면 표시된 field와 캡처 계약을 비교합니다.
  • 이미지 load 오류가 나오면 6자리 file name, 대소문자와 relative path를 확인합니다.
  • SIV 오류가 나오면 magic, version, JSON index, payload range와 PNG signature를 확인합니다.

About

수상 자율주행 시뮬레이션 구현

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages