← BlogAMR 내비게이션 입문· 10/12

VFH+ 반응형 회피 — costmap 없이 polar histogram으로 틈을 찾는 법

Nav2 없이도, 또는 동적 장애물이 많을 때 쓰는 Vector Field Histogram의 3단계 데이터 축소 원리. occupancy grid를 polar histogram으로 줄여 '지나갈 틈'을 직접 고른다.

약 2분
Nav2VFHreactiveobstacle-avoidanceAMR

VFH+ 반응형 회피

Nav2의 planner→controller 파이프라인은 강력하지만, 무겁고 전역 지도에 의존합니다. 그런데 "지금 눈앞의 장애물을 빠르게 피하는" 단순한 반응이 필요할 때가 있습니다 — Nav2가 안 떠 있거나, 동적 장애물이 너무 많을 때. 그때 쓰는 가벼운 방법이 VFH+(Vector Field Histogram) 입니다.

3단계로 데이터를 줄인다

VFH의 핵심은 복잡한 환경을 점점 단순한 형태로 줄여, 마지막엔 "어느 방향으로 갈지" 하나만 남기는 것입니다.

도식 렌더링 중…
  1. Occupancy Grid — 로봇 주변의 장애물 격자. (앞 글의 점유 격자와 같은 개념)
  2. Polar Histogram — 격자를 로봇 중심의 방향별 막대그래프로 변환. 각 방향(sector)에 장애물이 얼마나 빽빽한지.
  3. Steering Valley — 막대가 낮은(=비어 있는) 골짜기를 찾아, 목표 방향에 가장 가까운 골짜기로 핸들을 꺾습니다.

극좌표 히스토그램으로 보면 이렇습니다.

방향:  ←   ↖   ↑   ↗   →
밀도:  ███  █   ·   ··  ████
                ↑
            여기가 valley (비었음) → 이 방향으로

데이터 축소를 코드로 보면 — 히스토그램에서 빈 골짜기를 고르는 것.

# 격자 → 방향별 장애물 밀도(polar histogram) → 가장 빈 골짜기
def steer_vfh(grid, goal_dir, threshold):
    hist = [density(grid, s) for s in range(0, 360, 30)]   # 12방향(30도)
    valleys = [s for s, d in enumerate(hist) if d < threshold]
    return min(valleys, key=lambda s: abs(s*30 - goal_dir))  # 목표에 가까운 골짜기

VFH+가 "+"인 이유 — 로봇 폭 보상

원래 VFH는 로봇을 점으로 봤지만, VFH+ 는 로봇의 폭을 보상합니다. 장애물까지 거리가 가까울수록, 그 장애물이 막는 각도를 넓혀 잡습니다.

💡 각도 확장 공식 — 장애물이 막는 각도를 enlargement_angle = arcsin((r_robot + r_safety) / d_obstacle) 만큼 넓힙니다. Inflation이 costmap에서 장애물을 부풀린 것과 같은 발상을, 여기선 각도에서 합니다. 가까운 장애물일수록(d 작을수록) 더 넓게 막아 안전 여유를 확보합니다.

⚠️ 섹터 해상도의 한계 — 방향을 12개(30°)로만 나누면, 좁은 틈을 "막혔다"고 잘못 볼 수 있습니다. 해상도를 높이거나 VFH+의 보상을 쓰는 게 좁은 통로 통과의 관건입니다.

어디에 쓰나 — fallback 주행

VFH+는 전역 경로 계획기 없이도 도는 가벼움 덕분에, Nav2 스택이 가동되지 않는 상황의 fallback 주행으로 유용합니다. 순찰(patrol) 컨트롤러가 큰 경로를 주고, VFH+가 즉석 장애물을 피하는 식의 조합이 가능합니다. "무거운 전역 계획"과 "가벼운 반응 회피" 사이에서 상황에 맞게 고르는 것이죠.

한 줄 정리

📌 VFH+는 격자 → 방향별 polar histogram → 가장 빈 골짜기로 데이터를 줄여 "갈 방향"을 직접 고르는 가벼운 반응형 회피다. 로봇 폭을 각도 확장으로 보상하며, Nav2가 없을 때의 fallback 주행에 적합하다.