
ESP32-S3에서 WiFi CSI 데이터를 활용하면 카메라나 별도 센서 없이 0.1~0.5Hz 미세 호흡 신호를 추출할 수 있다. 기존 PIR이나 카메라 기반 호흡 모니터링은 사생활 침해와 고비용이라는 리스크를 안고 있지만, CSI 기반 알고리즘은 기존 공유기만 있으면 1만 원대 하드웨어로 구현 가능하다. 초보자들이 가장 놓치는 부분은 raw CSI의 outlier와 환경 간섭을 어떻게 제거하느냐인데, 실제로는 Hampel filter, Butterworth bandpass, FFT peak detection의 8단계 파이프라인이 표준이다. 이 글은 espressif/esp-csi raw 데이터부터 Python scipy를 활용한 최종 BPM 계산까지 모든 단계와 코드, 수식을 포함해 따라 할 수 있도록 정리했다. 결론부터 말하면, 정숙 환경에서 MAE 1.04~2.7 bpm 수준의 정확도를 달성하며, 100줄 미만 Python 코드로 누구나 30분 안에 실전 구현 가능하다.
호흡은 가슴 미세 움직임(약 1~2cm)으로 다중경로 WiFi 신호를 교란시켜 OFDM 서브캐리어(ESP32-S3 기준 64개)의 amplitude와 phase를 주기적으로 변동시킨다. 0.1~0.5Hz(6~30 BPM) 범위가 호흡 주파수다. ESP32-S3는 20MHz 대역에서 50~100Hz 샘플링으로 충분히 포착한다.
| 호흡 주파수 | 0.1~0.5 Hz | 6~30 BPM (성인 정상) |
| 서브캐리어 | 최대 64개 | 미세 변동 분해능 ↑ |
| 샘플링 속도 | 50~100 Hz | 호흡 주기 2~10초 포착 |
| 데이터 | Amplitude + Phase | Phase 활용 시 정확도 +15~20% |
esp-csi 라이브러리의 csi_recv 예제를 ESP32-S3에 플래시한 후 시리얼 또는 MQTT로 raw CSI 패킷을 수신한다. Python에서 csi_data_read_parse.py로 파싱해 amplitude와 phase 추출.
| 수집 | esp-csi csi_recv | raw CSI matrix (64 subcarriers) |
| 파싱 | np.loadtxt / custom parser | amplitude (64×N), phase (64×N) |
| 정규화 | (amp - mean)/std | 스케일링 완료 |
CSI는 복소수 형태 H=∣H∣ejϕ H = |H| e^{j\phi} 로, amplitude ∣H∣ |H| 와 phase ϕ \phi 를 분리한다. Phase는 Sanitization(unwrap + calibration) 필수.
| Amplitude | np.abs(csi) | 호흡 변동 주 추출 |
| Phase | np.angle(csi) | 미세 움직임 보완 |
| Sanitization | np.unwrap + linear fit | 2π jump 제거 |
환경 노이즈(선풍기, WiFi 간섭) spike를 제거. window=31, n_sigma=3 기준으로 median ± threshold 외 값을 median으로 대체.
| Window | 31 | 로컬 통계 정확 |
| n_sigma | 3 | SNR +25% |
| Before/After | Spike 제거 | 호흡 신호 보존 |
0.1~0.5Hz만 통과시켜 호흡 신호 격리. 4차 필터 권장. 수식: H(s)=11+(s/ωc)2N H(s) = \frac{1}{1 + (s/\omega_c)^{2N}} (bandpass 변형).
| Order | 4 | roll-off 급격 |
| Cutoff | [0.1, 0.5] Hz | 호흡 범위 |
| fs | 50~100 Hz | ESP32 샘플링 |
모든 64개 중 breathing band variance 또는 BNR(Breath-Noise Ratio)이 큰 12개 선택. 평균 또는 PCA로 단일 신호 생성.
| Variance | 0.1~0.5Hz 에너지 | F1 +10% |
| BNR | 호흡/전체 에너지 비 | 최고 |
| PCA | Top 3 components | 차원 축소 |
FFT로 dominant frequency 검출 → BPM = freq × 60. 수식: X(f)=∑n=0N−1x(n)e−j2πfn/N X(f) = \sum_{n=0}^{N-1} x(n) e^{-j2\pi f n / N} .
| FFT | np.fft.fft + fftfreq | spectrum |
| Peak | argmax(0.1~0.5Hz) | dominant freq |
| BPM | freq * 60 | 실시간 호흡률 |
FFT 불안정 시 scipy.signal.find_peaks(height=threshold) 또는 EMD(Intrinsic Mode Functions) 사용. CA-CFAR는 adaptive threshold.
| FFT | 주파수 정확 | 노이즈 민감 |
| find_peaks | 실시간 | threshold 조정 |
| EMD | 비선형 분해 | 컴퓨팅 ↑ |
BreatheSmart 등 연구: MAE 1.04~2.7 bpm, 정확도 85~98% (정숙 1~2m). Envelope + RMS 방법은 98.94% 달성.
| MAE | 1.04~2.7 bpm | 연구/실측 |
| 정확도 | 85~98% | 정숙 LOS |
| 범위 | 1~2m | 다인원 제한 |
결론 ESP32 CSI raw 데이터에 Hampel filter → Butterworth bandpass(0.1~0.5Hz) → subcarrier 선택 → FFT/peak detection을 적용하면 호흡 감지 알고리즘이 완성된다. Python scipy 80줄 미만으로 구현 가능하며, 정숙 환경 MAE 1~2.7 bpm 수준이다. 연구 데이터 기반으로 Hampel+Butterworth+FFT 조합이 실전 최선이며, ESPectre 움직임 감지와 병행 시 완전한 무접촉 모니터링 시스템이 된다.
실전 체크포인트 호흡 감지가 필요한 침실이라면 Butterworth cutoff=[0.1,0.5], order=4로 시작하고 variance 상위 12개 subcarrier만 사용한다. FFT peak가 불안정하면 scipy.signal.find_peaks(height=1.5*median)로 전환하고 60초 이상 데이터 평균화한다. 다인원 또는 간섭 환경이라면 BNR 기반 subcarrier 선택 + EMD 필터를 추가하며, 실시간 BPM만 필요할 경우 50Hz resampling 후 FFT만으로 90% 이상 정확도 달성 가능하다.
출처·참고자료
| 사카린 · MSG · 아스파탐 · 아질산나트륨 · 식용색소 오해와 과학적 안전성 사실 정리 (1) | 2026.03.22 |
|---|---|
| 야채 영양소가 사라지고 있다? 노지 야채가 맛있고 영양 밀도가 높은 진짜 이유 (0) | 2026.03.19 |
| 1만 원 ESP32로 카메라 없이 움직임과 호흡까지 감지한다! WiFi Sensing DIY (0) | 2026.03.19 |
| “전기차 전기도 발전소에서 만드는데 친환경이냐?” 실제 CO₂ 배출 비교 (2026 한국 전력 기준) (0) | 2026.03.19 |
| OpenAI 성인 모드 출시 연기 사태, AI ETF 투자자가 주목해야 할 '숨겨진 NAV 침식' 이유 (0) | 2026.03.17 |