칼만 필터
칼만 필터
내비게이션 서비스에서 GPS 노이즈를 제거하고 부드러운 위치 추적을 구현하기 위한 칼만 필터 학습 문서
1. GPS 노이즈란?
GPS가 알려주는 위치는 정확한 내 위치가 아니다. 항상 오차가 포함되어 있다.
가만히 서 있어도 GPS 좌표는 튄다
실제 위치: (37.5665, 126.9780) ← 고정
GPS 측정값 (1초 간격):
1초: (37.5667, 126.9782) ← 2m 북동쪽으로 튐
2초: (37.5663, 126.9778) ← 3m 남서쪽으로 튐
3초: (37.5666, 126.9781) ← 1m 정도 튐
4초: (37.5660, 126.9785) ← 6m나 튐!
이걸 그대로 지도에 표시하면 마커가 덜덜 떨리면서 이리저리 움직이는 것처럼 보인다.
오차가 생기는 이유
- 대기(전리층/대류층) 통과하면서 신호 지연
- 건물 반사(멀티패스) — 도심에서 특히 심함, 빌딩에 반사된 신호가 섞임
- 위성 배치 — 위성이 한쪽에 몰려있으면 정확도 하락
- 실내/터널 — 신호 차단
일반 스마트폰 GPS 오차 범위: 보통 3~15m, 도심 빌딩 사이에서는 20~50m까지 벌어질 수 있음.
GPS 노이즈 필터링이란?
이 튀는 값(노이즈)을 걸러내서 실제 위치에 가깝게 보정하는 과정이다.
2. 칼만 필터란?
1960년 루돌프 칼만(Rudolf Kálmán)이 발표한 알고리즘으로, NASA 아폴로 우주선 항법 시스템에 처음 실전 적용되었다.
핵심 아이디어
“불확실한 두 가지 정보를 합쳐서, 각각보다 더 정확한 추정을 만든다”
직관적 비유
눈을 감고 걷고 있다고 상상하자. 내 위치를 알 방법이 두 가지 있다:
방법 1: 내 감각 (예측)
"나는 1초에 한 걸음(1m) 앞으로 가니까,
지금쯤 출발점에서 5m 왔겠지"
방법 2: 친구가 알려줌 (GPS 측정)
"야, 너 지금 4.5m 지점에 있어!"
(근데 이 친구 눈이 좀 나빠서 정확하진 않음)
칼만 필터는 매초마다 이 두 방법을 번갈아 사용한다.
핵심 공식
최종 추정 = 예측 × (1-K) + GPS 측정 × K
- K = 칼만 이득 (Kalman Gain): GPS 측정값에 주는 가중치 (0~1 사이)
- (1-K): 예측값에 주는 가중치
- K + (1-K) = 1: 두 가중치의 합은 항상 1 (= 100%)
(1-K)를 하는 이유
두 정보의 가중치 합이 항상 1(=100%)이 되어야 결과값이 두 값 사이의 합리적인 지점에 오기 때문이다.
K = 0.3일 때 (GPS를 30% 신뢰):
최종 = 예측 × 0.7 + GPS × 0.3 → 예측 쪽에 가까운 값
K = 0.8일 때 (GPS를 80% 신뢰):
최종 = 예측 × 0.2 + GPS × 0.8 → GPS 쪽에 가까운 값
K = 0일 때: 예측 그대로 사용
K = 1일 때: GPS 그대로 사용
수식 변환 (실제 코드에서 쓰이는 형태)
최종 = 예측 × (1-K) + GPS × K
= 예측 + K × (GPS - 예측)
─────────
"잔차" (두 값의 차이)
→ “예측값에서 출발해서, GPS와의 차이를 K 비율만큼만 보정한다”
3. 칼만 필터 2단계 사이클
칼만 필터는 매 GPS 측정이 들어올 때마다 예측 → 보정 두 단계를 반복한다.
3-1. 1단계: 예측 (Predict) — “내 감각으로 추측하기”
GPS가 오기 전에, 내가 아는 정보만으로 지금 위치를 추측한다.
하는 일
이전에 내가 있던 곳: 10m 지점
내 속도: 초당 2m, 북쪽으로
→ 1초 지났으니까, 지금은 12m 지점이겠지!
정말 단순한 산수: 이전 위치 + 속도 × 시간 = 예측 위치
하지만 자신은 없음
- 바람이 불어서 살짝 밀렸을 수도 있고
- 속도가 정확히 2m/s가 아닐 수도 있고
- 도로가 살짝 커브였을 수도 있고
그래서 예측할 때 “나 좀 불확실해”라는 표시도 같이 한다:
이전 불확실성: ±3m
↓
시간이 지남 (1초)
↓
예측 불확실성: ±5m (시간 지나니까 더 불확실해졌어)
시간이 지나면 불확실성은 항상 커진다. 눈 감고 걷는데, 오래 걸을수록 내가 어디 있는지 점점 모르게 되는 것과 같다.
1단계 요약
📍 예측 위치 = 이전 위치 + 이동한 거리
❓ 불확실성 = 이전 불확실성 + 약간 더 (커짐!)
→ 아직 GPS는 안 봤음. 순전히 내 추측.
3-2. 2단계: 보정 (Update) — “친구 말도 듣고 합치기”
GPS 측정값이 도착했다!
상황
내 예측: "나는 12m 지점에 있어" (불확실성 ±5m)
GPS 측정: "너는 10m 지점에 있어" (불확실성 ±8m)
Step A: 누구를 더 믿을지 결정 (칼만 이득 K)
내 불확실성이 작으면 → K 작음 → 내 예측을 더 믿음
GPS 불확실성이 작으면 → K 큼 → GPS를 더 믿음
Step B: 두 정보 합치기
최종 위치 = 내 예측 + K × (GPS - 내 예측)
숫자 예시:
내 예측: 12m, GPS: 10m, K: 0.3
최종 = 12 + 0.3 × (10 - 12)
= 12 + 0.3 × (-2)
= 11.4m ← 최종 추정!
Step C: 불확실성이 줄어듦 (보물찾기 비유)
나: "보물은 이 근처 어딘가야" (큰 원)
친구: "보물은 저 근처 어딘가야" (큰 원)
두 원이 겹치는 부분 → 작은 영역 → 이게 보정 후 불확실성!
내 예측만 쓸 때: ±5m
GPS만 쓸 때: ±8m
둘 합친 후 (보정 후): ±4.2m ← 둘 다보다 작아졌다!
부정확한 정보 두 개를 합치면, 각각보다 더 정확해진다. 이게 칼만 필터의 마법.
2단계 요약
📡 GPS 측정값 도착!
⚖️ 누구를 더 믿을지 결정 (K 계산)
🎯 예측과 GPS를 적절히 섞어서 최종 위치 결정
📉 불확실성이 줄어듦!
3-3. 전체 사이클 예시
시작: 내가 5m 지점에 있다고 알고 있음 (불확실성 ±2m)
[1초 후]
① 예측: "1초 지났고 속도가 2m/s니까 7m 지점이겠지"
불확실성 ±2m → ±4m (커짐)
② GPS 도착: "6.5m 지점이야" (불확실성 ±6m)
③ 합치기: K = 작음 (내 예측이 더 확실하니까)
최종 = 6.8m / 불확실성 ±3m (줄어듦)
[2초 후]
① 예측: "6.8m에서 2m 더 갔으니 8.8m이겠지"
불확실성 ±3m → ±5m (또 커짐)
② GPS 도착: "9.2m 지점이야" (불확실성 ±4m)
③ 합치기: K = 좀 큼 (이번엔 GPS가 좀 더 확실하니까)
최종 = 9.0m / 불확실성 ±3m (또 줄어듦)
[3초 후]
① 예측 → ② GPS 도착 → ③ 합치기 ... 계속 반복!
4. 불확실성은 어떻게 구하는가?
4-1. “불확실성”이란
“실제 값이 이 범위 안에 있을 거야”라는 의미. 수학에서는 분산(variance)이라는 숫자로 표현한다.
"나는 10m 지점에 있어 (불확실성 ±3m)"
→ 실제로는 7m ~ 13m 사이 어딘가
→ 10m에 가까울 확률이 높고, 멀어질수록 확률이 낮아짐
4-2. 출발점: GPS의 accuracy 값
브라우저의 Geolocation API가 좌표와 함께 accuracy도 같이 준다:
navigator.geolocation.watchPosition((pos) => {
console.log(pos.coords.latitude); // 37.5665
console.log(pos.coords.longitude); // 126.9780
console.log(pos.coords.accuracy); // 12 ← 불확실성 (미터)
});
accuracy = 12의 의미: “실제 위치가 이 좌표 중심으로 반경 12m 원 안에 있을 확률이 약 68%”
상황별 accuracy 값:
하늘 탁 트인 곳: 3~5m (자신 있음)
일반 도심: 8~15m (보통)
빌딩 사이 골목: 20~50m (자신 없음)
실내/터널: 100m+ (거의 모름)
4-3. 불확실성이 변하는 3가지 순간
순간 1: 처음 시작할 때
아무 정보도 없으니, GPS accuracy를 그대로 쓴다.
this.variance = accuracy * accuracy; // 분산 = accuracy²
왜 제곱? → 수학에서 불확실성을 다룰 때 분산(variance) 단위를 쓰는데, 분산은 오차의 제곱이다. (수학의 약속/규칙)
accuracy = 10m → 분산 = 100 (m²)
다시 미터로: √100 = 10m
순간 2: 예측할 때 — 불확실성이 커짐
const Q = 3; // "1초마다 이만큼 불확실해진다"는 상수
this.variance += Q;
이전 분산: 9 (= ±3m)
Q를 더함: 9 + 3 = 12
새 불확실성: √12 ≈ ±3.46m (커졌다!)
Q 값은 개발자가 정하는 값 (실험으로 조절):
Q가 크면 (예: 10): GPS를 더 자주 믿게 됨. 반응 빠르지만 노이즈에 약함
Q가 작으면 (예: 1): 예측을 더 오래 믿음. 부드럽지만 실제 변화에 느림
GPS 내비게이션에서는 Q = 2~5 정도가 무난
순간 3: GPS로 보정할 때 — 불확실성이 줄어듦
const K = this.variance / (this.variance + accuracy * accuracy);
this.variance = (1 - K) * this.variance;
숫자 예시:
내 예측 분산: 25 (= ±5m)
GPS 분산: 64 (= ±8m)
K = 25 / (25 + 64) = 25/89 ≈ 0.28
보정 후 분산 = (1-0.28) × 25 = 18
보정 후 불확실성 = √18 ≈ ±4.2m
결과:
내 예측만: ±5m / GPS만: ±8m / 합친 후: ±4.2m ← 둘 다보다 작아짐!
4-4. 불확실성 변화 전체 흐름
[시작]
분산 = GPS accuracy²
[매 사이클마다 반복]
① 예측 단계: 분산 = 분산 + Q ← 커진다 (시간이 지나서)
② 보정 단계: 분산 = (1-K) × 분산 ← 작아진다 (정보가 합쳐져서)
→ 다음 사이클로...
불확실성은 예측하면 커지고, GPS로 보정하면 줄어드는 걸 계속 반복한다. 마치 풍선을 부풀렸다(예측) 눌렀다(보정) 반복하는 것과 같다.
5. 실제 코드
class KalmanFilter {
private lat: number = 0;
private lng: number = 0;
private variance: number = -1; // 불확실성의 제곱 (분산)
process(lat: number, lng: number, accuracy: number, timestamp: number) {
if (this.variance < 0) {
// 첫 측정: 아무 정보 없으니 GPS를 그대로 신뢰
this.lat = lat;
this.lng = lng;
this.variance = accuracy * accuracy;
return { lat, lng };
}
// ① 예측 단계: 불확실성 증가
const Q = 3;
this.variance += Q;
// ② 보정 단계
const K = this.variance / (this.variance + accuracy * accuracy);
// K가 작으면 → GPS를 덜 믿음 (내 예측을 더 따름)
// K가 크면 → GPS를 더 믿음
this.lat += K * (lat - this.lat); // 위치 보정
this.lng += K * (lng - this.lng);
this.variance *= 1 - K; // 불확실성 감소
return { lat: this.lat, lng: this.lng };
}
}
6. 필터 적용 전 vs 후
필터 없이 (원시 GPS):
- 마커가 1초마다 2~10m씩 튀어다님
- 가만히 있어도 마커가 떨림
- 주행 중 마커가 도로 밖으로 나갔다 들어왔다 반복
칼만 필터 적용 후:
- 마커가 부드럽게 이동
- 정지 시 거의 안 움직임
- 주행 중 도로 위를 자연스럽게 따라감
7. 실제 서비스에서는? (칼만 필터 + 맵 매칭)
실제 내비게이션 앱은 칼만 필터에 추가로 맵 매칭(Map Matching)도 함께 사용한다:
칼만 필터 보정 위치: 도로에서 3m 옆 인도 위
│
▼
맵 매칭 알고리즘
"이 위치에서 가장 가까운 도로 위 지점은?"
│
▼
최종 표시 위치: 도로 위에 정확히 스냅
칼만 필터(노이즈 제거) + 맵 매칭(도로 스냅) 조합으로 마커가 항상 도로 위를 매끄럽게 이동하는 것처럼 보이게 만든다.