상태: 초안 (2026-10-13). 논문 수치는 원문을 열어 조건과 함께 확인했습니다. 직접 해 본 것은 노트북 CPU 의 numpy 예제(
code/07_heatmap_decode.py, 10초 안팎)입니다. TT Score 에서 자세 모델을 실제로 쓴 결과는 TT Score 모델 해부 4편의 실측을 인용했습니다. 시리즈 「비전 모델 읽기 2」 7편. 다음 편: 사람 자세 2. HRNet 의 구조(여러 해상도를 끝까지 나란히 두기) 자체는 비전 모델 읽기 1이 다룹니다. 이 편은 「관절을 어떻게 학습시키고 꺼내나」에 집중합니다.
사람 자세 1: 히트맵으로 관절 찾기
목차
- 1. 직관: 좌표 대신 지도
- 2. 그림: 여러 사람을 다루는 두 길
- 3. OpenPose: 팔다리 방향을 지도로 그린다
- 4. 어떻게 학습시키나: 가우시안 정답과 OKS
- 5. 직접 해 보기: 히트맵에서 좌표 꺼내기
- 6. 논문 수치
- 7. 한계
- 8. 우리 프로젝트와의 연결
- 참고 문헌
1. 직관: 좌표 대신 지도
자세 추정(pose estimation)은 그림 속 사람의 관절(코, 어깨, 팔꿈치, 손목, 엉덩이, 무릎, 발목 …) 위치를 찾는 일입니다. COCO 데이터셋 기준으로 사람마다 17점입니다.
가장 곧은 방법은 망이 좌표 34개(17점 × x, y)를 바로 내게 하는 것입니다. 2014년의 DeepPose 가 그렇게 했습니다. 사람 상자로 정규화한 좌표를 제곱 오차로 회귀하고, 예측 둘레를 다시 잘라 한 번 더 고치는 단계를 두었습니다(논문).
같은 해 Tompson 외는 다른 길을 냈습니다. 관절마다 그림과 같은 모양의 지도(히트맵) 를 그리게 하고, 정답도 관절 자리에 작은 가우시안 언덕을 그린 지도로 만들어 화소마다 제곱 오차로 비교했습니다. 그리고 좌표를 바로 회귀하는 방식은 아주 정밀한 영역에서 약하다고 지적했습니다(논문). 이후 10년 가까이 자세 추정의 기본은 히트맵이었습니다.
히트맵이 강한 이유는 TT Score 모델 해부 2편에서 공 찾기에 대해 설명한 것과 같습니다. 화소마다 학습 신호가 생기고, 「여기는 아니다」가 자연스럽게 표현되며, 후보가 여럿이어도 됩니다. 사람 자세에는 하나가 더 있습니다. 합성곱은 그림의 위치 관계를 그대로 지키므로, 위치를 위치로 내는 출력이 망의 구조와 잘 맞습니다.
2. 그림: 여러 사람을 다루는 두 길
화면에 사람이 여럿이면 문제가 하나 더 생깁니다. 「이 손목은 누구의 손목인가.」
- 위에서 아래로(top-down): 사람 검출기로 사람 상자를 먼저 찾고, 상자마다 잘라 키워서 한 사람용 자세 모델에 넣습니다. 사람이 작게 찍혀도 키워서 보니 정확합니다. 대신 시간이 사람 수에 비례하고, 검출기가 놓친 사람은 끝입니다.
- 아래에서 위로(bottom-up): 그림 전체에서 모든 관절의 히트맵을 한 번에 그리고, 관절들을 사람별로 묶는 단서를 함께 냅니다. 망을 한 번만 돌리니 사람이 많아도 시간이 거의 같습니다. 대신 묶는 단계가 틀릴 수 있고, 작은 사람에 약합니다.
3. OpenPose: 팔다리 방향을 지도로 그린다
OpenPose(Cao 외, CVPR 2017)의 묶는 단서가 PAF(Part Affinity Field, 팔다리 친밀도 장) 입니다. 팔뚝 같은 팔다리마다 2채널 지도를 하나 그립니다. 팔뚝 위에 있는 화소에는 「팔꿈치에서 손목 방향」의 단위 벡터를, 그 밖의 화소에는 0을 넣습니다. 사람이 겹치면 벡터를 평균합니다.
시험 때는 이렇게 묶습니다.
- 관절 히트맵의 봉우리에서 팔꿈치 후보들과 손목 후보들을 뽑습니다.
- 팔꿈치 하나와 손목 하나를 잇는 선분마다, 선분 위를 따라 「PAF 벡터 · 선분 방향」을 적분해 점수를 냅니다. 같은 사람의 팔이면 PAF 가 선분과 같은 방향이라 점수가 큽니다.
- 전체를 한꺼번에 푸는 일은 너무 어려워서(NP-hard), 팔다리마다 따로 이분 짝 맞추기(헝가리안 알고리즘)로 풀고 이어 붙입니다(논문).
학습은 단순합니다. 관절 히트맵과 PAF 두 갈래를 여러 단계에 걸쳐 고쳐 가며 내고, 단계마다 정답 지도와의 제곱 오차를 겁니다(중간 감독). 라벨이 없는 사람이 있는 영역은 손실에서 가립니다. 라벨이 없다고 「관절이 없다」고 가르치면 안 되니까요.
속도 이야기도 흥미롭습니다. 사람 19명이 나오는 영상에서 노트북용 GPU 로 8.8fps 였고(368×654 입력), 사람 9명일 때 묶는 단계는 0.58ms, 망은 99.6ms 였습니다(논문). 망은 한 번이고 묶기는 아주 싸다는 bottom-up 의 장점이 숫자로 보입니다.
4. 어떻게 학습시키나: 가우시안 정답과 OKS
위에서 아래로의 표준형: SimpleBaseline 과 HRNet
SimpleBaseline(2018)은 이름처럼 단순합니다. ResNet 뒤에 디컨볼루션 층 3개를 붙여 입력 256×192 에서 64×48 히트맵(¼ 해상도)을 냅니다. HRNet(2019)은 해상도를 줄였다 키우는 대신 높은 해상도 갈래를 끝까지 유지하며 여러 해상도 갈래를 계속 섞습니다(구조는 시리즈 1에서).
학습은 둘 다 같습니다.
정답 히트맵 Hⱼ(p) = exp(−‖p − 관절 j 의 위치‖² / (2σ²)) (히트맵 칸 단위)
L = Σⱼ Σ_p (예측 Ĥⱼ(p) − Hⱼ(p))² (화소마다 제곱 오차)
HRNet 논문 본문은 σ 를 1화소로 적었습니다(공식 코드의 설정값은 확인하지 않았습니다). 증강은 회전, 크기 0.65–1.35배, 좌우 뒤집기(왼쪽·오른쪽 관절 라벨도 바꿔야 합니다), 상체·하체만 남기기였습니다(논문).
평가: OKS 와 AP
자세는 「맞았다/틀렸다」를 정하는 기준이 필요합니다. COCO 는 OKS(Object Keypoint Similarity) 를 씁니다.
OKS = 관절들에 대한 평균 exp( −d² / (2 · s² · (2σᵢ)²) )
d = 예측과 정답의 거리, s² = 사람의 면적, σᵢ = 관절마다 정한 상수
σᵢ 는 사람들이 그 관절을 얼마나 일정하게 찍는지에서 정한 값입니다. 손목(0.062)처럼 위치가 분명한 관절은 작고, 엉덩이(0.107)처럼 옷에 가려 애매한 관절은 큽니다. 같은 거리 오차라도 손목은 더 크게 깎입니다. AP 는 OKS 문턱을 0.50 부터 0.95 까지 바꿔 가며 잰 평균 정밀도를 다시 평균한 것입니다.
5. 직접 해 보기: 히트맵에서 좌표 꺼내기
히트맵을 ¼ 해상도로 그리면 칸 하나가 입력 4화소입니다. 가장 밝은 칸(argmax)을 고르면 그것만으로 최대 2화소 남짓의 오차가 생깁니다. 이를 줄이는 관행이 둘 있습니다.
- ¼ 칸 밀기: 가장 밝은 칸에서, 둘째로 밝은 이웃 쪽으로 0.25칸 옮깁니다. SimpleBaseline·HRNet 이 이렇게 합니다(논문).
- DARK(2020): 예측 히트맵을 가우시안으로 한 번 부드럽게 한 뒤, 로그 값을 최댓값 둘레에서 2차 테일러 전개해 봉우리를 소수점으로 찾습니다. 가우시안의 로그는 2차식이라 이 계산이 맞아떨어집니다. 정답을 그릴 때도 관절의 소수점 위치에 언덕을 그려야 한다(양자화하지 말 것)고 함께 제안했습니다.
code/07_heatmap_decode.py 로 셋을 비교했습니다. 참 위치를 소수점까지 무작위로 3,000번 두고, 그 자리에 가우시안을 그린 완벽한 예측과 잡음(표준편차 0.05)을 섞은 예측을 디코딩했습니다(입력 화소 단위의 평균 오차, code/07_heatmap_decode.out.txt).
| 히트맵(칸 = 입력 몇 화소) | σ | 잡음 | argmax | ¼ 칸 밀기 | DARK |
|---|---|---|---|---|---|
| 64×48(4화소) | 1 | 없음 | 1.52 | 0.76 | 0.00 |
| 64×48(4화소) | 1 | 0.05 | 1.58 | 0.85 | 0.20 |
| 64×48(4화소) | 2 | 0.05 | 1.97 | 1.29 | 0.21 |
| 32×24(8화소) | 1 | 0.05 | 3.16 | 1.67 | 0.41 |
| 32×24(8화소) | 2 | 0.05 | 4.03 | 2.64 | 0.41 |
같은 히트맵에서 디코딩만 바꿔도 오차가 1.5화소에서 0.2화소까지 달라집니다. 칸이 거칠수록(8화소) 차이가 더 커집니다. DARK 의 0.00 은 완벽한 가우시안이라 테일러 전개가 정확히 맞기 때문이고, 실제 망의 출력은 가우시안이 아니라서 이렇게 좋지는 않습니다.
이 오차가 점수에 얼마나 중요한지 OKS 로 환산해 봤습니다(사람 면적을 상자 256×192 의 절반으로 가정).
오차 2화소: 손목 0.995 · 엉덩이 0.998
오차 4화소: 손목 0.979 · 엉덩이 0.993
오차 8화소: 손목 0.919 · 엉덩이 0.972
OKS 0.95 문턱(AP 의 가장 엄한 칸)에서는 손목 오차 4화소와 8화소가 「맞음」과 「틀림」으로 갈립니다. 몇 화소의 디코딩 차이가 AP 몇 점으로 나타나는 이유입니다.
6. 논문 수치
모두 COCO 의 사람 17관절, OKS 기반 AP 입니다. 위에서 아래로 방식은 사람 상자의 품질에 따라 결과가 달라지므로 검출기의 AP 도 함께 적습니다.
| 모델 | AP | 조건(논문) |
|---|---|---|
| OpenPose(bottom-up) | test-dev 61.8, 저널판 64.2 | 2016년 COCO 대회. 중간 크기 사람에서 top-down 보다 약함 |
| Associative Embedding(bottom-up) | test-dev 65.5 | 관절마다 「누구 것인지」 를 나타내는 꼬리표 값을 함께 냄, 다중 크기 시험 |
| SimpleBaseline ResNet-152 | test-dev 73.7 | 384×288, 사람 검출기 AP 60.9 |
| HRNet-W32 / W48 | val 74.4 / 76.3 | 256×192 / 384×288. W32 를 ImageNet 사전학습 없이 학습하면 73.4 |
| HRNet-W48 | test-dev 75.5, 외부 데이터 추가 77.0 | 384×288 |
| HRNet-W32 + DARK | val 61.2 → 66.9 → 70.7 | 128×96 입력. 밀기 없음 → ¼ 칸 밀기 → DARK 전체 |
| HRNet-W32 + DARK | val 74.4 → 75.6 | 256×192 입력 |
| HigherHRNet-W48(bottom-up) | test-dev 70.5 | 640px, 다중 크기. 히트맵을 ½ 해상도까지 키워 작은 사람에 대응 |
두 가지가 보입니다.
- 입력이 작을수록 디코딩이 중요합니다. DARK 는 128×96 에서 +3.8점, 256×192 에서 +1.2점이었습니다. 위 예제에서 칸이 거칠수록 차이가 컸던 것과 같은 이야기입니다. 그리고 ¼ 칸 밀기 하나만으로 61.2 → 66.9, 5.7점이었습니다.
- top-down 이 대체로 더 정확합니다. 같은 시기 bottom-up(61.8–70.5)과 top-down(73.7–77.0) 사이에 차이가 큽니다. 대신 8편에서 보듯 속도와 사람 수의 사정이 다릅니다.
7. 한계
- 히트맵은 비싸다. 관절마다 2D 지도를 그리고, 정밀도는 지도의 해상도에 묶입니다. 해상도를 올리면 계산이 제곱으로 늘어납니다. 8편의 SimCC 가 이 문제를 겨냥합니다.
- 빠른 팔은 번진다. 탁구 스윙의 라켓 쪽 손목은 노출 시간 동안 번져서 「한 점」이 아닙니다. 공의 번짐을 다룬 BlurBall 같은 장치가 자세 쪽에는 흔하지 않습니다(9편과 비교해 보세요).
- 가림. 몸통에 가린 반대쪽 팔, 탁구대에 가린 다리는 자주 틀립니다. COCO 라벨은 가린 관절도 「있지만 안 보임」으로 표시하지만, 맞히기는 여전히 어렵습니다.
8. 우리 프로젝트와의 연결
- 실측: 지금 수준의 자세 신호는 판정을 바꾸는 데 못 썼다. TT Score 실험에서 사람 상자 대신 공을 가운데 둔 상자를 RTMPose-m 에 넣고 「공 곁에 손목이 있나」로 바운드 후보를 걸러 봤더니, 정답과 가짜를 거의 가르지 못했고 걸러 내면 랠리 승자 정확도가 93.1% 에서 92.0%(60px)·87.6%(90px)로 떨어졌습니다. 「확인 필요」 표시로만 쓰면 확인 없이 틀린 판정이 10건에서 6건으로 줄었지만 확인 필요가 17%p 늘었습니다(실제 12편 275랠리, TT Score 모델 해부 4편).
- 코칭에는 top-down. 연습 영상의 자세 코칭은 한 사람이 주인공이라 top-down 이 맞습니다. 서버 분석은 RTMDet + RTMPose(top-down), 브라우저는 MediaPipe 를 씁니다(같은 4편).
- 경기에는 두 명에서 네 명. 단식이면 두 명, 복식이면 네 명입니다. 사람 수가 적어 top-down 의 비용이 크지 않지만, 검출기를 따로 돌리는 부담이 있습니다. 8편의 RTMO 같은 한 단계 방식이 이 자리의 후보입니다.
티티 한마디. 히트맵에서 가장 밝은 칸만 고르면, 칸이 4화소일 때 평균 1.5화소쯤 틀려요. 「둘째로 밝은 쪽으로 4분의 1칸」이라는 작은 요령 하나로 반이 줄고, 그게 AP 5점이었어요. 작은 요령이 큰 차이를 만드는 곳이에요.
참고 문헌
- Toshev & Szegedy, DeepPose: Human Pose Estimation via Deep Neural Networks, CVPR 2014. https://arxiv.org/abs/1312.4659
- Tompson et al., Joint Training of a Convolutional Network and a Graphical Model for Human Pose Estimation, NeurIPS 2014. https://arxiv.org/abs/1406.2984
- Cao et al., Realtime Multi-Person 2D Pose Estimation using Part Affinity Fields(OpenPose), CVPR 2017. https://arxiv.org/abs/1611.08050 — 저널판 https://arxiv.org/abs/1812.08008
- Newell et al., Associative Embedding: End-to-End Learning for Joint Detection and Grouping, NeurIPS 2017. https://arxiv.org/abs/1611.05424
- Xiao, Wu, Wei, Simple Baselines for Human Pose Estimation and Tracking, ECCV 2018. https://arxiv.org/abs/1804.06208
- Sun et al., Deep High-Resolution Representation Learning for Human Pose Estimation(HRNet), CVPR 2019. https://arxiv.org/abs/1902.09212
- Zhang et al., Distribution-Aware Coordinate Representation for Human Pose Estimation(DARK), CVPR 2020. https://arxiv.org/abs/1910.06278
- Cheng et al., HigherHRNet: Scale-Aware Representation Learning for Bottom-Up Human Pose Estimation, CVPR 2020. https://arxiv.org/abs/1908.10357
- 예제 코드와 출력:
code/07_heatmap_decode.py,code/07_heatmap_decode.out.txt