리옹이

[GoingDeeper-CV Node.15] human pose estimation, DeepPose, CPM, Stacked Hourglass Network, SimpleBaseline, HRNet 본문

AIFFEL 15기/Node Study

[GoingDeeper-CV Node.15] human pose estimation, DeepPose, CPM, Stacked Hourglass Network, SimpleBaseline, HRNet

리옹이 2025. 12. 2. 17:14
반응형

학습 목표


  • 딥러닝 기반 Human Pose Estimation의 기본 개념$($Top-down/Bottom-up$)$을 익힌다.
  • DeepPose부터 HRNet까지 주요 모델의 발전 흐름과 구조적 특징을 이해한다.
  • SimpleBaseline 구현 코드를 통해 Pose Estimation 모델의 작동 원리를 파악한다.

1. body language, 몸으로 하는 대화

Human pose estimation는 크게 2D와 3D로 나뉜다고 한다.

이미지상에서 $($x,y$)$로 하게되면 2D이고, $($x,y,z$)$로 추정하면 3D로 된다.

 

Tesla는 3D를 수집하는 라이다나 레이더 없이, 오직 카메라로 2D를 받으면 이를 3D로 변환해서 인식하게 하는 걸로 안다.

아무튼 차원이 하나 늘어나게 되면 고려해야하는 점도 그만큼 늘어나고, 복잡해진다.


2. Pose 는 face landmark 랑 비슷해요

이전에 하던 Face Landmark와 비슷하다. 단순 얼굴에서 몸 전체로 옮긴거니깐

다만 얼굴은 어느정도는 고정된 상태로 되어있지만, 몸은 행동에 따라 다양해진다. 즉, 자유도가 높아진다.

 

그럼 어디부터 보면서 진행을 해야할까?

아래 그림처럼 2가지 방식이 있다. 이는 사실 여기에만 적용되는게 아니라, 모든 곳에서 적용되는 개념이다.

방식 1. Top-down 방식

절차:

  1. 사람을 먼저 검출 $($object detection$)$
  2. 각 사람의 keypoint를 개별적으로 추정

장점: 각 사람의 keypoint 정확도가 높음

단점: 사람이 많을수록 느림 $($모든 사람마다 detector 적용 필요$)$

방식 2. Bottom-up 방식

절차:

  1. detector 없이 모든 keypoint를 먼저 검출
  2. 검출된 keypoint를 클러스터링하여 각 사람으로 매칭

장점: 사람이 많아도 속도 저하가 적음

단점: 전체 영역에서 keypoint를 검출하므로 정확도가 Top-down보다 낮을 수 있음

 

그럼 아래는 Top-Down 방법들에 대해 알아볼 것이다.


3. human keypoint detection

Human pose estimation은 keypoint의 localization 문제를 푼다고 생각하면 된다.

 

위 gif를 보듯이, 자유도가 높은 행동으로 인식하기가 굉장히 어려워진다.

 

그럼 인체에 대해서 우선 알아봐야한다.

인체는 변형 가능 부분으로 나누어져 있고 각 부분끼리 연결성을 가지고 있다.

특히 관절부분으로 연결되어있죠

 

얼굴을 landmark하듯이, 인체도 마찬가지로 팔은 몸과 연결되어있으니 다리와 연결되어 있을 확률은 극히 드물거다

물론 일부 특정 사례나 특정 자세를 취하게 되면 그럴 수 있다.

특히 자세에 대해서는 어느정도 생각하고 진행해야할 거 같아보인다.

 

Articulated Human Detection with Flexible Mixtures-of-Parts

그래서 위 논문에서는 Deformable Part Models$($DPM$)$은 사람의 몸을 여러 part로 나누고, 각 부분 간의 complex joint relationship를 mixture model로 표현하여 다양한 자세를 모델링하려는 접근이라고 한다.

 

하지만 실제로는 다양한 포즈와 겹침 상황을 완벽히 처리하기 어려웠고, 계산 비용이 크며, keypoint 검출 정확도가 제한적이어서 기대만큼 높은 성능을 내지 못했다고 한다.

3-1. DeepPose

이번도 그렇고, 저번도 그렇지만 DL 이전에는 특정 한계들이 존재한다.

AlexNet 이후에 다양한 분야에 CNN이 적용되면서, Pose Estimation에도 사용하기 시작했다.

이때 나온게 딥러닝 기반의 Keypoint Localization이 제안되었다.

 

DeepPose: Human Pose Estimation via Deep Neural Networks

기존 기술로는 한계가 보이던, 동작의 다양성이나 invisible joint 문제들을 딥러닝으로 해결할 수 있다는 것을 증명한 논문이다.

 

초기 Pose Estimation 모델은 x,y 좌표를 직접 regression하는 문제로 인식하였다.

아래 이미지처럼, 사람 이미지를 Input으로 넣고 x,y 좌표를 출력하게끔 하였다. $($Loss function도 L2 Loss이었으니$)$

다만 성능은 기존 모델들에 비해서 높은 성능을 가진다고 말할 수 없었지만, SOTA를 나타내면서 딥러닝을 적용한 점이 크다고 한다.

3-2. Efficient Object Localization Using Convolutional Network

Efficient Object Localization Using Convolutional Networks

본 논문에서는 DeepPose의 성능을 해결하는 방식을 개선한 논문이다.

DeepPose에서는 단순히 x,y좌표를 예측하는 문제로 설정을 했었다.

 

아래 그림처럼, 단순히 x,y좌표를 예측하는 것이 아니라,Keypoint가 있을 확률 분포를 학습하게 하는 것으로 문제를 바꿔보았다.

사람마다 굵기, 길이 그리고 행동을 함에 따라, 정확히 그 위치에 예측하는 거는 힘들다고 본다.

단순히 우리가 직접 사진을 보고 팔꿈치를 정확히 한점으로 찍어보라고 해도 사람마다 어느정도 오차가 있을 것이다.

이에 대해서 본 논문은 그 확률분포를 Gaussian Distribution$($or Normal Dist$)$일 가능성이 높다고 보았다.

그래서 x,y 좌표를 중심으로 하는 heatmap으로 변환하여, 이 heatmap을 학습하게 한 것이다. 즉, keypoint가 존재할 확률을 학습하는 것이다.

 

그 결과 DeepPose에 비해서 굉장한 성능 차이를 보이게 되었다.

 

문제를 바꾼 것도 있지만, 모델 구조도 아래 처럼 변경하기도 했다.

위 이미지를 보면 두 부분으로 나뉘는데, 먼저 대강의 위치를 잡고 나중에 정밀하게 다듬는 상호보완적 관계로 구성된다.

우선 Coarse 모델이 전체 이미지를 훑어 관절의 대략적인 위치를 찾으면, Fine 모델은 그 위치만 잘라내어 미세한 오차를 수정한다. 즉, 1차로 Coarse가 위치를 제안하고, 2차로 Fine이 이를 보정하여 최종 결과를 낸다. $($이거 어디서 많이 보지 않았나?? 이전 노드인 Object Detection에서 2-Stage model과 굉장히 유사해보인다.$)$

 

또한 특이한 기능?으로는 Weight Sharing은 Coarse 모델 앞단의 병렬 처리 구간에 적용되었다. 두 경로가 동일한 Filter$($Weight$)$를 공유하는데, 이는 이미지 속 사람의 크기가 크든 작든 상관없이 신체 특징을 동일하게 인식할 수 있도록$($Scale Invariance$)$ 만들기 위해서라고 한다.

 

이를 통해 모델로도 어느정도 성능 향상을 한 것으로 보여진다.

3-3. Convolutional Pose Machines

이때까지만 해도 multi-stage 구조를 지속적으로 사용해 오긴 했다. 바로 위에서 말한 것처럼 말이다.

다만 굉장히 비효율적인 방법으로 사용해왔다.

 

Convolutional Pose Machines

본논문에서는 End-to-End 학습할 수 있는 모델을 제안한다고 한다. $($일명 CPM으로 불림$)$

 

아래 이미지를 보듯이

Stage 1 : Image feature 계산하는 역할

Stage 2 : Keypoint를 예측하는 역할

g1과 g2는 heatmap을 출력하게 하는데, 재사용이 가능한 부분은 Weight sharing 할 수 있도록 설계되어있다.

 

아래 이미지를 보면 Stage ≥ 2를 보면, stage 2 이후의 구조는 동일한 블록을 반복적으로 사용할 수 있도록 설계되어있다.

일반적으로는 총 3개의 Stage를 사용하는데, stage 1은 고정된 구조로 기본적인 feature를 만들고, stage 2부터는 같은 구조의 블록을 반복 적용해 추론을 점점 정교하게 만든다.

 

즉, 고정된 stage 1 + 반복 가능한 stage 2 블록들형태로 구성되어 필요에 따라 반복 횟수를 조절할 수 있는 방식이라고 한다.

 

또한 stage 2 부터는 입력이 heatmap$($image feature$)$이 되기 때문에 stage 단계를 거칠수록 keypoint가 refinement 되는 효과를 볼 수 있다.

 

추가로 CPM은 Receptive Field를 넓게 만드는 Multi Stage Refinement 방법으로 성능 향상에 크게 기여가 되었다.

 

다만 이렇게 보듯이, 학습과정도 복잡해지고 여러 단계에서 순차적으로 거치다 보니 가볍지도 않을 것이다.

서비스 측면에서 바라본다면 불편한 요소라고 설명이 되어져 있는데, 아직 이 말은 이해가 잘 안된다.

3-4. Stacked Hourglass Network

Stacked Hourglass Network

이번 논문의 모델은 아래처럼 구조가 되어있다.

모래시계느낌도 나고, 이전에 배운 U-Net과도 유사해보인다.

 

앞단에서 Image feature를 Encoding하고, Upsampling을 통해 feature map의 크기를 키우는 방향으로 Decoding되어간다.

feature map의 크기가 작아졌다가 커지는 것을 Hourglass라고 표현한다고 한다.

 

이전 모델과 다른 점을 보면, feature map upsampling을 하고, residual connection이 추가되었다.

pooling으로 image의 global feature를 찾고 upsampling으로 local feature를 고려하는 아이디어가 hourglass의 핵심 novelty라고 한다.

 

간단한 구조와 높은 성능으로, 아직도 많이 쓰인다고 한다.

3-5. SimpleBaseline

Simple Baselines for Human Pose Estimation and Tracking

이번 논문은 굉장히 간단한 모델이지만 높은 성능을 만드는 구조이다.

직전까지의 과정을 보면, 단순히 regression -> heatmap regression -> 모델 구조 변경 -> encoder-decoder 등으로

 

이번 모델도 Encoder-Decoder 구조이다.

다만 직전에서 본 Stacked Hourglass Network과는 다소 차이가 존재한다.

이 모델은 ResNet과 같은 깊은 BackBone Network를 Encoder로 사용하여 이미지 feature을 추출하고, 뒤이어 3개의 Deconvolution layer를 decoder로 연결해 resolution를 8배 키워 관절의 heatmap을 예측하는 아주 단순한 구조이다.

 

Stacked Hourglass Network와 비교했을 때 가장 큰 차이는 복잡성이다. Stacked Hourglass는 모래시계 형태의 모듈을 여러 번 쌓아 feature을 반복적으로 refine하고 skip connection을 사용하여 디테일을 보존하지만, SimpleBaseline은 이러한 반복 구조나 skip connection, Intermediate Supervision을 모두 제거했다.

 

즉, 복잡한 Network 설계 대신 강력한 Backbone과 학습 가능한 Upsampling layer$($Deconv$)$만으로도 충분히 높은 성능을 낼 수 있음을 보여주는 모델이다.

3-6. Deep High-Resolution Network $($HRNet$)$

Deep High-Resolution Representation Learning for Human Pose Estimation

본 논문은 Simplebaseline의 제1저자가 참여해 연구한 논문이라 같은 철학을 공유함과 동시에, 굉장히 높은 성능을 달성하는 알고리즘이라고 한다.

 

Object Detection에서 stage detector처럼, 여기서도 1-stage로 구조고 간결하고 사용하기 쉬우면서 성능은 높이는 연구를 진행한다.

아래 이미지는 지금까지 알아본 모델들의 구조이다.

$($a$)$ : Hourglass

$($b$)$ : CPN$($cascaded pyramid networks$)$

$($c$)$ : SimpleBaseline - transposed conv

$($d$)$ : SimpleBaseline - dilated conv

 

좀 더 들여다보면

공통점은 high resolution → low resolution 인 encoder 와 low → high 인 decoder 구조로 이루어진 점

차이점은 Encoder와 Decoder의 비율차이라던가, Skip connection이 존재유무 정도가 된다.

 

그래서 본 논문도 high → low → high 의 구조에서 high resolution 정보을 유지하기 위해서 어떻게 해야할지 고민했다고 한다.

 

Resolution을 줄이는 down-sampling 단계를 만든 뒤 다시 up-sampling을 통해 원본 크기로 복원하는 구조를 갖는다. 겉보기에는 복잡해 보일 수 있지만, 실제로는 1-stage로 동작하기 때문에 전체 흐름은 매우 단순하다. 앞서 본 CPM이나 Hourglass처럼 중간 단계에서 heatmap supervision을 따로 줄 필요도 없다는 점이 큰 특징이다.

 

또한 구현 측면에서도 기존 SimpleBaseline의 ResNet backbone을 HRNet으로만 교체해주면 되어 사용이 매우 편리하다. 학습 방식 역시 이전 방법과 동일하게 heatmap regression을 수행하고 MSE loss를 사용한다는 점에서 SimpleBaseline과 거의 유사하다.


4. 코드로 이해하는 pose estimation

SimpleBaseline의 간단한 구조만 알아보자.

아래 코드처럼 굉장히 간단하다.

class DeconvLayer(nn.Module):
    def __init__(self, num_deconv_layers):
        super(DeconvLayer, self).__init__()
        layers = []
        for _ in range(num_deconv_layers):
            layers.append(nn.ConvTranspose2d(256, 256, kernel_size=4, stride=2, padding=1))
            layers.append(nn.BatchNorm2d(256))
            layers.append(nn.ReLU())
        self.deconv_layers = nn.Sequential(*layers)

    def forward(self, x):
        return self.deconv_layers(x)
class PoseEstimationModel(nn.Module):
    def __init__(self):
        super(PoseEstimationModel, self).__init__()

        # ResNet-50 backbone 사용 (ImageNet pretrained)
        # 마지막 두 개의 layer(AvgPool, FC)를 제거하고 conv feature까지만 사용
        self.resnet = models.resnet50(pretrained=True)
        self.resnet = nn.Sequential(*list(self.resnet.children())[:-2])

        # ResNet 출력 채널 2048 → 256으로 축소
        # SimpleBaseline 논문에서 사용한 channel reduction 단계
        self.reduce_channels = nn.Conv2d(2048, 256, kernel_size=1)

        # 3개의 Deconv(ConvTranspose2d) layer로 해상도 upsampling
        # 각 layer: ConvTranspose2d → BatchNorm → ReLU
        # SimpleBaseline 구조와 동일
        self.upconv = DeconvLayer(3)

        # 최종 heatmap 생성 (256 → 17 keypoints)
        # keypoint 수에 해당하는 채널 수로 변환
        self.final_layer = nn.Conv2d(256, 17, kernel_size=1, padding=0)

    def forward(self, x):
        # Backbone feature 추출
        x = self.resnet(x)

        # Channel 축소 (2048 → 256)
        x = self.reduce_channels(x)

        # Deconvolution을 통해 해상도 복원
        x = self.upconv(x)

        # 최종 keypoint heatmap 생성
        x = self.final_layer(x)

        return x

반응형