거북이경보 거북이경보

🧠 자세 판정 로직

거북이경보가 웹캠 한 장으로 어떻게 거북목과 구부정한 자세를 가려내는지, 그리고 왜 사용자 캘리브레이션이 반드시 필요한지 정리한 문서입니다.

v0.7.6+ 정책 — "올바른 자세에서 알림이 뜨지 않는다"가 최우선. AI 자세 감시를 켜는 순간 본인의 바른 자세를 먼저 baseline 으로 저장해야 합니다. 이후 알림은 baseline 과의 변화량 기준으로만 발송되며, 카메라가 옮겨졌다고 판단되면 자동으로 baseline 을 무효화하고 재캘리브레이션을 요청합니다.
목차
  1. 1. 입력 — MoveNet 키포인트
  2. 2. 정규화된 지표 계산
  3. 3. 캘리브레이션 모드 (필수)
  4. 4. 카메라 이동 자동 감지
  5. 5. 판정 트리 — 강/약/이중 신호
  6. 6. 불확실(uncertain) 처리
  7. 7. 트레이드오프와 한계

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
baseline 으로 저장될 수 있는 자세는 다음 조건을 만족해야 합니다. 나쁜 자세를 baseline 으로 잡아 거북목이 영영 안 잡히는 사고를 방지합니다. 검증을 통과하지 못하면 트레이 메뉴에서 다시 시도할 수 있습니다.

4. 카메라 이동 자동 감지

캘리브레이션 기반 시스템의 약점은 카메라 위치/거리가 바뀌면 baseline 이 무효해진다는 점입니다. 거북이경보는 매 측정마다 baseline 의 어깨 픽셀 너비현재 어깨 너비를 비교해 30% 이상 차이가 나면 카메라가 옮겨진 것으로 간주합니다.

shifted = |nowShoulderWidth − baselineShoulderWidth| / baselineShoulderWidth > 0.30

이 조건이 걸리면 다음을 자동 수행합니다.

사용자는 AI 모드를 다시 켜면서 새 환경의 baseline 을 다시 저장합니다. 카메라 환경이 급변한 직후의 false positive 알림을 원천 차단하는 안전장치입니다.

5. 판정 트리 — 강/약/이중 신호

한 라벨만 부여되는 배타적 트리입니다. 강한 단독 신호 또는 약한 두 신호가 동시에 걸려야 거북목, 약한 단독 신호는 구부정한 자세로 라벨링됩니다.

1 강 단독 신호 코 또는 귀가 강 임계값을 넘으면 → 거북목
2 약 이중 신호 코 약 임계 AND 귀 약 임계 → 거북목
3 약 단독 신호 코 또는 귀 단독 약 임계 → 구부정한 자세
4 그 외 정상

거북목 / 구부정한 자세 라벨링과 독립적으로 다음 부가 라벨이 함께 붙을 수 있습니다.

6. 불확실(uncertain) 처리

코·어깨 키포인트 신뢰도가 0.3 미만이거나 두 어깨 사이가 10픽셀 미만으로 잡히면 uncertain 모드로 분류해 알림을 보내지 않습니다. 카메라 앞에 사람이 없거나 프레임 밖으로 어깨가 잘린 상황에서 잘못된 판정을 누적하는 걸 막기 위함입니다.

주의 — 거북목이 심한 사람일수록 모니터 쪽으로 몸을 굽히게 되어 어깨가 프레임에서 잘려 uncertain 비중이 높아질 수 있습니다. 이 경우 카메라 위치를 조금 더 멀리 두거나 노트북 받침대를 높이면 검출률이 올라갑니다.

7. 트레이드오프와 한계

📖 README 전체 보기 · 소스 코드 (posture-detector.js)