AnyMotionを利用して動きの違いを可視化してみた

## 概要
AIを用いた姿勢推定による動作解析APIサービスであるAnyMotionを用いて、人物の動きの違いを可視化してみました。

## AnyMotionとは

AnyMotionは、AIを用いた姿勢推定による動作解析APIサービスです。
人物の関節などの座標を推定して、様々な身体動作を可視化/定量化してくれます。

## 今回やること
* 動画に映る人物の関節などの座標を描画して、動きの違いを可視化する

### アクセストークンの取得

AnyMotionポータルで発行された、`Client ID` と`Client Secret`を用いて、AnyMotionにアクセスするためのトークンを取得します。

```python
from urllib.parse import urljoin

import requests

CLIENT_ID = "<your_client_id>"
CLIENT_SECRET = "<your_client_secret>"
BASE_URL = "https://api.customer.jp/"
AUTH_URL = urljoin(BASE_URL, "v1/oauth/accesstokens")


def authenticate() -> str:
    headers = {"Content-Type": "application/json"}
    body = {
        "grantType": "client_credentials",
        "clientId": CLIENT_ID,
        "clientSecret": CLIENT_SECRET,
    }
    res = requests.post(AUTH_URL, json=body, headers=headers)
    token = res.json()["accessToken"]
    result = f"Bearer {token}"
    return result


token = authenticate()
```

## 姿勢推定

取得したトークンを用いて人物の関節などの座標データを取得します。
今回用いる2つの動画をアップロードします。アップロード完了後、2つの動画それぞれに対して、座標データを取得します。
処理完了後、動画の各フレームごとに[検出した17点](https://docs.anymotion.jp/keypoint_extraction.html#%E6%A4%9C%E5%87%BA%E3%81%99%E3%82%8B%E9%83%A8%E4%BD%8D)の座標データを取得できます。

### 実装

```python
import base64
import hashlib
import time
from pathlib import Path

ANYMOTION_URL = urljoin(BASE_URL, "anymotion/v1/")
CURRENT_DIR = Path(__file__).parent
MEDIA_DIR = CURRENT_DIR / "media"
WAIT_TIME = 1


def calc_content_md5(path: Path) -> str:
    alg = hashlib.md5()
    with open(path, "rb") as f:
        alg.update(f.read())
    result = base64.b64encode(alg.digest()).decode()
    return result


def create_movie(token: str, content_md5: str) -> tuple[int, str]:
    headers = {"Content-Type": "application/json", "Authorization": token}
    body = {"contentMd5": content_md5}
    url = urljoin(ANYMOTION_URL, "movies/")
    res = requests.post(url, json=body, headers=headers)
    movie_id, upload_url = res.json()["id"], res.json()["uploadUrl"]
    return movie_id, upload_url


def upload(path: Path, upload_url: str, content_md5: str) -> None:
    with open(path, "rb") as f:
        headers = {"Content-MD5": content_md5}
        requests.put(upload_url, data=f, headers=headers)


def create_keypoint(token: str, movie_id: int) -> int:
    headers = {"Content-Type": "application/json", "Authorization": token}
    body = {
        "movieId": movie_id,
    }
    url = urljoin(ANYMOTION_URL, "keypoints/")
    res = requests.post(url, json=body, headers=headers)
    keypoint_id = res.json()["id"]
    return keypoint_id


def wait_for(token: str, resource: str, resource_id: int) -> None:
    headers = {"Authorization": token}
    url = urljoin(ANYMOTION_URL, f"{resource}/{resource_id}/")
    while True:
        res = requests.get(url, headers=headers)
        status = res.json()["execStatus"]
        if status == "SUCCESS":
            break

        time.sleep(WAIT_TIME)
        
# もう1つの動画に対しても同様の処理を実施
student_path = MEDIA_DIR / "student.mp4"
content_md5 = calc_content_md5(student_path)
movie_id, upload_url = create_movie(token, content_md5)
upload(student_path, upload_url, content_md5)
source_keypoint_id = create_keypoint(token, movie_id)
wait_for(token, "keypoints", source_keypoint_id)
```

## 2つの姿勢の比較描画

最後に、座標データを動画に描画します。
今回は1つの動画に座標データの描画ともう1つの動画の座標データを描画して、動きの違いを可視化します。
処理完了後、描画処理された動画のダウンロードURLが取得できるので、動画をダウンロードします。

### 実装

```python
def create_drawing(token: str, source_keypoint_id: int, target_keypoint_id: int) -> int:
    headers = {"Content-Type": "application/json", "Authorization": token}
    body = {
        "keypointId": source_keypoint_id,
        "rule": [
            {
                "drawingType": "merge",
                "overlap": {
                    "targetId": target_keypoint_id,
                    "pivot": "leftAnkle",
                },
            }
        ],
    }
    url = urljoin(ANYMOTION_URL, "drawings/")
    res = requests.post(url, json=body, headers=headers)
    drawing_id = res.json()["id"]
    return drawing_id


def get_download_url(token: str, drawing_id: int) -> str:
    headers = {"Authorization": token}
    url = urljoin(ANYMOTION_URL, f"drawings/{drawing_id}/")
    res = requests.get(url, headers=headers)
    download_url = res.json()["drawingUrl"]
    return download_url


def download(download_url: str, filename: str, path: Path = MEDIA_DIR) -> None:
    res = requests.get(download_url)
    dest_path = path / filename
    with open(dest_path, "wb") as f:
        f.write(res.content)


drawing_id = create_drawing(token, source_keypoint_id, target_keypoint_id)
wait_for(token, "drawings", drawing_id)
download_url = get_download_url(token, drawing_id)
download(download_url, "output.mp4")
```

### 結果

![out.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2516553/b90809ef-c036-8d03-bd4b-069078f70ae1.gif)





## おわりに

今回は、AnyMotionを用いて動きの違いを可視化してみました。
1つの動画に2人の関節などの座標を描画することで動きの違いが明らかになりました。


## 参考サイト

* [AnyMotion](https://dm.nttpc.co.jp/lp/anymotion)
* [AnyMotion APIドキュメント](https://docs.anymotion.jp/)