상태: 초안 (2026-10-13). 논문 수치는 원문을 열어 조건과 함께 확인했습니다. 직접 해 본 것은 노트북 CPU 의 numpy 예제(
code/08_simcc.py, 1초 미만)입니다. TT Score 가 RTMPose·MediaPipe 를 실제로 쓴 결과는 TT Score 모델 해부 4편을 인용했고, ViTPose·RTMO 는 돌려 보지 않았습니다(미실측). 시리즈 「비전 모델 읽기 2」 8편. 앞 편: 사람 자세 1. RTMPose 의 디코딩 코드와 우리 쓰임새는 TT Score 4편이 자세히 다뤘으니, 이 편은 「왜 그렇게 학습시키나」에 집중합니다.
사람 자세 2: 실시간과 정확도 사이
목차
- 1. 직관: 한쪽은 키우고, 한쪽은 덜어 낸다
- 2. ViTPose: 디코더가 거의 필요 없다
- 3. SimCC: 좌표를 두 개의 분류로
- 4. RTMPose: 학습 요령을 하나씩 쌓는다
- 5. RTMO: 사람 검출기를 없앤다
- 6. 한계
- 7. 우리 프로젝트와의 연결
- 참고 문헌
1. 직관: 한쪽은 키우고, 한쪽은 덜어 낸다
7편 끝의 표를 보면 2019–2020년의 강자들은 HRNet 같은 정교한 망에 정교한 디코딩을 얹은 구조였습니다. 그 뒤로 길이 둘로 갈렸습니다.
- 정확도 쪽: 그림 분야에서 커진 범용 트랜스포머(ViT)를 그대로 가져오고, 사전학습(3편의 MAE)에 기대면 자세 전용 장치가 거의 필요 없다(ViTPose).
- 속도 쪽: 2D 히트맵 자체를 버리고 더 싼 출력을 쓰며, 학습 요령을 쌓아 정확도를 되찾는다(SimCC → RTMPose). 사람 검출기까지 없앤다(RTMO).
2. ViTPose: 디코더가 거의 필요 없다
ViTPose(2022)는 평범한 ViT 를 몸통으로 쓰고(그림의 1/16 해상도 특징), 그 위에 아주 단순한 디코더를 얹습니다. 「4배로 키우기(쌍선형 보간) → ReLU → 3×3 합성곱」 하나로 히트맵을 냅니다. 학습은 7편과 같은 히트맵 제곱 오차입니다.
이 단순한 디코더가 통하는지 몸통별로 비교한 결과가 흥미롭습니다(COCO val). ResNet 몸통에서는 단순 디코더로 바꾸면 약 18점이 떨어졌는데(ResNet-50 71.8 → 53.1), ViT 몸통에서는 0.3점 이내였습니다(ViT-B 75.8 → 75.5). 주의 층이 이미 넓은 맥락을 섞어 주니, 디코더가 할 일이 거의 남지 않는다는 뜻입니다(논문).
정확도는 몸통 크기를 따라 오릅니다. 256×192 입력에서 ViTPose-B 75.8, -L 78.3, -H 79.1 이고, 10억 파라미터급 몸통으로 COCO test-dev 80.9(단일 모델, 576×432, 외부 데이터 추가)까지 갔습니다(논문). 속도도 생각만큼 나쁘지 않습니다. ViTPose-B 가 초당 944장, HRNet-W32 가 916장이었습니다(데이터센터급 GPU, 배치 64, 논문).
사전학습 데이터에 대한 실험도 있습니다. MAE 사전학습을 ImageNet 대신 COCO·AI Challenger 의 사람 자르기 50만 장으로 해도 같은 75.8 이었고, COCO 만으로 하면 74.5 였습니다(논문). 자세 데이터만으로도 꽤 된다는 뜻이고, 2·3편의 「라벨 없는 우리 영상」 이야기와 이어집니다.
ViTPose 는 4편의 증류도 씁니다. 큰 모델(L)을 얼린 채 학습 가능한 「지식 토큰」 하나만 학습시키고, 그 토큰을 작은 모델(B)에 끼워 넣었습니다. 토큰만으로 +0.2, 출력 증류와 함께 +0.8(75.8 → 76.6)이었습니다(논문).
3. SimCC: 좌표를 두 개의 분류로
생각
히트맵의 문제는 두 가지였습니다(7편). 관절마다 2D 지도를 그려야 해서 비싸고, 정밀도가 지도 해상도에 묶입니다. SimCC(2022)는 질문을 바꿉니다. 「x 좌표는 몇 번 칸인가」와 「y 좌표는 몇 번 칸인가」를 각각 분류 문제로 풉니다.
- 화소 하나를 k 칸으로 쪼갭니다(나누기 배율). 입력 너비 192, k=2 면 x 칸이 384개, 칸 하나가 ½ 화소입니다.
- 관절 특징을 펼쳐 선형 층 하나로 x 로짓 384개, y 로짓 512개를 냅니다.
- 디코딩은 argmax 두 번입니다.
지도를 키우지 않고도 화소보다 고운 해상도를 얻습니다.
라벨과 손실
정답 칸 하나만 1인 원-핫 라벨을 쓰면, 한 칸 옆도 열 칸 옆도 똑같이 「틀림」입니다. SimCC 는 정답 둘레로 확률을 나눠 주는 라벨 다듬기(label smoothing) 를 쓰고, 예측 분포와 정답 분포 사이의 KL 발산을 손실로 씁니다. 다듬는 방식 비교(ResNet-50, 384×288)에서 다듬지 않음 71.3, 고르게 나눔 73.0, 가우시안 73.4, 라플라스 73.0 이었습니다(논문). 가까운 칸일수록 덜 틀린 것으로 보는 가우시안이 가장 좋았습니다.
직접 해 보기
code/08_simcc.py 의 출력입니다(code/08_simcc.out.txt).
[1] x 정답 101.3px → 칸 202.6/384, 정답 분포의 최대 칸 203, 폭(σ) 6.0칸
[2] τ=1.0: 예측 분포 최댓값 0.012, KL(정답‖예측) 1.420, 디코딩 105.5px
[2] τ=0.1: 예측 분포 최댓값 0.171, KL(정답‖예측) 0.499, 디코딩 105.5px
[2] 는 온도의 역할을 보여 줍니다. 망이 내는 로짓은 보통 값의 폭이 좁습니다. 그대로(τ=1) 소프트맥스를 걸면 분포가 납작해서 정답 분포와 멀고(KL 1.42), τ=0.1 로 나누면 뾰족해져 가까워집니다(KL 0.50). RTMPose 는 이 온도를 0.1 로 두어 COCO val 이 72.7 이 됐습니다(아래 표의 단계). 그리고 온도는 argmax 를 바꾸지 않습니다(두 경우 모두 105.5px). 온도는 학습의 기울기를 바꾸는 손잡이이지 디코딩 결과를 바꾸는 손잡이가 아닙니다. 이 예시의 4px 오차는 일부러 넣은 잡음 때문입니다.
양자화 오차를 x 한 축에서 비교했습니다(완벽한 예측, 무작위 위치 2만 개).
| 출력 | 칸 크기 | 평균 오차 |
|---|---|---|
| SimCC k=1 | 1 화소 | 0.249px |
| SimCC k=2 | ½ 화소 | 0.125px |
| SimCC k=3 | ⅓ 화소 | 0.084px |
| 2D 히트맵 ¼ 해상도, argmax | 4 화소 | 1.012px |
로짓에 잡음을 섞으면(1천 번) k=1·2·3 의 평균 오차가 2.97·2.54·2.37px 로, 칸을 잘게 쪼개는 이득이 잡음에 묻혀 작아집니다. SimCC 논문도 k 를 1–4 로 시험해 2–3 이 가장 좋다고 했습니다. 너무 잘게 쪼개면 칸마다 학습 신호가 얇아집니다.
출력 크기(관절 하나당 값의 개수)는 이렇습니다.
2D 히트맵 ¼ 해상도 48×64 = 3,072 → 17관절 52,224
SimCC k=2: 384 + 512 = 896 → 17관절 15,232
낮은 해상도에서 특히 강하다
SimCC 의 이득은 입력이 작을 때 큽니다(논문, COCO val, HRNet-W48).
| 입력 | 히트맵 + ¼ 칸 밀기 | SimCC |
|---|---|---|
| 64×64 | 48.5 | 59.7 |
| 128×128 | 68.9 | 72.0 |
| 256×192 | 75.1 | 75.9 |
7편의 결론과 같습니다. 칸이 거칠수록 디코딩의 양자화가 크게 문제가 되고, SimCC 는 그 양자화를 출력 구조에서 없앱니다.
4. RTMPose: 학습 요령을 하나씩 쌓는다
RTMPose(2023)는 위에서 아래로(top-down) 방식입니다. 사람 검출기(RTMDet)가 상자를 찾고, CSPNeXt 몸통 → 큰 합성곱(7×7) → 관절별 특징 → 주의 층(GAU) → SimCC 두 분류기로 관절을 찾습니다. 이 논문의 가치는 구조보다 학습 요령을 하나씩 더하며 그 효과를 다 적어 둔 표에 있습니다(COCO val, RTMPose-m).
| 단계 | AP |
|---|---|
| 출발점 | 69.7 |
| 몸통을 히트맵(UDP)으로 먼저 학습 | 70.3 |
| + 가중치 지수 이동 평균(EMA) | 70.4 |
| + 학습률 스케줄(평평하다가 코사인) | 70.7 |
| + 두 단계 증강(강하게 180에폭 → 약하게 30에폭) 등 | … |
| + 주의 층(GAU) | 71.9 |
| + 온도 τ=0.1 | 72.7 |
| + x·y 따로 σ | 72.8 |
| + 7×7 합성곱 | 73.3 |
| + 420에폭 | 73.7 |
| + 데이터 추가(AI Challenger) | 75.3 |
라벨은 「가우시안 거리로 만든 분포에 소프트맥스」를 씌운 꼴이고, 예측과 라벨 양쪽에 온도를 씁니다(논문). 강한 증강은 크기 0.6–1.4배, 회전 80도, 일부 지우기(Cutout)를 늘 거는 정도였습니다. 학습 데이터 조건이 다른 판이 여러 개라(COCO 만 / AI Challenger 추가 / 뒤집기 시험 포함), 75.8 같은 숫자를 인용할 때는 어느 판인지 맞춰야 합니다.
속도(논문, RTMPose-m): 데스크톱 CPU(ONNX Runtime) 11.06ms, 데스크톱급 GPU(TensorRT) 2.29ms, 휴대폰 SoC 에서 35fps 이상. 작은 판 RTMPose-s 는 휴대폰 SoC 에서 13.89ms(70fps 이상)에 72.2 AP 입니다. 사람 검출기를 매 프레임 돌리지 않고 몇 프레임마다 돌리며, 그 사이에는 앞 프레임의 자세로 상자를 만듭니다.
5. RTMO: 사람 검출기를 없앤다
top-down 의 약점은 사람 수에 비례하는 시간과, 검출기를 따로 돌리는 부담입니다. RTMO(CVPR 2024)는 물체 검출기(YOLO 계열)처럼 그림을 격자로 나눠 칸마다 「여기 사람이 있나, 있다면 상자와 관절은?」을 한 번에 냅니다.
관절 좌표는 SimCC 처럼 1D 분류로 내되, 칸을 그림 전체가 아니라 그 칸이 예측한 사람 상자(1.25배로 넓힘) 안에만 펼칩니다(동적 좌표 분류기, DCC). 사람이 작든 크든 상자 안을 x 192칸·y 256칸으로 나누니 해상도가 사람 크기에 맞춰집니다.
손실도 바꿨습니다. 정답 분포와의 KL 대신, 「예측 위치 둘레의 가우시안 분포에서 정답이 나올 우도」를 최대화하되 분포의 폭(불확실도)도 망이 함께 배우게 했습니다(최대 우도, MLE). 같은 RTMO-s 에서 KL 64.4, 좌표 회귀(OKS 손실) 65.6, MLE 67.6 이었습니다(COCO val, 논문). 5편의 SEA-RAFT 처럼, 원래 애매한 경우를 덜 벌하는 손실이 이긴 예입니다.
| 항목 | 값(논문) |
|---|---|
| COCO val(COCO 만 학습) | RTMO-s 67.6 · -m 71.2 · -l 72.5 |
| 초록의 대표 수치 | RTMO-l val 74.8, 데이터센터급 GPU 한 장에서 141fps(학습 데이터·실행기 조건은 초록에 없음) |
| COCO test-dev | s 66.9 · m 70.1 · l 71.6(외부 데이터 추가 시 67.7 · 71.5 · 73.3) |
| CrowdPose(붐비는 장면) | RTMO-l 73.2(CrowdPose 만 학습) |
| top-down(RTMPose) 대비 | 사람 4명 안팎부터 RTMO 가 더 빠름(ONNX Runtime). 1명에서 10명 넘게 늘어도 지연 증가 약 0.1ms |
6. 한계
- 정확도와 속도는 여전히 맞바꾼다. ViTPose-G 80.9 와 RTMPose-s 72.2 사이에는 9점 가까운 차이가 있습니다. 휴대폰에서 돌려야 한다면 아래쪽에서 골라야 합니다.
- COCO 는 탁구가 아니다. COCO 는 일상 사진입니다. 탁구 스윙의 빠른 팔, 번진 손목, 탁구대에 가린 다리는 덜 대표됩니다. 논문 AP 가 탁구 영상의 정확도를 보장하지 않습니다(미실측).
- 2D 관절만으로는 라켓 면을 모른다. 회전을 걸었는지, 라켓 각도가 어땠는지는 손목 점 하나로 알기 어렵습니다. 라켓 다섯 점을 따로 찾는 RacketVision 같은 시도가 그래서 나왔습니다(TT Score 모델 해부 4편).
7. 우리 프로젝트와의 연결
- 실측: 서버에서는 RTMPose, 브라우저에서는 MediaPipe. TT Score 코칭의 서버 분석은 RTMDet-m + RTMPose-m(ONNX Runtime)으로, 서버 GPU 를 나눠 쓸 때 사람 찾기 50–98ms, 관절 찾기 6–10ms/프레임이었습니다. 브라우저 쪽 MediaPipe 는 노트북의 헤드리스 브라우저에서 12–15ms/프레임이었고 실제 휴대폰은 미실측입니다(TT Score 4편).
- 실측: CPU 에서는 무겁다. 점수 엔진 실험에서 RTMPose-m 을 OpenCV DNN 으로 CPU 에서 돌렸을 때 상자 하나(540×720)에 74ms 였습니다(다른 작업과 CPU 를 나눠 쓴 중앙값, TT Score 4편). 60fps 실시간에 매 프레임 넣을 수 없는 이유입니다.
- RTMO 를 볼 이유. 그 실험에서 사람 검출기 RTMDet 의 ONNX 파일 안 NMS 연산을 OpenCV DNN 이 읽지 못해 사람 상자 대신 공 둘레 상자를 써야 했습니다. 검출기가 따로 없는 RTMO 는 이 걸림돌을 피하고, 복식 네 명도 한 번에 다룹니다. 아직 받아 보거나 재지 않았습니다(미실측).
- 증류로 키운 판. 전신 133점을 내는 DWPose 는 RTMPose 를 4편의 2단계 증류로 키운 모델입니다. 손가락 관절이 필요해지면 후보입니다.
티티 한마디. 좌표를 「몇 번째 칸?」이라는 분류 문제로 바꾸니, 지도를 그리던 3,072칸이 896칸으로 줄었어요. 그리고 온도는 정답을 바꾸는 게 아니라 배우는 방식을 바꾸는 손잡이라는 것도 직접 계산해서 확인했어요.
참고 문헌
- Xu et al., ViTPose: Simple Vision Transformer Baselines for Human Pose Estimation, NeurIPS 2022. https://arxiv.org/abs/2204.12484
- Li et al., SimCC: a Simple Coordinate Classification Perspective for Human Pose Estimation, ECCV 2022. https://arxiv.org/abs/2107.03332
- Jiang et al., RTMPose: Real-Time Multi-Person Pose Estimation based on MMPose, arXiv 2023(기술 보고서). https://arxiv.org/abs/2303.07399
- Lu et al., RTMO: Towards High-Performance One-Stage Real-Time Multi-Person Pose Estimation, CVPR 2024. https://arxiv.org/abs/2312.07526
- Yang et al., Effective Whole-body Pose Estimation with Two-stages Distillation(DWPose), ICCV 2023 Workshop. https://arxiv.org/abs/2307.15880
- 예제 코드와 출력:
code/08_simcc.py,code/08_simcc.out.txt