이전 까지는 많은 사람들이 카메라나 라이다(LiDAR) 센서가 어떻게 장애물을 ‘보는 것(인지)’이 자율주행을 공부했습니다. 또 자율주행 자동차는 어떻게 복잡한 도로 위에서 공간을 어떻게 이해하는 지도 알아보았습니다. 이제 전부였으면 너무 좋겠지만! 사실 핵심은 이 데이터를 바탕으로 ‘어떻게 움직일지 결정하는 것(판단)‘에 있습니다.
오늘은 자율주행 시스템이 비정형 데이터를 어떻게 정형화된 지도로 바꾸고, 생존을 위한 판단 로직을 거쳐, 30ms 이내의 실시간성을 확보하는지 그 모든 과정을 파헤쳐 보겠습니다. 자율주행 개발자나 AI 엔지니어를 꿈꾸고 있다면 반드시 알아야 할 지능형 판단(Intelligent Planning)을 알아봅시다!
공간을 판단으로 바꾸는 마법: OGM과 코스트맵(Costmap)
카메라와 라이다(LiDAR) 센서로부터 쏟아지는 수만 장의 데이터는 컴퓨터가 즉각적으로 계산할 수 없는 ‘비정형 데이터’입니다. 자율주행 차량이 이 데이터를 이용해 경로를 찾으려면 복잡한 세상을 단순하게 압축하는 추상화(Abstraction) 작업이 필수적입니다.
무한한 세상을 유한한 격자로, OGM (점유 격자 지도)
판단 알고리즘이 탐색해야 할 현실 세상의 좌표는 무한대입니다. 이를 해결하기 위해 공간을 칸칸이 나누어 OGM(Occupancy Grid Map)을 만듭니다. 이를 PCD(Point Cloud Data)라고 부릅니다

- 센서를 통해 들어온 점(Point) 데이터를 핀홀 카메라 모델 공식을 활용해 3차원 위치 좌표로 변환합니다.
- 이후 각 격자를 주행 가능 공간(Free Space, 0), 점유됨(Occupied, 1), 알 수 없음(Unknown, 0.5)으로 단순화합니다.
OGM의 한계와 코스트맵(Costmap)의 등장
하지만 OGM만으로는 자율주행을 할 수 없습니다. OGM 기반의 경로 탐색 알고리즘(A*, Dijkstra 등)은 단순히 ‘빈 공간’을 모두 갈 수 있는 곳으로 여기기 때문입니다. OGM의 치명적 단점은 최단 거리를 찾으려다 장애물 옆 1cm 지점을 스치듯 지나가거나, 낭떠러지 끝에 아슬아슬하게 붙어서 주행하는 경로를 생성할 수 있습니다.
이러한 사고를 방지하기 위해 점유 여부 위에 ‘위험도 수치’라는 전략적 가치를 덧씌운 것이 바로 코스트맵(Costmap)입니다.
- 0 (Free Space): 마음대로 주행 가능한 빈 공간.
- 1~127 (Inscribed): 장애물 근처로, 차체 일부가 닿을 수 있어 가급적 회피해야 하는 안전 감쇠 영역.
- 128~252 (Circumscribed): 내 차의 중심이 진입하면 반드시 충돌하는 매우 위험한 영역.
- 253~254 (Lethal): 장애물 그 자체로 절대 통과 불가.
- 255 (Unknown): 센서가 닿지 않아 정보가 없는 미지의 영역.
import numpy as np
from scipy.ndimage import distance_transform_edt
def generate_fast_costmap(points, resolution=0.1, inscribed_r=0.4, inflation_r=1.2, cost_scale=4.0):
"""
3D 포인트를 입력받아 유클리드 거리 변환 기반으로 고속 2-Layer Costmap 생성
"""
margin = inflation_r + 1.0
min_x, max_x = np.min(points[:, 0]) - margin, np.max(points[:, 0]) + margin
min_y, max_y = np.min(points[:, 1]) - margin, np.max(points[:, 1]) + margin
width = int(np.ceil((max_x - min_x) / resolution))
height = int(np.ceil((max_y - min_y) / resolution))
# 장애물 마스크 매핑
gx = ((points[:, 0] - min_x) / resolution).astype(int)
gy = ((points[:, 1] - min_y) / resolution).astype(int)
valid = (gx >= 0) & (gx < width) & (gy >= 0) & (gy < height)
occupied = np.zeros((height, width), dtype=bool)
occupied[gy[valid], gx[valid]] = True
# 거리 변환 (픽셀 단위 -> m 단위)
dist_meters = distance_transform_edt(~occupied) * resolution
# ROS 표준 Costmap 인플레이션 레이어링
costmap = np.zeros((height, width), dtype=np.float32)
decay_mask = (dist_meters > inscribed_r) & (dist_meters <= inflation_r)
costmap[decay_mask] = 253.0 * np.exp(-cost_scale * (dist_meters[decay_mask] - inscribed_r))
costmap[(dist_meters <= inscribed_r) & (~occupied)] = 253.0
costmap[occupied] = 254.0
return costmap.astype(np.uint8), (min_x, max_x, min_y, max_y)💡 실무 팁:
Open3D모듈을 사용해.pcd형태의 포인트 클라우드 데이터를 로드하고, NumPy 행렬 벡터화를 통해 코스트맵의 이중 for문을 제거하면 연산 시간을 수십 배 단축할 수 있습니다.
피할 것인가, 멈출 것인가? 자율주행 판단의 3대 계층과 FSM
환경 모델링이 끝났다면, 이제 실제로 움직임을 결정할 차례입니다. 자율주행의 판단(Planning) 시스템은 인간의 사고방식과 유사하게 3가지 계층으로 나뉩니다.
- Mission Planning (전략적 수준): 출발지부터 목적지까지의 거시적 경로(내비게이션 기능)를 생성합니다.
- Behavior Planning (전술적 수준): FSM(Finite State Machine)과 같은 규칙 기반 로직을 통해 “차선을 유지할지(Lane Follow)”, “차선을 변경할지(Change Lane)”, “정지할지(Stop)” 상태를 결정합니다.
- Motion Planning (실행적 수준): 승차감과 차량 동역학을 고려해 물리적인 연속 궤적(Trajectory)을 만듭니다.
회피, 제동 판단: TTC (Time-to-Collision)
전술적 상황 판단에서 가장 널리 쓰이는 위험 척도는 TTC(충돌 예상 시간)입니다.

원론적 전략 트리
Braking (제동): TTC가 위험 수준(TTC < 1.5s)일 때 즉시 감속하는 가장 보수적이고 안전한 선택입니다.
Swerving (회피): 정지가 불가능할 때 코스트맵을 분석하여 안전한 인접 차선으로 꺾어 회피합니다.
Emergency (긴급): 모든 회피가 불가능하다면 피해를 최소화하는 궤적을 선택합니다.
import numpy as np
# [1. SELECT: 관심 영역 및 위험 객체 선별]
# 전방 중심(가로 폭 40% ~ 60%) ROI 추출로 배경 노이즈 제거
center_start, center_end = int(W * 0.4), int(W * 0.6)
front_seg = seg_map[:, center_start:center_end]
front_depth = depth_map[:, center_start:center_end]
# 충돌 위험이 높은 사람(11) 및 차량(13) 클래스만 마스킹 (Cityscapes 기준)
critical_mask = np.isin(front_seg, [11, 13])
# 기본 주행 상태 초기화
fsm_state = "Lane Keeping"
action = "SAFE"
estimated_dist = 100.0
ttc = 99.0
# [2. OPTIMIZE & RESPOND: TTC 계산 및 FSM 천이]
if np.any(critical_mask):
# 단안 카메라 Depth 노이즈 방어를 위해 하위 5% 거리 백분위수 활용
target_depths = front_depth[critical_mask]
estimated_dist = np.percentile(target_depths, 5)
relative_velocity = 15.0 # 상대속도 (m/s) 가정
ttc = estimated_dist / (relative_velocity + 1e-6)
# 위험도 임계치에 따른 계층적 대응 로직
if ttc < 1.5:
# 긴급 제동: 조향할 여유조차 없는 극단적 위험 상황 (충돌 피해 최소화)
action = "EMERGENCY BRAKE"
fsm_state = "Stop"
elif ttc < 3.0:
# 주의 및 차선 변경: 코스트맵을 참고해 인접 차선으로 회피 기동 검토
action = "SWERVE / CAUTION"
fsm_state = "Change Lane"
실무 FSM 상태 머신
주의/회피 구간 (1.5s ≤ TTC < 3.0s): 전방 장애물 위험이 감지되어 제동 준비와 함께 인접 차선 여유 공간(Costmap)을 확인하여 차선 변경(Swerving)을 우선 검토하는 구간입니다.
긴급 제동 구간 (TTC < 1.5s): 회피 기동을 안전하게 수행할 시간적 여유가 없으므로 즉시 최대 제동(Emergency Braking)을 걸어 충돌을 방지하거나 피해를 최소화합니다.
비상 궤적 수립 (Emergency): 만약 제동 거리 부족과 인접 차선 점유로 둘 다 불가능한 최악의 조건이라면, 차량 동역학 한계 내에서 충돌 에너지를 최소화하는 비상 탈출 궤적을 탐색합니다.
현실에서 돌아가는 판단: 지연 시간의 치명성과 SOR 원칙
이론적으로 완벽한 자율주행 인공지능을 만들었다 하더라도 실제 차량(Edge AI)에 탑재하면 문제가 발생합니다. 자율주행에서 지연 시간(Latency)은 곧 안전과 직결됩니다. OneFormer나 Depth-Anything V2 같은 무거운 딥러닝 모델은 한 프레임을 추론하는 데 수 초가 걸리기 때문입니다.
30ms 이내에 응답하라! 지연(Latency)과 안전의 관계

시속 100km로 주행하는 자동차에서 시스템 반응이 0.1초(100ms)만 늦어져도 자동차는 제어 없이 약 2.8m를 이동하게 됩니다. 그렇기에 자율주행에서 지연 시간(Latency)은 곧 안전과 직결됩니다.
판단 파이프라인 T(response) = T(sensing) + T(inference) + T(actuation)의 총 지연 시간이 임계점(보통 30ms)을 넘어서면, 정확도가 아무리 뛰어나도 그 판단 알고리즘은 무용지물이 됩니다.
제약의 한계, 해결책 : SOR 원칙

S (Select) – 결정적인 정보만 선별: 센서 데이터의 90%는 불필요한 배경(파란 하늘 등) 노이즈입니다. 전체를 연산하는 대신 화면의 40~60%를 관심 영역(ROI)으로 지정하고, 판단에 결정적인 영향을 미치는 ‘사람(클래스 11)’과 ‘자동차(클래스 13)’ 등 고밀도 정보만 선별하여 연산 부하를 줄여야 합니다.
O (Optimize) – 하드웨어 맞춤 경량화: 거대 모델(SOTA) 대신 Segformer-B0나 GLPN 같은 경량화 모델을 병렬로 배치해야 합니다. 또한 TFLite나 TensorRT(NVIDIA GPU 엔진)를 이용해 양자화(Quantization) 및 가지치기(Pruning)를 수행하여 임베디드 칩셋 구조에 맞게 최적화합니다.
R (Respond) – 실시간성 확보: 지연 시간이 데드라인(30ms) 내에 들어오는 ‘최악의 경우(Worst-case Latency)’를 보장해야 합니다. 이를 위해 즉각적인 제동을 담당하는 단순 수치 기반의 Fast Path(1단계 생존 판단), 온디바이스 AI로 상황을 인지하고 회피하는 Medium Path(2단계), 그리고 여유 있게 경로를 수정하는 Slow Path(3단계)로 계층을 분리해 안전을 담보합니다.
마무리하며
자율주행의 지능형 판단은 결국 “수많은 센서 노이즈 속에서 무엇을 버리고, 하드웨어에 맞게 얼마나 모델을 압축하여, 결정적인 충돌 순간에 얼마나 빨리 응답(Respond)할 수 있는가?”의 싸움입니다.
단순히 AI 모델의 정확도(Accuracy)를 높이는 클라우드 환경의 연구를 넘어, 메모리가 부족한 자동차 임베디드 환경에서 30ms 이내에 생존을 결정짓는 최적화 로직의 아름다움을 이해하셨기를 바랍니다. 다음 포스팅에서는 실전 코드를 바탕으로 모델 경량화 기법을 직접 구현해 보는 시간을 갖겠습니다!
FAQ
Q1. 왜 장애물 주변 위험도를 깎아줄 때 ‘완만하게(선형)’ 줄이지 않고 ‘급격하게(지수 감쇠)’ 줄이나요?
A: 뜨거운 난로를 피하는 사람의 본능과 같습니다.난로 바로 옆 1cm는 옷이 탈 정도로 극도로 위험하지만, 한 걸음만 물러나면 확 안전해집니다. 만약 위험도를 완만하게 깎으면, 차가 “어? 조금만 떨어져도 위험도가 별로 안 줄어드네?”라며 쓸데없이 차선을 확 넘어가 버립니다. 반대로 지수 감쇠를 쓰면 장애물 코앞에는 ‘절대 접근 금지’ 수준의 벼락 페널티를 주고, 차가 살짝만 떨어져도 금방 ‘안전한 길’로 인식하게 만들어 안정적으로 직진하게 만듭니다.
Q2. 비싼 라이다(LiDAR) 대신 ‘카메라 사진 한 장’으로 거리 재서 만든 지도를 믿어도 되나요?
A: 카메라 눈대중은 착시가 생길 수 있어서 ‘안전 필터’를 꼭 달아야 합니다. 카메라는 한쪽 눈을 감고 거리를 가늠하는 것과 같아서, 도로 경사나 카메라 각도에 따라 거리가 튀는 에러(노이즈)가 생깁니다. 그래서 카메라만 쓸 때는 측정값 중 가장 튀는 엉터리 수치를 버리고, 실제로 가장 가까운 안전 마진(하위 5% 거리)만 골라내는 방어 코드를 넣어 라이다 부럽지 않은 신뢰도를 확보합니다.
Q3. ‘0.03초(30ms) 안에 판단하기’가 목표인데, 어디서 시간이 제일 많이 잡아먹히나요?
A: 사진 크기(해상도)와 파이썬 for문(반복문)입니다. 사진 다이어트: 4K 고화질 사진 전체를 AI한테 보여주면 뇌(GPU)가 멈칫하며 0.1초를 훌쩍 넘깁니다. 도로 한가운데(관심 영역)만 싹둑 잘라서 보여주면 속도가 몇 배 빨라집니다. 반복문 없애기: 지도 칸 수십만 개를 파이썬 for문으로 하나하나 세고 있으면 굼벵이가 됩니다. NumPy 같은 도구를 써서 수십만 개 칸을 한 번에 도장 찍듯 계산(행렬 연산)해야 0.03초 안에 칼같이 핸들을 꺾을 수 있습니다.
참고 문헌 및 기술 출처 (References)
1. OGM & Costmap 이론
- Using Occupancy Grids for Mobile Robot Perception and Navigation (Elfes, A., 1989) – 점유 격자 지도(OGM)의 기본 개념 정립
- The Office Marathon: Robust Navigation in an Unstructured Environment (Marder-Eppstein, E. et al., ICRA 2010) – ROS
costmap_2d패키지의 인플레이션(Inflation) 레이어 기반 이론
2. 벤치마크 및 실습 데이터셋
- KITTI Vision Benchmark Suite (Geiger, A. et al., IJRR 2013) – 자율주행 컴퓨터 비전 및 스테레오/LiDAR 표준 데이터셋
- AI-Hub 생활도로 객체인식 3D LiDAR 데이터 (과학기술정보통신부 / 한국지능정보사회진흥원 NIA) – 복합 환경 객체 인식 포인트 클라우드 데이터셋
3. 인지 알고리즘 및 딥러닝 모델 (SOTA & 경량화)
- SegFormer: SegFormer: Simple and Efficient Design for Semantic Segmentation with Transformers (Xie, E. et al., NeurIPS 2021)
- GLPN: Global-Local Path Networks for Monocular Depth Estimation with Vertical CutDepth (Kim, D. et al., arXiv 2022)
- Depth Anything V2: Depth Anything V2: A More Capable Foundation Model for Monocular Depth Estimation (Yang, L. et al., arXiv 2024)
- OneFormer: OneFormer: One Transformer to Rule the All-in-One Image Segmentation Task (Jain, J. et al., CVPR 2023)
“[자율주행 알고리즘 판단편] OGM, 코스트맵(Costmap) : 최적화 지능형 판단 5편”에 대한 1개의 생각