로봇에게도 감각이 여러 개 필요한 이유
사람은 길을 걸을 때 눈만 쓰지 않습니다. 발바닥으로 바닥의 기울기를 느끼고, 귀로 주변 소리를 듣고, 몸의 균형 감각으로 넘어질 것 같은 순간을 알아챕니다. 어두운 계단에서는 눈보다 발끝 감각이 더 중요해지고, 시끄러운 거리에서는 귀보다 눈에 더 의존합니다.
로봇도 비슷합니다. 바퀴가 얼마나 돌았는지만 보면 로봇이 얼마나 움직였는지 대략 알 수 있습니다. 하지만 바닥이 미끄러우면 바퀴는 돌았는데 몸체는 생각보다 덜 움직일 수 있습니다. 카메라로 주변을 보면 벽, 문, 표지판을 알아볼 수 있지만, 조명이 어둡거나 렌즈가 흔들리면 판단이 불안정해집니다. LiDAR는 거리 측정에 강하지만 유리처럼 빛을 이상하게 반사하는 물체 앞에서는 어려움을 겪을 수 있습니다.
그래서 로봇은 하나의 센서만 믿지 않습니다. 여러 센서가 보는 정보를 모아 서로의 약점을 보완합니다. 이 과정을 센서 퓨전(sensor fusion)이라고 부릅니다. 퓨전은 말 그대로 섞는다는 뜻이지만, 아무렇게나 평균을 내는 것이 아닙니다. 각 센서가 무엇을 잘하고, 언제 틀리기 쉬우며, 얼마나 자주 값을 내보내는지를 고려해 더 믿을 만한 상태 추정을 만드는 일입니다.
여기서 말하는 상태(state)는 로봇이 알고 싶은 중요한 값입니다. 예를 들어 2D 이동 로봇에서는 다음과 같은 값이 상태가 될 수 있습니다.
state = [x, y, theta, v]여기서 x, y는 위치, theta는 방향, v는 속도입니다. 로봇은 센서 퓨전을 통해 "나는 지금 어디에 있고, 어느 방향을 보고 있으며, 얼마나 빠르게 움직이고 있는가?"라는 질문에 더 안정적으로 답하려고 합니다.
지난 장에서 우리는 칼만 필터를 통해 예측과 보정을 배웠습니다. 센서 퓨전도 이 흐름과 매우 잘 맞습니다. 빠르게 들어오는 센서로 일단 상태를 예측하고, 더 느리지만 기준이 분명한 센서가 들어오면 예측을 보정합니다. 이 단순한 리듬이 많은 로봇 추정 시스템의 기본 박자입니다.

센서마다 잘하는 일이 다르다
센서 퓨전을 이해하려면 먼저 각 센서의 성격을 알아야 합니다. 로봇에서 자주 만나는 센서로는 엔코더, IMU, 카메라, LiDAR가 있습니다.
엔코더(encoder)는 바퀴나 모터가 얼마나 회전했는지 측정합니다. 바퀴 지름을 알고 있다면 회전량을 이동거리로 바꿀 수 있습니다. 예를 들어 바퀴가 한 바퀴 돌 때 로봇이 0.2m 전진한다면, 바퀴가 다섯 바퀴 돌았을 때 약 1m 움직였다고 추정할 수 있습니다.
하지만 엔코더는 "바퀴가 돌았다"는 사실을 알려줄 뿐, "로봇 몸체가 정말 그만큼 움직였다"는 사실을 직접 보장하지는 않습니다. 바닥이 미끄럽거나 바퀴가 헛돌면 엔코더는 자신 있게 숫자를 내지만, 그 숫자는 실제 이동거리와 달라질 수 있습니다.
IMU(Inertial Measurement Unit)는 관성 측정 장치입니다. 보통 가속도계와 자이로스코프를 포함합니다. 가속도계는 가속도를 재고, 자이로스코프는 회전 속도를 잽니다. IMU는 빠르게 반응합니다. 로봇이 갑자기 방향을 틀거나 흔들릴 때 즉시 변화를 잡아낼 수 있습니다.
그러나 IMU는 적분을 많이 해야 하는 센서입니다. 회전 속도를 계속 더하면 각도가 되고, 가속도를 계속 더하면 속도와 위치가 됩니다. 문제는 작은 오차도 계속 더하면 점점 커진다는 점입니다. 이를 드리프트(drift)라고 했습니다. IMU는 빠르지만 시간이 지날수록 서서히 틀어질 수 있습니다.
카메라(camera)는 풍부한 정보를 줍니다. 물체의 모양, 색, 표지판, 사람, 차선, 문 같은 의미 있는 정보를 얻을 수 있습니다. 카메라를 이용하면 로봇이 "여기가 복도인지", "앞에 컵이 있는지", "벽에 붙은 마커가 어디 있는지"를 알 수 있습니다.
하지만 카메라는 조명, 그림자, 흐림, 흔들림에 민감합니다. 또 한 장의 이미지에서 거리를 정확히 알아내려면 추가적인 계산이 필요합니다. 스테레오 카메라나 깊이 카메라를 쓰면 거리 정보를 얻을 수 있지만, 그 역시 환경 조건의 영향을 받습니다.
LiDAR(Light Detection and Ranging)는 빛을 쏘고 돌아오는 시간을 이용해 주변까지의 거리를 측정합니다. 2D LiDAR는 평면상의 거리 스캔을 만들고, 3D LiDAR는 주변 공간의 점 구름을 만듭니다. LiDAR는 거리 측정에 강하고, 어두운 곳에서도 카메라보다 안정적인 경우가 많습니다.
다만 LiDAR도 완벽하지 않습니다. 유리, 거울, 검은 표면처럼 빛의 반사가 까다로운 물체에서는 측정이 불안정해질 수 있습니다. 또한 카메라보다 의미 정보를 직접 주지는 않습니다. LiDAR는 "저 방향 2.3m 앞에 무언가 있다"는 데 강하지만, 그것이 의자인지 사람인지 표지판인지는 별도의 해석이 필요합니다.
정리하면 다음과 같습니다.
| 센서 | 잘하는 일 | 약한 상황 |
|---|---|---|
| 엔코더 | 바퀴 회전량, 짧은 시간 이동거리 추정 | 미끄러짐, 헛돎, 바닥 상태 변화 |
| IMU | 빠른 회전과 흔들림 감지 | 적분 오차, 바이어스, 장기 드리프트 |
| 카메라 | 물체 인식, 색과 형태, 의미 정보 | 어두운 곳, 역광, 흔들림, 거리 추정의 어려움 |
| LiDAR | 거리 측정, 장애물 형상 파악 | 유리, 거울, 반사율 문제, 의미 정보 부족 |
센서 퓨전의 핵심은 이 표를 외우는 것이 아닙니다. 중요한 것은 "어떤 센서가 어떤 상황에서 더 믿을 만한가?"라는 질문을 계속 던지는 습관입니다.
빠른 센서와 느린 센서를 함께 쓰는 이유
로봇 센서는 모두 같은 속도로 데이터를 보내지 않습니다. IMU는 1초에 수백 번 값을 줄 수 있습니다. 엔코더도 꽤 빠르게 회전량을 알려줍니다. 반면 카메라 영상 처리는 상대적으로 느릴 수 있습니다. LiDAR 역시 스캔 한 바퀴를 완성하는 데 시간이 걸립니다.
이 차이는 불편해 보이지만, 사실 센서 퓨전에서 아주 자연스러운 구조를 만듭니다. 빠른 센서는 로봇의 짧은 순간 변화를 따라가는 데 좋습니다. 느린 센서는 더 넓은 기준에서 "너 지금 조금 틀어졌어"라고 알려주는 데 좋습니다.
예를 들어 실내 로봇이 복도를 따라 움직인다고 생각해 봅시다. IMU와 엔코더는 매 순간 로봇이 앞으로 가고 있는지, 회전하고 있는지 알려줍니다. 이 정보로 로봇은 위치를 빠르게 갱신합니다. 하지만 시간이 지나면 바퀴 미끄러짐과 IMU 드리프트 때문에 추정 위치가 조금씩 실제 위치에서 벗어납니다.
이때 카메라가 벽에 붙은 마커를 발견하거나, LiDAR가 복도 벽의 모양을 지도와 맞춰 보면 로봇은 자신의 위치를 다시 보정할 수 있습니다. 카메라와 LiDAR는 IMU만큼 빠르지 않을 수 있지만, 절대 기준 또는 환경 기준을 제공할 수 있습니다.
이 관계를 아주 단순하게 쓰면 다음과 같습니다.
빠른 센서: 지금 막 어떻게 움직였는가?
느린 센서: 그래서 현재 위치가 전체 기준에서 맞는가?이 흐름은 지난 장의 칼만 필터 구조와 닮았습니다.
예측: 엔코더와 IMU로 상태를 빠르게 업데이트한다.
보정: 카메라나 LiDAR 관측으로 누적 오차를 줄인다.즉, 센서 퓨전은 단순히 센서를 많이 붙이는 일이 아닙니다. 빠른 센서와 느린 센서의 역할을 나누고, 드리프트가 있는 센서와 기준을 주는 센서를 조합하는 일입니다.
같은 값을 두 센서가 본다면 어떻게 섞을까
가장 쉬운 센서 퓨전은 두 센서가 같은 물리량을 측정할 때입니다. 예를 들어 두 센서가 모두 로봇의 위치 x를 측정한다고 해 봅시다. 하나는 불안정하지만 빠르고, 다른 하나는 느리지만 비교적 정확합니다.
센서 A의 측정값이 10.0, 센서 B의 측정값이 12.0이라면 단순 평균은 11.0입니다.
x_a = 10.0
x_b = 12.0
x_fused = (x_a + x_b) / 2
print(x_fused)하지만 두 센서의 신뢰도가 다르다면 똑같이 반반 섞는 것은 이상합니다. 센서 A가 훨씬 정확하다면 결과는 10.0에 더 가까워야 합니다. 센서 B가 더 정확하다면 결과는 12.0에 더 가까워야 합니다.
이때 사용할 수 있는 간단한 방법이 가중 평균(weighted average)입니다.
x_a = 10.0
x_b = 12.0
w_a = 0.8
w_b = 0.2
x_fused = w_a * x_a + w_b * x_b
print(x_fused)결과는 10.4입니다. A를 더 믿었기 때문에 결과가 A에 가까워졌습니다.
그런데 지난 장에서 배운 분산을 이용하면 더 수학적인 방식으로 가중치를 정할 수 있습니다. 분산이 작다는 것은 측정값이 덜 흔들린다는 뜻이므로 더 믿을 만합니다. 반대로 분산이 크면 불확실성이 큽니다.
두 센서의 분산이 있을 때, 더 작은 분산을 가진 센서에 더 큰 가중치를 줄 수 있습니다.
import numpy as np
x_a = 10.0
x_b = 12.0
var_a = 0.25 # 표준편차 0.5
var_b = 4.0 # 표준편차 2.0
trust_a = 1 / var_a
trust_b = 1 / var_b
w_a = trust_a / (trust_a + trust_b)
w_b = trust_b / (trust_a + trust_b)
x_fused = w_a * x_a + w_b * x_b
print("w_a:", w_a)
print("w_b:", w_b)
print("fused:", x_fused)이 코드는 분산의 역수를 신뢰도로 사용합니다. 분산이 작을수록 1 / var 값은 커집니다. 즉, 흔들림이 적은 센서일수록 결과에 더 크게 반영됩니다.
이 방식은 칼만 필터의 감각과도 이어집니다. 칼만 필터도 결국 "예측을 얼마나 믿고, 측정을 얼마나 믿을 것인가?"를 불확실성으로 조절합니다. 센서 퓨전에서는 이 질문이 더 넓어집니다.
엔코더를 얼마나 믿을까?
IMU를 얼마나 믿을까?
카메라 관측을 얼마나 믿을까?
LiDAR 위치 보정을 얼마나 믿을까?센서 퓨전은 이 질문들에 숫자로 답하려는 과정입니다.
엔코더와 IMU를 함께 써서 이동 추정하기
이제 조금 더 로봇다운 예제로 들어가 봅시다. 2D 평면 위를 움직이는 로봇이 있습니다. 로봇의 상태는 x, y, theta입니다.
x: 월드 좌표계에서의 x 위치
y: 월드 좌표계에서의 y 위치
theta: 로봇이 바라보는 방향로봇이 전진 속도 v와 회전 속도 omega를 알고 있다면, 작은 시간 dt 동안 다음처럼 위치를 업데이트할 수 있습니다.
x = x + v * np.cos(theta) * dt
y = y + v * np.sin(theta) * dt
theta = theta + omega * dt여기서 v는 엔코더로 추정할 수 있습니다. 바퀴가 얼마나 돌았는지 알면 로봇이 얼마나 전진했는지 계산할 수 있기 때문입니다. omega도 엔코더로 추정할 수 있습니다. 왼쪽 바퀴와 오른쪽 바퀴의 회전 차이를 보면 로봇이 얼마나 회전했는지 알 수 있습니다.
하지만 회전 속도는 IMU의 자이로스코프가 더 빠르고 민감하게 측정할 수 있습니다. 특히 로봇이 순간적으로 회전하거나 바닥에서 살짝 흔들릴 때 IMU가 더 빠르게 반응합니다.
그렇다면 이렇게 생각할 수 있습니다.
전진 속도 v는 엔코더에서 얻자.
회전 속도 omega는 엔코더와 IMU를 함께 섞자.아래 코드는 엔코더가 추정한 회전 속도와 IMU가 측정한 회전 속도를 가중 평균으로 섞는 아주 단순한 예입니다.
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(7)
dt = 0.05
total_time = 12.0
times = np.arange(0, total_time, dt)
true_x = 0.0
true_y = 0.0
true_theta = 0.0
est_encoder = np.array([0.0, 0.0, 0.0])
est_fused = np.array([0.0, 0.0, 0.0])
true_path = []
encoder_path = []
fused_path = []
for t in times:
# 실제 로봇의 명령 속도
v_true = 0.7
omega_true = 0.35 * np.sin(0.6 * t)
# 실제 움직임
true_x += v_true * np.cos(true_theta) * dt
true_y += v_true * np.sin(true_theta) * dt
true_theta += omega_true * dt
# 엔코더 측정: 전진 속도는 비교적 좋지만, 회전은 바퀴 미끄러짐 때문에 오차가 있다고 가정
v_encoder = v_true + np.random.normal(0, 0.03)
omega_encoder = omega_true * 0.85 + np.random.normal(0, 0.05)
# IMU 자이로 측정: 빠르게 회전을 잡지만 작은 바이어스가 있다고 가정
gyro_bias = 0.03
omega_imu = omega_true + gyro_bias + np.random.normal(0, 0.03)
# 엔코더만 사용한 추정
x, y, theta = est_encoder
x += v_encoder * np.cos(theta) * dt
y += v_encoder * np.sin(theta) * dt
theta += omega_encoder * dt
est_encoder = np.array([x, y, theta])
# 엔코더 + IMU 퓨전
# 여기서는 IMU 회전 속도를 조금 더 믿는다고 가정한다.
w_encoder = 0.35
w_imu = 0.65
omega_fused = w_encoder * omega_encoder + w_imu * omega_imu
x, y, theta = est_fused
x += v_encoder * np.cos(theta) * dt
y += v_encoder * np.sin(theta) * dt
theta += omega_fused * dt
est_fused = np.array([x, y, theta])
true_path.append([true_x, true_y])
encoder_path.append(est_encoder[:2])
fused_path.append(est_fused[:2])
true_path = np.array(true_path)
encoder_path = np.array(encoder_path)
fused_path = np.array(fused_path)
plt.figure(figsize=(7, 7))
plt.plot(true_path[:, 0], true_path[:, 1], label="true", linewidth=3)
plt.plot(encoder_path[:, 0], encoder_path[:, 1], "--", label="encoder only")
plt.plot(fused_path[:, 0], fused_path[:, 1], label="encoder + imu")
plt.axis("equal")
plt.grid(True)
plt.legend()
plt.xlabel("x [m]")
plt.ylabel("y [m]")
plt.title("Simple Encoder-IMU Fusion")
plt.show()이 예제는 실제 로봇의 모든 복잡함을 담고 있지는 않습니다. 하지만 중요한 구조는 들어 있습니다. 엔코더는 이동량을 주고, IMU는 회전 변화를 빠르게 알려줍니다. 두 정보를 섞으면 엔코더만 사용할 때보다 궤적이 실제 경로에 가까워질 수 있습니다.
물론 가중치 0.35, 0.65는 마법의 숫자가 아닙니다. 로봇의 바퀴 상태, 바닥 마찰, IMU 품질, 이동 속도에 따라 달라집니다. 더 정교한 시스템에서는 이 가중치를 고정하지 않고, 센서의 분산이나 상황에 따라 바꿉니다.
예를 들어 로봇이 매끄러운 바닥에서 천천히 움직인다면 엔코더를 더 믿을 수 있습니다. 반대로 바닥이 미끄럽거나 로봇이 급회전한다면 IMU의 회전 측정이 더 중요해질 수 있습니다. 센서 퓨전은 이렇게 상황에 따라 신뢰도를 조절하는 문제로 확장됩니다.
보완 필터의 감각
엔코더와 IMU를 섞는 방법으로 자주 떠올릴 수 있는 것이 보완 필터(complementary filter)의 감각입니다. 보완 필터는 이름 그대로 서로 보완하는 센서를 섞습니다.
가령 IMU 자이로스코프는 짧은 시간 변화에 강합니다. 방향이 갑자기 바뀌면 빠르게 잡아냅니다. 하지만 시간이 길어지면 바이어스 때문에 각도가 서서히 틀어집니다. 반대로 카메라나 LiDAR 기반 위치 추정은 느릴 수 있지만, 지도나 랜드마크를 기준으로 누적 오차를 줄일 수 있습니다.
이때 다음과 같은 식을 생각할 수 있습니다.
theta_pred = theta + gyro_omega * dt
theta = alpha * theta_pred + (1 - alpha) * theta_absolutetheta_pred는 자이로스코프로 빠르게 예측한 방향입니다. theta_absolute는 카메라, LiDAR, 마커, 나침반처럼 더 절대적인 기준에서 얻은 방향이라고 생각할 수 있습니다. alpha가 1에 가까우면 빠른 예측을 많이 믿고, 0에 가까우면 절대 측정을 많이 믿습니다.
alpha = 0.98이런 값은 흔히 "빠른 센서의 흐름은 유지하되, 느린 센서가 들어올 때 조금씩 끌어당긴다"는 의미를 가집니다. 마치 길을 걸으면서 발걸음 감각으로 계속 나아가다가, 가끔 지도 앱을 보고 방향을 살짝 고치는 것과 같습니다.
보완 필터는 칼만 필터보다 단순합니다. 그래서 처음 센서 퓨전을 이해할 때 좋은 출발점이 됩니다. 하지만 센서의 불확실성이 시간에 따라 달라지거나, 상태가 여러 차원으로 커지고, 관측 모델이 복잡해지면 칼만 필터나 입자 필터 같은 더 체계적인 방법이 필요해집니다.
지금 단계에서 기억할 핵심은 하나입니다.
빠른 센서는 움직임의 흐름을 만든다.
느린 센서는 흐름이 너무 멀리 틀어지지 않게 잡아준다.카메라와 LiDAR를 함께 쓸 때 생기는 문제
엔코더와 IMU는 모두 로봇의 움직임과 직접 관련된 센서입니다. 반면 카메라와 LiDAR는 주변 환경을 봅니다. 이 둘을 함께 쓰면 로봇은 더 풍부하게 세상을 이해할 수 있습니다. 카메라는 물체의 종류와 색을 보고, LiDAR는 물체까지의 거리를 정확히 알려줄 수 있습니다.
예를 들어 카메라가 이미지 안에서 사람을 발견했다고 해 봅시다. 카메라만으로는 그 사람이 정확히 몇 미터 앞에 있는지 알기 어려울 수 있습니다. 하지만 LiDAR 점들 중 이미지의 사람 위치에 해당하는 점을 찾으면, 로봇은 "사람이 전방 3.2m에 있다"처럼 더 구체적으로 판단할 수 있습니다.
그런데 여기서 중요한 문제가 생깁니다. 카메라와 LiDAR는 같은 위치에 붙어 있지 않습니다. 카메라는 로봇 앞쪽 위에 달려 있고, LiDAR는 몸체 가운데에 달려 있을 수 있습니다. 두 센서는 서로 다른 좌표계를 사용합니다.
카메라 좌표계에서는 앞쪽을 z축으로 볼 수 있고, LiDAR 좌표계에서는 앞쪽을 x축으로 볼 수도 있습니다. 로봇 몸체 좌표계는 또 다를 수 있습니다. 그러므로 카메라가 본 물체와 LiDAR가 측정한 점을 연결하려면 좌표 변환이 필요합니다.
이전에 배운 동차 변환 행렬을 떠올려 봅시다.
p_robot = T_robot_lidar @ p_lidar
p_camera = T_camera_robot @ p_robot또는 한 번에 이렇게 쓸 수도 있습니다.
p_camera = T_camera_robot @ T_robot_lidar @ p_lidar이 식은 단순해 보이지만, 실제 센서 퓨전에서는 매우 중요합니다. 좌표 변환이 조금만 틀려도 카메라 이미지의 사람 위치와 LiDAR 거리점이 어긋납니다. 그러면 로봇은 사람의 위치를 잘못 추정할 수 있습니다.
여기서 필요한 작업을 외부 파라미터 보정(extrinsic calibration)이라고 부릅니다. 말은 어렵지만 뜻은 간단합니다. 센서와 센서 사이의 상대 위치와 방향을 정확히 알아내는 일입니다. 예를 들어 "LiDAR는 카메라보다 10cm 아래에 있고, 3도 왼쪽으로 틀어져 있다" 같은 관계를 구하는 것입니다.
카메라와 LiDAR 퓨전에서는 좌표 정렬만큼 시간 정렬도 중요합니다.
시간 정렬: 같은 순간을 보고 있는가
로봇이 빠르게 움직이고 있을 때, 카메라와 LiDAR가 찍은 시간이 다르면 문제가 생깁니다. 카메라 이미지는 10.00초에 찍혔고, LiDAR 스캔은 10.08초에 완성되었다고 해 봅시다. 0.08초는 짧아 보이지만, 로봇이 빠르게 움직인다면 그 사이 위치가 꽤 달라질 수 있습니다.
센서 퓨전에서는 단순히 값이 들어온 순서대로 섞으면 안 됩니다. 각 데이터가 언제 측정되었는지를 알아야 합니다. 이를 위해 센서 데이터에는 보통 타임스탬프(timestamp)가 붙습니다.
camera_msg = {
"time": 10.00,
"image": None
}
lidar_msg = {
"time": 10.08,
"points": None
}두 센서의 시간을 맞추는 방법은 여러 가지가 있습니다. 가장 단순한 방법은 가장 가까운 시간의 데이터끼리 짝을 짓는 것입니다.
def find_nearest_message(target_time, messages):
best_msg = None
best_diff = float("inf")
for msg in messages:
diff = abs(msg["time"] - target_time)
if diff < best_diff:
best_diff = diff
best_msg = msg
return best_msg, best_diff하지만 로봇이 빠르게 움직이거나 정밀도가 중요하다면 이것만으로는 부족할 수 있습니다. 이때는 로봇의 움직임 모델을 이용해 한 센서의 측정 시점에서 다른 센서의 좌표를 보정해야 합니다. 쉽게 말해 "LiDAR가 찍힌 시점과 카메라가 찍힌 시점 사이에 로봇이 얼마나 움직였는가?"를 계산해 맞춰 주는 것입니다.
시간 정렬을 무시하면 이상한 일이 생깁니다. 카메라는 장애물이 오른쪽에 있다고 보고, LiDAR는 장애물이 정면에 있다고 말하는 것처럼 보일 수 있습니다. 사실 두 센서는 서로 틀린 것이 아니라, 서로 다른 순간을 보고 있었을 뿐입니다.
센서 퓨전에서 시간은 좌표만큼 중요합니다.
좌표 정렬: 같은 기준에서 보고 있는가?
시간 정렬: 같은 순간을 보고 있는가?이 두 질문에 답하지 못하면 아무리 좋은 센서를 많이 붙여도 안정적인 결과를 얻기 어렵습니다.
센서 주기가 다를 때의 업데이트 구조
실제 로봇 프로그램에서는 모든 센서가 동시에 들어오지 않습니다. IMU는 매우 자주 들어오고, 엔코더는 그보다 조금 느릴 수 있으며, 카메라와 LiDAR는 더 느리게 들어올 수 있습니다. 따라서 센서 퓨전 코드는 보통 "모든 데이터를 기다렸다가 한 번에 계산"하지 않습니다.
대신 데이터가 들어올 때마다 상태를 갱신합니다.
IMU가 들어오면 방향과 회전 속도 예측을 갱신한다.
엔코더가 들어오면 이동거리 예측을 갱신한다.
카메라가 들어오면 랜드마크 기준으로 위치를 보정한다.
LiDAR가 들어오면 지도와 맞춰 위치를 보정한다.이를 간단한 의사 코드로 쓰면 다음과 같습니다.
state = initialize_state()
for msg in sensor_stream:
if msg["type"] == "imu":
state = predict_with_imu(state, msg)
elif msg["type"] == "encoder":
state = predict_with_encoder(state, msg)
elif msg["type"] == "camera":
state = correct_with_camera(state, msg)
elif msg["type"] == "lidar":
state = correct_with_lidar(state, msg)이 구조에서 중요한 점은 센서마다 역할이 다르다는 것입니다. 어떤 센서는 예측에 더 자주 쓰이고, 어떤 센서는 보정에 더 자주 쓰입니다. 물론 실제 시스템에서는 IMU도 보정에 쓰일 수 있고, LiDAR도 움직임 추정에 쓰일 수 있습니다. 하지만 처음에는 다음 그림처럼 생각하면 이해하기 쉽습니다.
엔코더 + IMU → 빠른 예측
카메라 + LiDAR → 환경 기준 보정이것이 모바일 로봇의 위치 추정, 자율주행, 드론 자세 추정, SLAM에서 반복해서 등장하는 기본 구조입니다.
신뢰도는 고정되어 있지 않다
처음 센서 퓨전을 배울 때는 센서마다 고정된 신뢰도를 주는 방식이 이해하기 쉽습니다. 예를 들어 "IMU 70%, 엔코더 30%"처럼 정할 수 있습니다. 하지만 실제 로봇에서는 신뢰도가 계속 바뀝니다.
비가 오는 야외에서 카메라 영상이 흐려지면 카메라의 신뢰도는 낮아져야 합니다. 바닥이 미끄럽다면 엔코더의 신뢰도도 낮아져야 합니다. LiDAR가 유리 벽 앞에서 이상한 거리값을 내면 그 순간 LiDAR 측정도 조심해야 합니다. IMU가 심하게 진동하는 곳에 붙어 있다면 자이로와 가속도값도 더 많이 흔들릴 수 있습니다.
따라서 좋은 센서 퓨전 시스템은 센서값 자체뿐 아니라, 그 센서값이 나온 상황도 함께 봅니다. 예를 들어 다음과 같은 판단이 가능합니다.
def encoder_reliability(wheel_slip_detected):
if wheel_slip_detected:
return 0.2
return 0.8
def camera_reliability(brightness, blur_score):
if brightness < 30:
return 0.2
if blur_score > 0.7:
return 0.3
return 0.8이 코드는 실제 로봇에 바로 쓰기에는 단순하지만, 감각을 잡기에는 좋습니다. 센서 퓨전은 "센서값을 섞는 일"이면서 동시에 "언제 무엇을 믿을지 판단하는 일"입니다.
확률 관점에서는 신뢰도가 낮아진다는 말을 분산이 커진다고 표현할 수 있습니다. 카메라가 어두운 곳에서 흔들리는 이미지를 보고 있다면, 카메라 관측의 분산을 크게 잡습니다. 그러면 퓨전 결과에서 카메라가 미치는 영향이 줄어듭니다. 반대로 밝고 선명한 이미지에서 확실한 랜드마크를 찾았다면 분산을 작게 잡아 더 많이 믿을 수 있습니다.
이 점이 5부에서 배운 확률의 핵심과 연결됩니다. 평균은 추정값이고, 분산은 그 추정값을 얼마나 조심해야 하는지를 말해 줍니다.
작은 예제: 느린 위치 센서로 드리프트 줄이기
이번에는 빠른 속도 센서와 느린 위치 센서를 섞어 보겠습니다. 상황은 단순합니다. 로봇이 1차원 직선 위를 움직입니다. 빠른 센서는 속도를 자주 알려주지만 약간의 바이어스가 있습니다. 그래서 속도를 적분하면 위치가 점점 실제와 어긋납니다.
느린 위치 센서는 가끔씩만 들어오지만, 절대 위치를 측정합니다. 노이즈는 있지만 드리프트를 줄이는 데 도움이 됩니다.
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(3)
dt = 0.05
total_time = 20.0
times = np.arange(0, total_time, dt)
true_x = 0.0
est_x_dead_reckoning = 0.0
est_x_fused = 0.0
velocity_bias = 0.04
true_history = []
dead_history = []
fused_history = []
measurement_history = []
measurement_time_history = []
for i, t in enumerate(times):
# 실제 속도: 조금씩 변하는 움직임
true_v = 0.8 + 0.2 * np.sin(0.5 * t)
true_x += true_v * dt
# 빠른 속도 센서: 바이어스와 노이즈 포함
measured_v = true_v + velocity_bias + np.random.normal(0, 0.03)
# 속도 적분만 사용한 위치 추정
est_x_dead_reckoning += measured_v * dt
# 퓨전 추정도 먼저 속도로 예측
est_x_fused += measured_v * dt
# 느린 절대 위치 센서: 1초마다 한 번 들어온다고 가정
if i % int(1.0 / dt) == 0:
measured_x = true_x + np.random.normal(0, 0.25)
# 예측값과 측정값을 섞어 보정
alpha = 0.75
est_x_fused = alpha * est_x_fused + (1 - alpha) * measured_x
measurement_history.append(measured_x)
measurement_time_history.append(t)
true_history.append(true_x)
dead_history.append(est_x_dead_reckoning)
fused_history.append(est_x_fused)
plt.figure(figsize=(9, 4))
plt.plot(times, true_history, label="true", linewidth=3)
plt.plot(times, dead_history, "--", label="velocity integration only")
plt.plot(times, fused_history, label="fused")
plt.scatter(measurement_time_history, measurement_history, s=20, alpha=0.6, label="slow position measurement")
plt.grid(True)
plt.xlabel("time [s]")
plt.ylabel("x [m]")
plt.legend()
plt.title("Fast Drifting Sensor + Slow Absolute Sensor")
plt.show()이 예제를 실행하면 속도 적분만 사용한 추정은 시간이 지날수록 실제 위치에서 점점 멀어질 수 있습니다. 반면 느린 위치 센서를 가끔 섞은 추정은 완벽하지는 않아도 드리프트가 줄어듭니다.
이 장면은 센서 퓨전의 핵심을 매우 잘 보여 줍니다.
빠른 센서만 쓰면 부드럽지만 서서히 틀어진다.
느린 센서만 쓰면 기준은 있지만 반응이 늦고 듬성듬성하다.
둘을 함께 쓰면 빠르면서도 덜 틀어지는 추정을 만들 수 있다.이 구조는 로봇에서 계속 반복됩니다. IMU와 GPS, 엔코더와 카메라, LiDAR와 관성 센서, 바퀴 오도메트리와 SLAM 위치 추정이 모두 이런 감각 위에 있습니다.
센서 퓨전을 설계할 때 묻는 질문들
센서 퓨전 문제를 만나면 바로 복잡한 수식부터 떠올릴 필요는 없습니다. 먼저 좋은 질문을 던지는 것이 중요합니다.
첫 번째 질문은 "각 센서가 무엇을 측정하는가?"입니다. 엔코더는 바퀴 회전량을 측정합니다. IMU는 가속도와 회전 속도를 측정합니다. 카메라는 이미지 밝기와 색의 배열을 측정합니다. LiDAR는 방향별 거리를 측정합니다. 센서가 직접 측정하는 값과 우리가 알고 싶은 상태는 다를 수 있습니다.
두 번째 질문은 "그 측정값을 상태와 어떻게 연결할 것인가?"입니다. 엔코더 회전량은 바퀴 반지름을 통해 이동거리로 바뀝니다. IMU 회전 속도는 적분을 통해 방향 변화가 됩니다. LiDAR 점들은 좌표 변환을 통해 로봇 주변 장애물 위치가 됩니다. 카메라 픽셀은 카메라 모델을 통해 공간상의 방향이나 위치 추정으로 이어집니다.
세 번째 질문은 "언제 이 센서를 덜 믿어야 하는가?"입니다. 바퀴가 미끄러질 때 엔코더를 조심해야 합니다. 진동이 심할 때 IMU를 조심해야 합니다. 어두운 곳에서는 카메라를 조심해야 합니다. 반사가 이상한 물체 앞에서는 LiDAR를 조심해야 합니다.
이 질문들을 조금 더 실무적인 목록으로 정리하면 다음과 같습니다.
센서 데이터에는 정확한 타임스탬프가 있는가?
센서 좌표계와 로봇 좌표계 사이의 변환을 알고 있는가?
각 센서의 노이즈와 바이어스를 어느 정도 알고 있는가?
센서가 실패하거나 이상한 값을 낼 때 감지할 방법이 있는가?
빠른 예측과 느린 보정의 역할이 분리되어 있는가?
추정값뿐 아니라 불확실성도 함께 관리하고 있는가?
이 목록은 앞으로 SLAM, 자율주행, 로봇 팔 비전 제어, 드론 자세 추정 등을 배울 때 계속 다시 만나게 됩니다.
센서 퓨전은 더 믿을 만한 이야기 만들기다
센서 하나하나는 세상의 일부만 봅니다. 엔코더는 바퀴의 이야기를 들려줍니다. IMU는 몸의 흔들림과 회전 이야기를 들려줍니다. 카메라는 빛과 모양의 이야기를 들려줍니다. LiDAR는 거리와 형상의 이야기를 들려줍니다.
각 이야기는 유용하지만 불완전합니다. 센서 퓨전은 이 불완전한 이야기들을 모아 더 일관된 하나의 이야기를 만드는 과정입니다.
로봇이 "나는 지금 여기 있다"고 말하려면 많은 증거가 필요합니다. 바퀴는 이만큼 굴렀고, 몸은 이만큼 회전했으며, 카메라에는 익숙한 문이 보이고, LiDAR 스캔은 지도 속 복도와 잘 맞습니다. 이 증거들이 서로 맞아떨어질 때 로봇의 추정은 강해집니다.
이 장에서 배운 내용은 다음과 같이 정리할 수 있습니다.
엔코더, IMU, 카메라, LiDAR는 각각 잘하는 일과 약한 상황이 다르다.
빠른 센서는 짧은 시간 변화 추적에 좋지만 드리프트가 생길 수 있다.
느린 센서나 환경 기준 센서는 누적 오차를 보정하는 데 도움이 된다.
센서를 결합할 때는 신뢰도, 시간 정렬, 좌표 정렬을 함께 고려해야 한다.
센서 퓨전은 칼만 필터의 예측과 보정 구조로 자연스럽게 확장된다.
이제 5부의 큰 흐름이 마무리되었습니다. 우리는 센서가 왜 틀리는지, 그 오차를 확률로 어떻게 표현하는지, 베이즈 추정과 칼만 필터가 어떤 감각으로 동작하는지, 그리고 여러 센서를 어떻게 함께 써야 하는지 살펴보았습니다.
다음 부에서는 관점이 조금 바뀝니다. 지금까지는 "로봇이 세상을 어떻게 추정하는가?"에 집중했다면, 이제는 "로봇이 여러 선택지 중 무엇을 선택해야 하는가?"로 넘어갑니다. 그 출발점이 바로 최적화입니다. 로봇은 가장 짧은 길, 가장 안전한 경로, 가장 작은 오차, 가장 부드러운 움직임을 찾으려 합니다. 그 모든 문제의 중심에는 비용을 정의하고 줄여 나가는 생각이 있습니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.