개요

SMPL이라는 모델링 자체를 이해하고 나면 이어서 3D Mesh를 어떻게 표현했는지에 대한 시작인 SMPLify에 대한 얕은 이해를 해보는 것이 좋다고 생각하여 해당 논문을 읽었다. 논문의 내용이 결코 적은 것은 아니지만 논문의 내용은 얕게 다루면서 이를 실제로 구현한 코드를 다뤄본다.

SMPLify

단일의 RGB 이미지로부터 3차원의 사람의 Pose를 추정하는 문제에서 방법론을 제시한다. 우선적으로 DeepCut 이라는 관절인식 모델에서부터 2D Joint를 추정한다. 여기서 추정한 2D Joint로 부터 3D Pose 와 Shape를 추정하게 된다. 그 결과로 SMPL 모델을 활용하여 3D Mesh를 생성할 수 있게된다. 이런 시스템을 SMPLify로 부른다.

큰 틀에서는 SMPLify는 2차원의 관절 인식을 통해 SMPL 모델을 활용한 3D Mesh를 생성하는 것으로 볼 수 있지만, 세부 모듈적인 관점에서 보자면 2D 관절 정보를 입력으로 사용하여 3D Pose와 Shape 파라미터(SMPL의 입력)을 출력으로 내는 모델링이라고 볼 수 있겠다.

그 과정에서 SMPL의 결과로 몸의 부위가 몸을 관통해버리는 interpenetration 문제를 최대한 방지하기 위한 노력 및 방법론도 보인다.

Notation

수학적인 정의들을 정리해보자면 다음과 같다.

  • $J_{est}$: 단일 입력 이미지에서부터 DeepCut 모델을 활용해 추정한 2차원 신체의 관절.
  • $i$: 각 관절에 인덱스를 붙인 것
  • $w_i$: DeepCut CNN 모델이 예측한 $i$ 관절의 confidence value. 본인은 스코어 정도로 이해했다.
  • $M(\beta, \theta, \gamma)$: 3가지 파라미터로 구성된 3D Mesh를 생성하는 body model
    • $\beta$: Shape parameters. 3D 스캔의 결과로부터 PCA 된 저차원 파라미터.
    • $\theta$: Pose parameters. 23개의 파라미터는 연결된 관절 간의 상대적 회전을 axis-angle 표현로 나타낸다.
    • $\gamma$: Camera translation parameter
    • $\mathcal{M}$: 해당 모델의 결과로 나오는 6890개의 버텍스. 즉 출력.
  • $J(\beta)$: shape parameters로 부터 3차원 관절 위치를 예측하는 함수
  • $R_\theta(J(\beta)_i)$: 관절 i에 대해 Pose parameter $\theta$를 활용하여 유도한 global rigid transformation.
  • $K$: SMPL의 관절을 이미지에 프로젝션 매핑하기 위해 사용하는 원근 카메라 모델의 파라미터

Gender-neutral model

기존 SMPL에서는 남성(male)과 여성(female) 모델 2가지를 모델링하였다. SMPLify에서는 추가적으로 2000명의 남성 신체와 2000명의 여성 신체를 학습한 gender neutral(중성 혹은 성별 중립적) 모델을 제공한다. 논문도 그렇고 코드도 그렇고 이미지에 포함된 사람의 성별을 알고 있다면 특정 성별 모델을 사용하는 것이 더 좋을 것이라고 한다.

Body Capsules

앞에서 간단히 이야기한 interpenetration 문제를 해결하기 위한 캡슐 형태의 입체 도형을 사용한 방법론을 이야기한다. 하나의 캡슐을 수학적으로 정의하기 위해서 축의 길이(axis-length)와 반지름(radius)를 사용한다. 20개의 캡슐로 몸의 형태를 맞춰서 배치한다. 여기서 말하는 20개의 표면은 손가락과 발가락을 제외한다. SMPL의 결과로 생성 되야 할 3D 버텍스의 표면과 캡슐의 표면과의 거리를 최소화 하도록 학습하면 반지름과 축 길이를 통해 최적화 가능하다. 그래서 Shape Parameters로 부터 캡슐의 반지름과 축 길이를 출력으로 하는 선형 리그레서를 학습한다.

Object Function

2D Joint로 부터 3D Pose와 Shape을 매핑하는 과정 상에서의 수학적인 이해는 우리가 최소화 해야하는 5개의 에러항으로 표현가능하다. 5개의 항은 논문의 표현 상 1개의 joint-based data term, 3개의 pose priors, 1개의 shape prior로 구성된다. 우리의 목적함수 $E(\beta,\theta)$를 아래와 같이 표현한다.

\[E(\beta,\theta)=E_J(\beta,\theta;K,J_{est}) +\lambda_\theta E_\theta(\theta) +\lambda_a E_a(\theta) +\lambda_{sp}E_{sp}(\theta;\beta) +\lambda_\beta E_\beta(\beta)\]

위의 식에서 $K$ 는 원근 카메라 모델 파라미터이며 각 람다들은 목적함수의 비중을 조절하기 위한 가중치 값(scalar)들이다.

joint-based data term

$E_J(\beta,\theta;K,J_{est})$ 는 DeepCut 모델을 통해 예측된 2D Joint와 SMPL의 3차원 Joint를 2차원으로 projected 한 위치와의 거리 차를 통해 최소화 하도록 최적화한다. 식은 다음과 같다.

\[E_J(\beta,\theta;K,J_{est})=\sum_{\text{joint }{i}}w_i\rho(\Pi_K(R_\theta(J(\beta)_i))-J_{est,i})\]

위의 식에서 $\Pi_K$ 는 카메라 파라미터 $K$ 를 가지고 3차원 좌표를 2차원으로 프로젝션 해주는 함수이다. 관절 $i$ 에 대해 Deepcut의 관절 i로 표현된 $J_{est,i}$ 와의 거리 차이를 구하고 이에 2가지 조치를 더 한다. 우선 $\rho$ 로 표현된 값과 각 관절 i의 스코어라고 할 수 있는 $w_i$ 를 곱한 값을 Sum 해줄 것이고 이를 최소화 하도록 학습할 것이다. 여기서 $\rho$ 는 추정관절(DeepCut)과 프로젝션 관절의 거리의 차이에 노이즈를 주기 위한 함수를 한번 더 적용하는데 이 함수 $\rho$ 를 Geman-McClure penalty function으로 부른다.

Pose Priors

Eq(3)

3가지 Pose Priors에 대한 항 중 식(3)은 비자연스러운 팔꿈치와 무릎에 패널티를 주는 식이다.

\[E_a(\theta) = \sum_{i}\exp(\theta_i)\]

지수함수 식의 합으로 표현 된 항이다. 음수일 때는 0에 가까우니 상관없고 양수 값이 높을 수록 패널티가 높다. $\theta_i$ 는 논문 상의 설명에 의하면 관절이 굽혀지지 않아 있을 때 0에 가깝다고 한다.

Eq(4), Eq(5)

확률적으로 가능한 자세를 만드는 것이 확률적으로 거의 불가능한 자세를 만드는 것보다 낫기에 특정 Pose에 대한 Prior를 사용한다. 그래서 Pose parameter에 대해 가우시안 분포의 혼합을 구현한다.

\[E_\theta(\theta)\equiv -\log\sum_i(g_j\mathcal{N}(\theta;\mu_{\theta,j},\Sigma_{\theta,j})) \approx-\log(\max_j(cg_j\mathcal{N}(\theta;\mu_{\theta,j},\Sigma_{\theta,j})))\\ =\min_j(-\log(cg_j\mathcal{N}(\theta;\mu_{\theta,j},\Sigma_{\theta,j})))\]

$g_j$ 는 $N=8$인 가우시안의 혼합 모델 가중치다. $c$ 는 이를 최적화 하기 위한 양의 상수인데, $E_\theta$ 는 미분가능하지 않아서 이를 근사한 식으로 최적화 한다고만 이해하면 될 듯 하다. 수식이 처음엔 직관적으로 이해가 가지않았는데 로그 값이 커져서 음의 로그 값 자체가 음수 방향으로 작아지도록 최적화 되는 것으로 일차적으로 이해하고, 여기서 $j$는 로그함수에 들어가는 입력인 실행 상수와 혼합모델 가중치와 확률 분포 값의 곱의 값을 최대화 시켜주는 값으로 이해했다.

Eq(6)

위에서 이론적으로 언급한 캡슐과 표면의 오차를 최소화하는 항이다. 원래는 캡슐과 캡슐 간의 교차부피를 계산해서 최소화 해야하지만 이를 단순화 하기위해 캡슐형태 대신의 구의 형태로 계산한다.

\[E_{sp}(\theta;\beta)=\sum_{i}\sum_{j\in I(i)} \exp(\frac{\Vert C_i(\theta,\beta)-C_j(\theta,\beta) \Vert^2}{\sigma_i^2(\beta)+\sigma_j^2(\beta)})\]

$i$ 와 $i$ 가 아닌 $j$ 구에 대해서(즉 특정 하나의 구와 나머지 구들의 관계) 계산하며, $C(\theta, \beta)$ 는 캡슐의 축 길이 상에 있는 구의 중심이고, 구의 반지름은 캡슐의 반지름 $r(\beta)$ 와 동일하게 한다. $\sigma(\beta)$ 는 $r(\beta)/3$ 으로 고려한다. 식 자체가 이해되지 않더라도 해당 식은 구로 표현된 몸의 교차 부피에 관련된 식이며 이를 지수함수로 감싸고 각 구 끼리의 교차부피의 합으로 표현하는 오차 항이다.

Shape Prior

Shape Prior는 식(7)로 정의된다.

\[E_\beta(\beta) = \beta^T \Sigma_\beta^{-1}\beta\]

여기서 $\Sigma_\beta^{-1}$ 는 PCA를 통해 뽑아낸 $\beta$ 의 제곱값을 가지는 대각형렬이다.

최적화

앞서 모델을 정의할 때 언급된 camera translation 이라 하는 $\gamma$ 는 모른다고 가정하는데, 카메라의 초점거리(focal length)는 대략적으로 안다는 전제를 한다. 이러면 camera translation을 어떤 값으로 초기화 가능하며 SMPL의 형태의 몸통 길이와 2차원 관절을 통해 거리(Depth)를 추정할 수 있다.

논문에선 방법론에 대해서 이러한 가정이 항상 참이 아니기에 정확도를 높이기 위해 $\beta$ 를 고정한 상태에서 관절의 오차 항인 $E_J$ 를 최소화한다고 한다. 또한 카메라 초점거리에 대해서도 최적화 불가의 문제로 오차 항에서 제외한다고 한다. 이 포스팅을 하는 2024년 현재 최종적으로 해당 데모코드를 돌려보기 위해서 학습 순서를 완벽히 이해하진 않아도 코드를 돌려볼 수 있으니 얕게라도 이해하고 넘어가자.

정면이 아닌 이미지 내의 사람이 측면으로 보일 때, 몸의 방향을 판단하는 것이 애매한 경우가 있다. 우선적으로 DeepCut이 추정한 2D의 어깨관절 거리가 특정 임계값 이하일 때 측면으로 간주하고, 우선 한 방향으로 $E_J$ 값을 구한 뒤 180도 회전 시켜서 이를 다시 구해 값이 낮은 것을 선택한다. 이는 추후 코드를 돌려 볼 때 비슷하게 보이는 점이 있다. Flip 하여 데이터 오차항을 구해보고 더 낮은 쪽을 선택하여 SMPL로 생성해버린다.

일반적으로 1장의 이미지에 대해서 SMPL 모델까지 뽑아내는 데에 논문에서는 1분 미만이라고 표현되어 있는데 본인의 사무실 PC에서는 1분 전후로 걸리는 듯 했다.

Code

우선적으로 SMPLify의 코드는 공식 페이지에서 제공한다. 회원가입 및 메일인증이 필요하며 해당 페이지에선 SMPLify의 코드와 LSP 데이터 셋의 DeepCut의 관절인식 결과물을 같이 다운로드 할 수 있다. Readme.md를 보며 데모를 돌려보려 하다보면 해당 파일이 필요하다. 또한 LSP 데이터셋이 필요한데 Readme.md에서 걸어놓은 링크는 페이지가 죽었는지 연결이 되지 않는다. 그래서 이를 포함해놓은 리포지토리를 찾았다. 이 리포지토리를 fork하여 본인이 부가 작업을 하였다.

또한 SMPLify 코드 뿐만 아니라 SMPL의 파이썬 코드도 필요하다. 이 또한 SMPL 공식 페이지에서 받을 수 있으며 SMPLify와 마찬가지로 회원가입 및 메일인증 후 Download 탭에서 받을 수 있다. 아래 언급한 리포지토리에 소스코드를 첨부해 두었으니 굳이 가서 받지 않아도 된다.

기존 SMPLify 코드는 python2 기준으로 작성되어있다. 이 포스팅을 작성하는 2024년 기준으로는 python2는 공식 지원버전이 아니기 때문에 python3로 코드가 작동할 수 있도록 하는 코드의 수정작업을 일부 해서 본인의 fork한 리포지토리에 커밋해두었다.

  • fork 리포지토리: https://github.com/dongle94/smplify_python3

아마 해당 리포지토리의 Readme.md를 보며 쭉 따라하면 문제는 없을 것이라 생각되는데 여기서의 핵심 수정 내용은 2가지다.

Installation opendr

chumpyopendr이라는 라이브러리를 사용하여 3D Mesh의 시각화를 구현하는데 이 라이브러리 역시 python2 때부터 사용된 라이브러리기 때문에 기존 리포지토리대로 설치하다보면 잘 되지 않는다. opendr 패키지가 소스코드로 부터 휠파일을 빌드하여 설치하는 방식이기 때문인데 빌드 시 에러가 날 것이다.

핵심은 opendr 패키지를 pip download 명령어로 소스코드를 받아 우리가 직접 python setup.py install 명령어를 통해 설치 해 줄 것인데, 설치하기 전에 소스코드가 빌드 시 에러가 나지 않도록 아래와 같은 과정으로 수정 후 설치해준다. 아래에서는 opendr 0.78 버전(2024년 기준 최신)을 사용하였다.

$ pip download --no-deps opendr==0.78
$ tar zxvf opendr-0.78.tar.gz
$ cd opendr-0.78
$ sed -i 's/from _constants import/from opendr.contexts._constants import/g' ./opendr/contexts/ctx_base.pyx 
$ python setup.py install

From Python2 to Python3

python2의 프로젝트 코드를 python3에서 돌리기 위해 변경한 것은 2가지 정도로 요약해볼 수 있을 것 같다.

하나는 cPickle에 관한 부분이다. python3에서 해당 패키지를 사용하려면 깃허브 이슈나 스택 오버플로를 찾아보니 처음엔 아래와 같이 접근할 수 있다고 했다.

import _pickle as cPickle

즉, _pickle 이라는 패키지가 python3 에서의 cPickle 역할을 해준다고하는데 해당 방식으로는 LSP 데이터셋 결과의 관절 정보를 읽어오는데 다음과 같이 에러가 발생하였다. UnicodeDecodeError: 'ascii' codec can't decode byte 0x81 in position 1224: ordinal not in range(128)

이를 깃허브 이슈에서 힌트를 얻어 지금의 pickle 패키지로 해결하였다. 그래서 기존에 아래와 같이 작성되어 있는 부분을 변경한다.

  • 기존
    dd = pickle.load(open(fname_or_dict))
    
  • 변경
    with open(fname_or_dict, 'rb') as f:
      u = pickle._Unpickler(f)
      u.encoding = 'latin1'
      dd = u.load()
    

    해당 코드 변경 후 확장자가 .pkl인 SMPL 모델이나 LSP 데이터셋의 관절인식 결과를 읽어오는 데에는 문제가 없다.

그 외에는 각종 소스 코드 상의 import 구문이 상대경로로 되어 있지 않아 이를 수정해 주었다.

Run Demo

DeepCut 모델은 matlab으로 작성되어있는 것으로 알고있다. 그래서 해당 프로젝트 데모 코드(fit_3d.py) 내에서 2차원 관절인식 단계부터 새로운 영상에 대해 돌리는 코드 부분은 보이지 않는다. LSP Dataset을 가지고 아래와 같이 돌린다.

# at smplify_python3
$ PYTHONPATH=./ python code/fit_3d.py ./ --out_dir=./out --viz

base_dir을 현재경로로 잡아주고, --out_dir 파라미터를 SMPLify의 결과물이 저장될 경로를 하나 지정해준다. --viz 옵션을 명시하지 않는다면 plt 시각화를 하지 않을 수 있다. 그래도 결과 이미지는 저장된다.

또한 argument parser 내용을 살펴보면 interpenetration을 방지하기 위한 캡슐 리그레서의 사용 여부를 정할 수 있는 --no_interpenetration 파라미터가 있고, SMPL 생성 모델의 성별을 정할 수 있는 --gender_neutral 옵션이 있다. 해당 옵션을 명시하면 중성 모델을 사용하여 3D Mesh를 생성하고 명시하지 않으면 LSP 결과 데이터 셋 중 성별 분류 결과 lsp_gender.csv의 파일 내용을 보고 특정 성별 모델을 사용하여 SMPL Mesh를 생성한다.

그 외에도 shape parameters 갯수에 대한 --n_betas, 카메라 초점 거리에 대한 --flength, 측면 뷰 여부를 판단하기 위한 임계값 --side_view_thsh 옵션들이 있다. 필요 시 조정해볼 수 있지만 기본값으로 사용해도 문제는 없을 듯 하다.

코드를 돌려보면 데이터셋에 대한 SMPLify의 결과로 생성된 렌더링 된 이미지를 볼 수 있다.

Federica Bogo, Angjoo Kanazawa, Christoph Lassner, Peter Gehler, Javier Romero, Michael J. Black Keep it SMPL: Automatic Estimation of 3D Human Pose and Shape from a Single Image
https://github.com/githubcrj/simplify
https://github.com/dongle94/smplify_python3
https://github.com/facebookresearch/DensePose/issues/142

Leave a comment