<!doctype html>
🐾 Android | Kalman vs EMA — 고도/센서 데이터 필터링 비교와 적용기

개요 (Intro)
- 오늘의 목표 / 배경: 위치·고도 데이터(바로미터 + PDR/IMU)를 안정적으로 시각화/동기화하기 위해 Kalman 필터와 EMA(지수평활)의 차이를 비교 분석하고, 프로젝트에 적용할 방향을 정리한다.
- 해결하려는 문제: 그래프의 NaN 크래시 방지, 스파이크(이상치)와 드리프트에 강한 필터링, 실시간성(응답속도) 유지 간의 균형.
- 사용한 기술 스택: Kotlin, Jetpack Compose, Vico chart, Coroutines, Wear OS
📅 날짜: 2025.12.09
🎯 목표: Kalman/EMA 특성 비교 및 프로젝트 적용 가이드 정리
🧰 기술: Kotlin, Compose, Vico, Wear OS, Coroutines
문제 정의 (Problem / Motivation)
- 바로미터(고도) 데이터는 환경/기압 변화에 민감해 노이즈와 스파이크가 잦음.
- PDR/IMU는 누적 드리프트가 생기기 쉬움(특히 장시간).
- 그래프에 NaN이 들어가면 마커/그리기 로직에서 크래시 위험(실제 NaN round 예외 경험).
- 실시간 UI에서는 빠른 반응과 안정적 시각화 둘 다 필요.
// NaN 방어를 위해 앞값 채움(fill-forward)으로 시계열을 안전화한 부분
fun fillForward(arr: FloatArray): List<Float> {
val out = ArrayList<Float>(arr.size)
var last = 0f
var seen = false
for (i in arr.indices) {
val v = arr[i]
if (!v.isNaN()) { last = v; seen = true; out.add(v) }
else { out.add(if (seen) last else 0f) }
}
return out
}
해결 과정 (How I Solved It)
EMA(지수평활) — 간단·저비용 필터
- 정의: y[n] = α·x[n] + (1-α)·y[n-1]
- 특징: 구현 매우 쉬움, 레이턴시 낮음, 이상치에는 약함.
// 초보자도 이해하기 쉬운 EMA 필터 — 주석 상세 버전
class EmaFilter(private val alpha: Float) {
private var state: Float? = null // 이전 출력값(초기엔 없음)
// 새 입력값 x를 받아 필터링된 값 반환
fun update(x: Float): Float {
val s = state
val out = if (s == null) {
// 첫 샘플은 기준값으로 사용
x
} else {
// EMA 핵심 — 현재 입력과 이전 출력의 가중 평균
alpha * x + (1 - alpha) * s
}
state = out
return out
}
fun reset() { state = null }
}
Kalman — 상태공간 기반 최적 추정(선형/가우시안 가정)
- 특징: 모델(A,H), 노이즈(Q,R)로 예측/갱신. 드리프트·노이즈 균형적으로 제어.
- 장점: 통계적으로 최적(가정 하), 속도/가속 등 상태 추정 가능.
- 단점: 튜닝 복잡, 계산량 EMA보다 큼.
// 1D 고도용 아주 간단한 Kalman — 주석 상세
class SimpleKalman1D(
private var q: Float, // 프로세스(모델) 노이즈 공분산 — 모델 불확실성
private var r: Float // 측정 노이즈 공분산 — 센서 불확실성
) {
private var x: Float = 0f // 상태(고도)
private var p: Float = 1f // 상태 오차 공분산
fun init(initial: Float, initialP: Float = 1f) {
x = initial
p = initialP
}
// 예측 단계 — 간단 모델(고정)에서는 p만 늘려 불확실성 반영
fun predict() {
p += q
}
// 갱신 단계 — 관측 z 반영
fun update(z: Float): Float {
val k = p / (p + r) // 칼만 이득(측정 신뢰도 vs 상태 신뢰도 비율)
x = x + k * (z - x) // 상태 보정
p = (1 - k) * p // 오차 공분산 업데이트
return x
}
}
상보(Complementary) 융합 — 간단 융합법
- 예: fusedZ = α·pdrZ + (1-α)·baroZ
- 장점: 직관적인 튜닝, 구현 쉬움.
- 단점: 노이즈 통계 반영 없음, 이상치 처리 필요.
// 프로젝트에서 사용 중인 융합 아이디어(유사): baro와 ENU zUp을 비율로 합성
val fusedZ = altitudeFusionRatio * enu.zUp + (1 - altitudeFusionRatio) * baro
결과 (Result)
- 그래프 입력의 NaN 제거(앞값 채움)로 마커 크래시 방지.
- EMA/상보/칼만의 트레이드오프를 정리해 적용 방향을 확립.
- 실시간 UI 요구가 큰 경우: EMA 또는 상보 필터부터 적용하고, 필요 시 칼만으로 단계적 고도화.
✅ 실시간성(EMA/상보)과 안정성(칼만)의 균형을 프로젝트 요구에 맞게 선택할 수 있도록 기준을 수립.
느낀 점 / 회고 (Reflection)
- NaN 같은 ‘형식적 오류’도 시각화 라이브러리에서 큰 크래시 원인이 된다 — 입력 정규화가 최우선.
- EMA는 UI엔 매우 좋은 출발점. 칼만은 모델·노이즈 튜닝이 핵심 — 로그 기반 정량 튜닝이 필요.
- 상보 필터는 간단하지만 이상치 방어(스파이크 클리핑/중앙값 필터)와 함께 쓰면 체감 품질이 훨씬 좋아진다.
참고자료 (References)
- Kalman Filter — Greg Welch & Gary Bishop, "An Introduction to the Kalman Filter"
- Signal Processing Stack Exchange — EMA/Moving Average 비교 토론
- Android Sensor Docs — Barometer, SensorManager Delay 등
- Vico Chart — https://github.com/patrykandpatrick/vico
다음 단계 제안
- 바로미터 측정에 중앙값(Median) 또는 MAD 기반 이상치 제거를 추가.
- 상보 필터 계수(altitudeFusionRatio)를 프로파일(배터리/표준/고속)에 따라 자동 조정.
- 간단한 1D 칼만(고도+속도)로 샘플 러닝 테스트 — Q/R 튜닝 로그(innovation) 기록.
반응형
'모바일 앱(안드로이드)' 카테고리의 다른 글
| 🧪 테스트 시나리오 | AiAutoSelector 단위 테스트 실패 → 가중치 조정 및 외부 설정 리팩터링 (1) | 2025.12.13 |
|---|---|
| 🦾 Android | 워치앱 빌드 오류 수정과 UI/국제화 개선 정리 (1) | 2025.12.11 |
| 🧭 Android | 위치 수신·히스토리 저장 및 Vico 그래프 NaN 크래시 대응 (1) | 2025.12.07 |
| ⌚ Wear OS | 센서 수명주기·권한·동기화 연결로 기본 데이터 파이프 완성 (2) | 2025.12.05 |
| 🛰️ Android | 워치-폰 동기화 + 위치(x,y,z)+고도 융합(칼만/EMA) 기능 통합 작업 기록 (0) | 2025.12.03 |