스마트폰 카메라로 심박수를 재려 했을 때 가장 의외였던 것은 알고리즘이 아니었습니다. 손가락으로 렌즈와 플래시를 덮으면 화면은 붉게 흐려 보입니다. 그런데 수백 프레임을 이어 보면 그 안에 작은 리듬이 있습니다.

MonoPump는 그 리듬을 사용합니다. 사진에서 심장을 인식하는 것이 아니라, 카메라를 광학 센서처럼 사용해 빨간 채널의 변화를 시간 축으로 모으고 BPM을 추정합니다.
먼저 보이는 현상
플래시 빛이 손끝에 들어가면 심장 박동에 따라 혈액량이 변하고, 빛의 흡수와 산란도 조금씩 달라집니다. 한 장의 프레임은 의미가 거의 없습니다. 연속된 프레임이 파형을 만듭니다.
This diagram could not be rendered. Its source is still available below.
View diagram source
flowchart LR
A[플래시가 손끝을 비춤] --> B[혈액량이 박동마다 변함]
B --> C[빨간 채널 강도가 조금씩 흔들림]
C --> D[시계열 신호 생성]
D --> E[피크를 찾아 BPM 추정]
처음에는 어려운 부분이 고급 신호 처리라고 생각했습니다. 실제로는 그 전에 이 프레임을 신호로 써도 되는가를 판단하는 일이 먼저였습니다. 손가락이 떠 있거나, 빛이 새거나, 누르는 힘이 흔들리거나, 노출이 계속 움직이면 맥박보다 노이즈가 더 크게 보입니다.
카메라를 먼저 안정화한다
MonoPump의 캡처 경로는 단순합니다. 후면 광각 카메라, BGRA 프레임, 30 fps, 플래시 켜기, 그리고 빛이 안정된 뒤 노출 고정. 자동 노출이 계속 움직이면 우리가 보려는 작은 밝기 변화를 카메라가 보정해 버릴 수 있습니다.
This diagram could not be rendered. Its source is still available below.
View diagram source
sequenceDiagram
participant UI as 측정 화면
participant C as CameraManager
participant F as 프레임 분석기
UI->>C: 카메라 프레임 요청
C->>C: 30 fps BGRA 출력 설정
C->>C: 플래시 켜기와 노출 고정
C-->>F: CMSampleBuffer 스트림
F-->>UI: 덮임 상태, 파형, 신뢰도
프레임 자체가 심박 샘플은 아닙니다. MonoPump는 중앙 영역을 샘플링하고 평균 RGB, 밝기, 빨간색 비율, 빨간색 우세도를 계산합니다.
redRatio = avgRed / max((avgGreen + avgBlue) / 2, 1)
redDominance = avgRed / max(avgGreen, avgBlue)
redIntensity = redRatio * (avgRed / 255.0)
중요한 점은 두 질문을 분리하는 것입니다. 이 프레임은 손가락으로 렌즈가 덮인 상태처럼 보이는가. 그렇다면 파형에 넣을 값은 무엇인가. MonoPump는 의료 진단이 아니라 품질과 신뢰도를 함께 보여주는 설명 가능한 추정으로 다룹니다.