개요

SMPL 이라는 모델링의 등장 이후 이를 단일 RGB 이미지로부터 최적화 기법으로 파라미터를 추출하는 SMPLify가 등장하였고, 그 사이(2017년)에 UP이라는 다양한 키포인트 디텍터 기반의 접근(Unite the People: Closing the Loop Between 3D and 2D Human Representations)도 있었다. 그래도 2023년 혹은 그 이후의 흐름까지 중에 순차적인 과정으로써 CVPR 2018에 등재된 해당 논문(End-to-end Recovery of Human Shape and Pose)을 읽어야겠다고 판단하였다. 해당 논문은 GAN이라 불렀던 적대적 생성 네트워크에서 구별자 혹은 식별자, 판별자(discriminator)로 불리는 개념을 도입하여 성능을 끌어올린 모델이다.

HMR

기존 SMPLify 방식은 2차원 관절의 위치를 추정한 뒤 이를 최적화 기반으로 3차원 모델의 파라미터를 추정하는 방식이었으나 HMR은 이미지 픽셀로부터 3차원 모델(SMPL)의 파라미터의 매핑을 바로 학습하도록 설계하였다. 그러나 이러한 설계가 가지는 문제는 학습 데이터의 부족이 다. 일반적으로 이미지-2차원 관절 데이터셋 들이 많고 이런 이미지들 대해 3차원 모델의 데이터는 거의 없고, 있더라도 모션캡쳐(MoCap) 환경같이 제한적인 환경에서 수집되기 마련이다. 또한 2차원 관절과 3차원 관절을 매핑함에도 모호함이 존재한다.

이를 해결하기 위해 GAN에서 착안한 적대적 모델의 식별자(Discriminator)를 도입하여 생성된 3차원 모델 파라미터가 비정상적인 사람의 형태나 관절이 아닌지 검수하는 방식을 도입하여 학습을 촉진한다. 그 외에도 Introduction의 내용에는 2-stage 모델이 아니라는 점과 End-to-End 방식이라는 점을 어필한다.

그래서 논문 내 그림2를 통해 모델의 구조를 보자면 다음과 같은 순서를 가진다.

  • 이미지는 이미지 인코더를 통해 피쳐맵(feature map)으로 추출된다.
  • 3차원 모델 리그레서(Regressor): 피쳐맵이 반복적인 잠재적인 3차원 표현을 추론한다.
  • 리그레서로 부터 생성된 카메라 파라미터($s$, $R$, $T$)와 shape 파라미터($\beta$) Pose 파라미터($\theta$)는 한 명의 사람 모델 $M(\theta, \beta)$ 로 표현되고 이에 대해 Discriminator $D$ 가 진짜 사람의 형상인지 판단한다.
  • 최소화 하고자 하는 로스의 큰 개념은 관절의 재투영(reprojection)의 $L_2$ 노말값이다.

Two-Stage && Direct estimation

3차원 자세 추정 문제를 다룰 떄 2-stage 방식은 일반적으로 이미지로 부터 2차원 관절 좌표를 추정하고, 이를 다시 입력으로 사용하여 3차원 자세를 추정한다. 이는 어떠한 도메인에도 유연하게 작동할 수 있다는 이점이 있지만 2차원 자세 입력에 대해 의존적으로 될 수록 풍부한 이미지 정보를 낭비하게 된다는 문제가 있다.

직접 추정 방식은 HumanEva, Human3.6M 같은 데이터 셋을 활용하여 3차원 표현에 의존한다. 그러나 이런 데이터는 이미지와 3차원 스캔데이터 혹은 캡쳐데이터가 있지만 데이터셋의 제작 환경이 모션캡쳐 환경에서 수집되었기에 도메인도 제한적이고 현실에서 관측되는 이미지와 다른 부분이 있다.

Model

해당 HMR 모델에 대해 좀 더 디테일 한 구조를 수식과 함께 보고자한다. 논문이 서술하는 순서와 같다. 앞의 모델설명에서 간단히 이야기했다시피 단일 RGB 이미지 $I$ 는 이미지 인코더를 통해 특징벡터를 추출하고 이 특징벡터는 반복적인 회귀 모듈로 들어가 카메라, 자세, 형태 파라미터를 추론한다. 예측된 파라미터는 적대적 식별자로 보내져서 진짜 사람의 파라미터인지 판별된다. 이런 모델의 로스함수는 다음의 식1과 같다.

\[L=\lambda(L_{\text{reproj}}+\mathcal{1}L_{\text{3D}}) + L_{\text{adv}}\]

데이터는 기본적으로 2차원 관절에 대한 라벨값이 있다고 전제하고, 3차원 관절이 있거나 없는 경우로 나누어 함수를 설계한다. 이미지에 대해서 3차원 자세정보에 대한 라벨값이 존재하면 $L_{\text{3D}}$ 는 1을 곱하고 없으면 0을 곱하는 $\mathcal{1}$ 상수를 도입하여 목표함수를 차별적으로 가져간다. 또한 $\lambda$ 를 통해 각 요소의 가중치를 다르게 제어한다.

3D Body Representation

해당 논문이 추후 나오는 많은 논문에 많이 이용되는 여러 이유 중 하나라고 생각되는 점은 Weak Perspective Camera 모델을 처음 도입했다는 점이다. 소실점(Vanishing Point)를 무한에 가깝게 멀리두는 카메라 모델을 도입한다. 즉 깊이(Depth)에 따른 크기 변화를 거의 없다고 상정한다. 상은 Image Plane에 맺히는 데, 이는 초점거리(focal length)에만 영향을 받는다.

기존의 SMPL의 정의를 그대로 도입한다.

  • $\beta$: 신체의 형태를 10개의 PCA 요소로 표현.
  • $\theta$: 루트 관절(pelvis)를 (0,0,0)으로 표시하고 $3 \times 23$ 을 같이 붙여 24개로 표현.
  • $M(\theta, \beta)$: 6890개의 정점으로 표현되는 3차원 Mesh
  • $X(\theta, \beta)$: 재투영 에러에 사용되는 3차원 관절 키포인트. 3차원 Mesh를 입력으로하는 선형 회귀로 부터 얻는다.

추가 파라미터 및 사용하는 정의는 다음과 같다.

  • $R$: 카메라 파라미터 중 axis-angle 표현의 $3 \times 3$ 의 전역 회전 변환
  • $t$: 카메라 파라미터 중 2차원의 이동 행렬
  • $s$: 카메라 파라미터 중 스케일 값
  • $\phi$: 이미지 인코더로 부터 추출된 특징 벡터

위의 한 사람을 표현하기 위한 파라미터들의 집합을 85개 차원의 벡터로 표현할 수 있고 $\Theta = { \theta, \beta, R, t, s } $ 로 표현한다. 그래서 $\Theta$ 가 주어지면 3차원 관절 $X$의 투영(projection)은 다음의 식2와 같다. 여기서 $\Pi$ 는 소실점이 없는 orthographic projection 함수라고 한다.

\[\mathbf{\hat{x}}= s\Pi(RX(\theta, \beta))+ t\]

머리 속으로 그림이 잘 안그려지거나 수학적인 부분이 이해가 잘 가지 않더라도, HMR 모델이 이미지로부터 85개의 파라미터를 추론하면 이를 통해 3차원 관절의 orthographic 한 투영을 얻는다고 알고 넘어가면 될 듯 하다.

Iterative 3D Regression with Feedback

반복적 회귀 모듈은 이미지 인코더 다음의 단계다. 회귀 모델은 결국 85개 차원의 출력을 주며 이를 최적화 하기 위한 재투영 오차함수를 식 3과 같이 설계한다. 여기서 $v_i$ 는 Ground Truth에서 해당 관절이 보이면 1, 아니면 0의 값을 취한다.

\[L_{\text{reproj}} = \Sigma_i\Vert v_i \mathbf{x}_i - \mathbf{\hat{x}_i} \Vert_1\]

이러한 손실함수를 기반으로 회귀 모듈을 3번 반복한다는데, 인코딩 된 $\phi$ 를 입력으로 주고, 85개의 $\Theta$ 가 출력으로 나오면 이 반복이 말이 안 된다. 헷갈릴 수 있으니 잘 이해해야한다. 입력은 이미지 특징 벡터 $\phi$ 와 현재의 파라미터 $\Theta_t$ 다. 이를 입력으로 넣으면 회귀 모듈은 잔차(residual) $\Delta\Theta_t$ 를 출력으로 뱉는다. 해당 논문은 $\Theta_{t+1} = \Theta_t + \Delta\Theta_t$ 로 정의하여 이를 3번 반복했다고 한다. 맨 처음 시작하는 $\Theta_0$ 는 무엇인가 의문을 느낄 수 있는데 파라미터들의 평균 $\bar{\Theta}$ 라고 한다.

3차원 관절에 대한 라벨 값이 있다면 추가 손실함수 $L_\text{3D}$를 적용할 수 있고, 해당 손실함수는 명확하게 식4, 식5, 식6으로 표현된다. 식5와 식6에는 $\text{3D}$ 라는 아랫첨자가 붙어야 맞는 것 같긴한데 그냥 독자들이 이해했다고 하고 넘어간 듯 하다.

\[L_{\text{3D}}= L_{\text{3D joints}} + L_{\text{3D smpl}}\] \[L_{\text{joints}} = \Vert(\mathbf{X_i} − \hat{\mathbf{X_i}})\Vert^2_2\] \[L_{\text{smpl}} = \Vert[\beta_i, \theta_i] − [\hat{\beta}_i, \hat{\theta}_i]\Vert^2_2\]

실제로 이러한 손실 함수를 매 스텝에 사용한 것은 아니다. 매 반복마다 해당 손실 함수를 적용하는 것은 회귀의 값이 overshoot 되고, 손실이 로컬 미니멈에 빠지게 한다. 그래서 최종 추정하는 $\Theta_T$ 에만 적용했다고 한다.

Factorized Adversarial Prior

손실함수에 대해서 마지막으로 볼 것은 Adversarial Prior의 Loss다. 기존의 재투영 손실만으로는 2차원 관절로부터 3차원 몸체를 생성하도록 유도하는데, 이것만으로는 불가능한 인간의 굉이한 몸체도 가능하도록 loss의 최소화가 가능해진다. 이를 막기 위해 식별자(discriminator)를 도입하고 손실함수에 추가한다.

식별자는 입력으로 10개(10-D로 표현됨)의 $\beta$ 와 9차원의 K개의 관절(9K-D로 표현됨)의 입력을 가져서 작은 네트워크로도 학습이 가능하다고 한다. 처음에 왜 9차원의 관절인지 몰랐는데 추후 깃허브의 코드를 보니 관절 정보를 로드리게스 변환을 통한 상대적인 회전 행렬로 표현한 것으로 보인다. 식별자는 그 구조내에서 일반적인 가중치를 공유하는데 각 관절의 회전행렬을 판단하는 끝단을 각각 따로 학습한다. 코드를 보면 이해가 쉽긴한데 공유되는 가중치와 24개로 나뉘는 부분이 따로 있는 모습이다.

총 $K+2$ 개(25개)의 식별자를 학습하며 이는 23개의 관절에 대한 식별자, Shape 파라미터에 대한 식별자, 23개의 식별자로 들어가기 전에 32개의 채널로 표현되는 컨볼루션 레이어의 특징 벡터를 입력으로하는 포즈 전체에 대한 식별자로 구성된다. 말이 어렵다면 자세 전체에 대한 것 1개, 자세 각각에 대한 것 23개, 형태에 대한 것 1개로 이해하면 된다. 각 식별자 $D_i$ 는 [0, 1] 의 값을 가진다. 이 때의 손실함수는 식 7과 같다. $E$ 는 이미지 인코더와 3차원 모듈을 포함한 인코딩 표현이다. 이 인코딩 표현은 코드를 보면 이해가 쉽지만 방금 위에서 말한 ‘32개의 채널로 표현되는 포즈 파라미터’‘shape 파라미터’로 볼 수도 있겠다.

\[\min L_{\text{adv}}(E) = \sum_i\mathbb{E}_{Θ∼p_E}[(D_i(E(I)) − 1)^2]\]

Implemetation Details

실제로 HMR을 구현하기 위한 필요 요소 및 오퍼레이션, 레이어를 설명한다.

2차원 데이터 셋은 LSP, LSP-Extended, MPII, MS-COCO 데이터셋을 이용. 3차원 데이터 셋은 Human3.6MMPI-INF-3DHP 데이터 셋을 이용한다. SMPL은 관절을 23개의 키포인트로 표현하므로 2차원 데이터 셋들의 관절과 일치하지 않는다. 앞선 연구(SMPLify, Up)을 따라 14개의 관절을 채용하고(이는 DeepCut에서 왔다.) 이를 Human3.6M의 3차원 Mesh 데이터를 회귀하여 얻는다. 그리고 MS COCO의 얼굴의 5개 키포인트를 더 채용한다. 이 새로운 5개의 키포인트는 3차원 Mesh 표현의 버텍스 ID와 연계하기 용이해서 통합하기 쉽다.

  • 이미지 인코더는 Resnet50 모델을 사용
    • 2048개의 특징 벡터를 출력으로 생성.
    • 입력 이미지는 (N * H * W * 3)의 shape.
  • 3차원 Regressor는 앞서 설명한 입력을 받아 최종적으로 85개 차원의 출력을 생성
    • 총 3개의 FCL 레이어 사이에 Dropout 레이어가 각 1개씩, 총 2개 끼어있는 구조
    • 앞 2개의 FCL 레이어는 1024개의 출력, 마지막은 85
  • Shape Discriminator 는 10개 - 5개 - 1개의 채널로 이루어진 총 3개의 FCL 레이어 구조
    • 마지막 3번째가 [0, 1]의 출력 생성
  • Pose Discriminator는 전체 자세용과 각 관절 별로 구분(그런데 코드와 논문이 다르다)
    • 논문대로 라면 관절 하나당 9개로 표현된 9K 피쳐가 32개의 출력을 각각 가진 2개의 FCL 레이어로 가야한다.
    • 하지만 코드에는 로드리게스 변환을 통해 3*3 으로 표현된 행렬이 32개의 출력을 가진 컨볼루션 레이어로 갔다.
    • 아무튼, 이렇게 나온 32개의 출력은 23개의 각 관절 식별자로 각 [0, 1] 의 출력을 만든다.
    • 그리고 (23*32)의 형태를 한번에 사용하여 중간에 1024개의 채널을 가진 FCL 통과후 [0, 1]로 만들어주는 출력 FCL으로 아키텍쳐를 구성.

그 외에 나머지 하이퍼 파라미터는 다음과 같다

  • 마지막 출력 레이어를 제외한 모든 레이어는 ReLU 활성화 함수 채용
  • 인코더는 $1 \times 10^{-5}$의 학습률, 식별자는 $1 \times 10^{-4}$ 의 학습률
  • Adam 옵티마이저 사용
  • 데이터 셋에 대해 55에폭 학습
  • Titan 1080ti GPU에서 5일 소요

No Discriminator + No 3D

원래 위의 설명만으로 충분할 것 같은데, 이는 좀 인상적이라 인지하고 가면 좋을 것 같다. 이미지와 2차원 관절좌표는 기본이었고, 3차원 관절좌표와의 매핑을 가지고 가는 데이터와 가지고 가지 않은 페어링 데이터가 없는 설정을 HMR unpaired 로 지칭하고 학습해보았을 때 그림 5가 보여주는 것은 adversarial loss 와 2차원-3차원 매핑을 둘 다 안준(3차원 Loss를 적용하지 않은) 모델의 출력물이다. 논문에서도 monster라는 표현을 할 정도로 인상적이었나 싶다. 그만큼 논문 저자의 모델 설계가 유효했다는 것이기도 하다.

Code

앞선 SMPLify 포스팅과 같이 기존 리포지토리를 fork 하여 기록을 해야할 상황이 있었다. 기존 리포지토리 주소는 포스팅 최하단 레퍼런스에 넣어둔다. 본인이 바라본 기존 리포지토리의 문제는 다음과 같다.

  • Requirement에 Python 2.7 을 적어놓았다는 점.
    • 이 글을 포스팅하는 현재 Python 2는 상당히 유물이다(본인도 프로그래밍을 할 때 Python 2버전을 접하긴 했다.).
    • 그래서 Python 3 버전으로 돌아가는 작업을 하고 싶었다.
  • 또한 Tensorflow 1.x 버전을 사용했다
    • 2.x 버전을 사용해서 cuda 11.x 버전과 호환 시킨 뒤 tf.compat.v1 모듈을 사용하여 2버전 속에 있는 1버전 코드들을 활용할 계획이었다. 그러나 원작자의 코드가 tf.contrib 모듈을 사용함으로써 앞선 계획처럼은 할 수 없었고(2버전에선 해당 모듈이 아예 없다) tensorflow 1버전을 CPU 버전으로 받아서 데모만 돌려보기로 한다.

굳이 fork를 해서 코드를 수정하는 것은 python 3 버전으로 돌리기 위한 일부 구형 코드들에 대한 수정과 남들이 따라 돌려보기 쉽도록 requirement를 현 시점에 맞게 구체적인 명시를 했다는 것에 의의를 두고 싶다. fork 한 리포지토리는 다음과 같다.

conflict absl-py

absltensorflow와 연계되있는 config 모듈이라고 알고 있다. src/config.py 내의 flag 명시 중 log_dir 이라는 항목은 absl이 기본으로 가지고 있는 모듈이라 중복 정의가 안되는 것 같아 해당 키워드에 대한 부분을 logs_dir로 전부 변경하였다.

Above TF 1.12

본인은 Tensorflow 1.15.5의 CPU 버전으로 작업하였고, 이때 나는 문제는 다음 링크와 같았다.

ypeError: default_name type (<type 'list'>) is not a string type. You likely meant to pass this into the values kwarg.

TF 1.12 버전 이상에서 난다고 토론이 이루어진 것 같다. tf.name_scope()의 두번째 인자에 values= 라는 키워드를 명시하여 해결하였다.

Replace python 2

src/util/renderer.py 내의 코드 중에 xrange를 쓰는 부분들을 range로 고쳐주었다. 이런 부분들은 기존 python2의 코드들이니까. 특이한 점은 이땐 opencv의 circle이나 rectangle 함수에 color 아규먼트에 np.ndarray로 컬러값을 주었었나보다. 타입은 np.int64 였지만 그 부분이 에러가 나서 기본 파이썬 리스트로 변경해주어 정상작동을 확인하였다.

Run Demo

Readme.md에 명시된 대로 coco 샘플 이미지를 돌려보았다.

$ python demo.py --img_path ./data/coco1.png

결과는 다음과 같았다.

작동 시간을 체크하기 위해 time.time() 모듈을 이용하였다. 원래는 이렇게 측정하면 안된다. CUDA 연산은 CPU와 싱크가 안맞을 수 있기 때문에 CUDA의 연산을 대기하여 CPU와 싱크를 맞춘 뒤 시간을 재야하지만, 대략적인 것만 보기위해 간단히 앞뒤로 시간차이만 구하였다.

Tensorflow 1.15 CPU 버전에서도 이미지 한장의 연산이 0.182초 정도 소요되는 것을 확인하였다. 여기서 ‘연산’은 HMR 모델의 1번의 predict 기준이다.

2018년으로 부터 몇 년이 지난 2024년 현재에도 성능을 끌어올리기위해 실시간 처리와 성능의 트레이드 오프를 고민하는 많은 연구들이 있지만 이미 2018년에 실시간 처리가 가능했다는 점과 메트릭 기준 많은 성능을 향상 시켰다는 점, 적대적 판별 방식과 새로운 카메라 파라미터의 도입까지 상당히 프로그레시브 한 연구로 생각한다.

Angjoo Kanazawa, Michael J. Black, David W. Jacobs, Jitendra Malik End-to-end Recovery of Human Shape and Pose
https://github.com/akanazawa/hmr/tree/master
https://github.com/dongle94/hmr_python3
hmr - isssu

Leave a comment