정리된 파형은 보기 좋지만, 사용자가 알고 싶은 것은 결국 하나입니다. 지금 심박수가 얼마인가. 답은 피크의 높이에 있지 않습니다. 피크와 피크 사이의 거리에 있습니다.

MonoPump의 방법은 일부러 단순합니다. 그럴듯한 피크를 찾고, 타임스탬프로 간격을 재고, 의심스러운 간격을 제거하고, 초를 BPM으로 바꾼 뒤, UI가 흔들리지 않도록 평활화합니다.
This diagram could not be rendered. Its source is still available below.
View diagram source
flowchart LR
A[정리된 PPG 윈도우] --> B[적응형 피크 임계값]
B --> C[국소 최대값]
C --> D[피크 간격 목록]
D --> E[중앙값 기반 이상치 필터]
E --> F[BPM = 60 / 평균 간격]
F --> G[EMA 평활화]
왜 FFT부터 쓰지 않았나
FFT는 아름다운 모델을 줍니다. 맥박 대역에서 가장 강한 주파수를 찾고 BPM으로 바꾸면 됩니다. 하지만 모바일 카메라의 짧고 지저분한 윈도우에서는 그 우아함이 항상 이기지 않습니다. 움직임, 프레임 누락, 접촉 불안정이 있으면 잘못된 주파수가 자신 있어 보일 수 있습니다.
피크 검출은 화려하지 않지만 디버깅하기 쉽습니다. 추정이 틀리면 피크와 간격을 직접 볼 수 있습니다.
피크는 시간 표식이다
MonoPump는 적응형 임계값을 넘는 국소 최대값을 찾습니다. 동시에 한 번의 박동이 여러 작은 피크로 쪼개지지 않도록 피크 사이 최소 거리를 둡니다.
minPeakDistance = sampleRate * 60 / maxBPM
threshold = mean + 0.5 * standardDeviation
고정 임계값은 신호 크기가 안정적일 때만 좋습니다. 카메라 PPG의 진폭은 충분히 예측 가능하지 않기 때문에 윈도우마다 임계값을 맞춥니다.
This diagram could not be rendered. Its source is still available below.
View diagram source
flowchart TD
P1[t1 피크] --> I1[t2 - t1]
P2[t2 피크] --> I2[t3 - t2]
P3[t3 피크] --> I3[t4 - t3]
I1 --> M[중앙값 간격]
I2 --> M
I3 --> M
M --> F[너무 벗어난 간격 제거]
숫자를 안정시키되 숨기지 않는다
원시 BPM은 업데이트마다 흔들립니다. 모두 보여 주면 앱이 불안해 보입니다. 반대로 숫자를 너무 고정하면 가짜처럼 느껴집니다. MonoPump는 EMA를 쓰고 업데이트마다 변할 수 있는 폭도 제한합니다.
smoothed = 0.35 * current + 0.65 * previous
smoothed = clamp(smoothed, previous - maxDelta, previous + maxDelta)
신뢰도는 장식이 아닙니다. 사용 가능한 피크 간격 수와 최근 신호 변화가 최종 결과의 문지기 역할을 합니다. 숫자를 출력하는 것은 쉽습니다. 출력하지 않을 때를 아는 것이 더 어렵습니다.