🧠 자세 판정 로직
거북이경보가 웹캠 한 장으로 어떻게 거북목과 구부정한 자세를 가려내는지, 그리고 왜 사용자 캘리브레이션이 반드시 필요한지 정리한 문서입니다.
1. 입력 — MoveNet 키포인트
TensorFlow.js 의 MoveNet SINGLEPOSE_LIGHTNING 모델로 웹캠 한 프레임에서 17 개 키포인트(코·눈·귀·어깨·팔꿈치·손목·엉덩이·무릎·발목)와 각각의 신뢰도(score)를 얻습니다. 자세 판정에 실제로 쓰는 건 그중 5 개입니다.
| 키포인트 | 역할 |
|---|---|
nose | 고개의 수직 위치 — 코가 어깨에 가까울수록 고개가 숙여진 신호 |
left_shoulder / right_shoulder | 기준점이자 거리 정규화 단위. 두 어깨 사이 너비로 모든 비율을 나눠 카메라 거리 영향을 제거 |
left_ear / right_ear | 거북목 보조 신호. 정면 카메라에서 거북목이 심해질수록 귀가 어깨 높이에 가까워짐 |
2. 정규화된 지표 계산
픽셀 좌표를 그대로 쓰면 카메라 거리에 따라 값이 출렁입니다. 모든 지표는 어깨 너비로 나눠 카메라와 사용자 거리에 무관한 비율로 변환합니다.
shoulderWidth = |leftShoulder.x − rightShoulder.x|
shoulderMidY = (leftShoulder.y + rightShoulder.y) / 2
noseShoulderRatio = (shoulderMidY − nose.y) / shoulderWidth
earShoulderRatio = (shoulderMidY − ear.y) / shoulderWidth
shoulderTilt = |leftShoulder.y − rightShoulder.y| / shoulderWidth
noseOffset = |nose.x − shoulderMidX| / shoulderWidth
headTilt = |leftEar.y − rightEar.y| / shoulderWidth
비율이 클수록 머리가 어깨 위로 충분히 들려있는 좋은 자세, 작아질수록 코·귀가 어깨 높이로 내려와 거북목·구부정 신호가 됩니다.
3. 캘리브레이션 모드 (필수)
AI 자세 검사를 켜면 자동으로 캘리브레이션 흐름이 시작됩니다. 알림 5초 후
카메라가 한 컷을 찍고, 그 시점의 지표를 baseline 으로 저장합니다.
이후 매 검사 때는 baseline 대비 변화량(delta)으로 평가합니다.
절대 임계값이 아니라 본인 기준의 변화량을 보기 때문에 카메라 위치, 체형, 거리, 각도 등 환경 차이가 거의 자동으로 흡수됩니다. "내 평소보다 얼마나 나빠졌나"에 신호 대 잡음비가 훨씬 강합니다.
| 신호 | 약 임계값 (drop) | 강 임계값 (drop) |
|---|---|---|
| noseShoulderRatio drop | > 0.12 | > 0.20 |
| earShoulderRatio drop | > 0.08 | > 0.15 |
| shoulderTilt 증가 | > 0.05 | |
| headTilt 증가 | > 0.05 | |
| noseOffset 증가 | > 0.06 | |
noseShoulderRatio > 0.70— 이미 구부정하지 않음earShoulderRatio > 0.20— 이미 고개가 숙여지지 않음shoulderTilt ≤ 0.08— 양쪽 어깨 높이가 비슷함- 모든 키포인트 신뢰도 ≥ 0.3
4. 카메라 이동 자동 감지
캘리브레이션 기반 시스템의 약점은 카메라 위치/거리가 바뀌면 baseline 이 무효해진다는 점입니다. 거북이경보는 매 측정마다 baseline 의 어깨 픽셀 너비와 현재 어깨 너비를 비교해 30% 이상 차이가 나면 카메라가 옮겨진 것으로 간주합니다.
shifted = |nowShoulderWidth − baselineShoulderWidth| / baselineShoulderWidth > 0.30
이 조건이 걸리면 다음을 자동 수행합니다.
postureBaseline을 store 에서 삭제 — 이전 baseline 으로는 더 이상 판정하지 않음- AI 자세 감시를 일시 중지하고 트레이 메뉴의 토글도 OFF
- "카메라 위치가 바뀐 것 같아요 — 기준 자세를 다시 저장해주세요" 알림 1회
사용자는 AI 모드를 다시 켜면서 새 환경의 baseline 을 다시 저장합니다. 카메라 환경이 급변한 직후의 false positive 알림을 원천 차단하는 안전장치입니다.
5. 판정 트리 — 강/약/이중 신호
한 라벨만 부여되는 배타적 트리입니다. 강한 단독 신호 또는 약한 두 신호가 동시에 걸려야 거북목, 약한 단독 신호는 구부정한 자세로 라벨링됩니다.
거북목 / 구부정한 자세 라벨링과 독립적으로 다음 부가 라벨이 함께 붙을 수 있습니다.
- 어깨 기울어짐 — shoulderTilt 임계 초과
- 고개 기울어짐 — 좌우 귀 y 차이 임계 초과
- 고개 회전 — 좌우 귀 신뢰도 차이가 큼 (한쪽 귀가 안 보이는 정도로 돌아감)
- 한쪽으로 기울어짐 — 코 x 가 어깨 중심선에서 벗어남
6. 불확실(uncertain) 처리
코·어깨 키포인트 신뢰도가 0.3 미만이거나 두 어깨 사이가 10픽셀 미만으로 잡히면 uncertain 모드로 분류해 알림을 보내지 않습니다. 카메라 앞에 사람이 없거나 프레임 밖으로 어깨가 잘린 상황에서 잘못된 판정을 누적하는 걸 막기 위함입니다.
7. 트레이드오프와 한계
- 정면 카메라 1장만 사용 — 임상적 거북목 판정 지표인 CVA (Craniovertebral Angle, 측면 사진의 C7-귀 각도) 를 직접 계산할 수 없습니다. 정면에서 보이는 코·귀의 수직 위치 변화를 프록시로 씁니다.
- 화면 가까이서 몸을 굽히는 자세는 어깨가 프레임에서 잘려 uncertain 으로 빠질 수 있습니다. 이때는 카메라 위치 조정 + 캘리브레이션이 가장 효과적입니다.
- 로컬 추론 (CPU) — 모든 키포인트 추정은 사용자 기기에서만 수행되고 외부 서버로 이미지가 전송되지 않습니다. 정확도-속도 트레이드오프에서 MoveNet Lightning 을 택해 사용자 기기 부담을 최소화했습니다.
- 연속 N 회 임계 — 한 번의 false positive 로 사용자를 깨우지 않도록 연속 3 회 이상 나쁜 자세가 감지될 때만 알림을 보냅니다 (v0.7.6 부터 2 → 3 으로 보수화).
- baseline 강제 — v0.7.6 부터 baseline 없이 절대 임계값만으로 알림을 보내지 않습니다. AI 모드를 켜는 즉시 캘리브레이션 흐름이 자동 시작되며, 카메라가 옮겨졌다고 판단되면(baseline 어깨 너비 ±30% 초과) 자동으로 baseline 을 무효화하고 감시를 일시 중지합니다. "올바른 자세인데 알림이 뜨는" 케이스를 원천 차단하기 위함입니다.