Raspberry Pi AI Camera の Workout Monitoring サンプルアプリをアレンジしてバーチャル試着アプリを作る

こんにちは。
ソニーセミコンダクタソリューションズの高橋です。
<!-----------------
記事本文は下記レビュー観点を意識したうえで、自由に記載してください。 
 - [ ] 非公開情報はないか
 - [ ] 社内用語が使われていないか
 - [ ] 共通ワーディングを利用しているか
 - [ ] 技術的間違いがないか
 - [ ] リンク切れを起こしていないか
 - [ ] 政治、宗教等センシティブな領域に言及していないか (CATOPE対応)
 - [ ] 文体の統一/誤字チェック (JustRight対応) 
 - [ ] その他不適切な表現・内容が含まれていないか
 - [ ]  問い合わせに関する文言を記載しているか  　
------------------->  
# はじめに
2024年9月に、[Raspberry Pi AI Camera](https://www.raspberrypi.com/products/ai-camera/) が発売されました！
Raspberry Pi に接続可能な、エッジAIのカメラモジュールです。  

本記事では、Raspberry Pi AI Camera を使ってアプリ開発を始める方向けに、公式サンプルアプリである「[Workout Monitoring](https://github.com/SonySemiconductorSolutions/aitrios-rpi-sample-apps/tree/main/examples/workout-monitor
)」を動かし、さらにそれをアレンジして新たなアプリケーションを作ってみます。

# 手順

## 事前準備
機材を用意します。主な機材は下記です。
- Raspberry Pi 5
    - Raspberry Pi 4 Model B などでも問題ないです
    - Raspberry Pi OS (64-bit) をインストール
- Raspberry Pi AI Camera
- Raspberry Pi を操作するための付属品 (モニタ、キーボード、マウスなど) 

## Raspberry Pi AI Cameraのセットアップ
Raspberry Pi AI Camera をセットアップして、Raspberry Pi 本体と Raspberry Pi AI Camera が適切に通信できるようにします。  
詳細な説明は省略しますが、[公式ドキュメント](https://www.raspberrypi.com/documentation/accessories/ai-camera.html) や [他の方の素晴らしい記事](https://qiita.com/daiki0321/items/4223611adb453d6c7aa2) を参考に、セットアップを実施してください。

Raspberry Pi の端末 (ターミナル) で下記コマンドを実行して、正しく映像と物体検出結果が表示されるようになればOKです。
```bash
rpicam-hello -t 0s --post-process-file /usr/share/rpi-camera-assets/imx500_mobilenet_ssd.json
```

## Application Module Library のインストール
[Application Module Library](https://github.com/SonySemiconductorSolutions/aitrios-rpi-application-module-library) をインストールします。  
Application Module Library とは、ソニーセミコンダクタソリューションズからリリースされている Raspberry Pi AI Camera 向けライブラリです。  
これを利用すると、容易にデータ可視化やアプリケーション開発をすることができます。

まず、リポジトリを GitHub から Raspberry Pi へクローンします。

```bash
git clone https://github.com/SonySemiconductorSolutions/aitrios-rpi-application-module-library.git
cd aitrios-rpi-application-module-library
```

続いて、Application Module Library の [README](https://github.com/SonySemiconductorSolutions/aitrios-rpi-application-module-library) に記載されている下記の手順を実行します。 
1. Development environment setup.  
2. Building the Python wheel.  

:::note info 
[README](https://github.com/SonySemiconductorSolutions/aitrios-rpi-application-module-library) の Basic example を実行して、Application Module Library を利用した物体検出アプリケーションが動作するまでを確認することをおすすめします。  
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3884385/1f431ae1-9dc8-41de-a815-2f8240ffb8d2.png" width="50%">
:::

## Workout Monitoring サンプルアプリをそのまま動かしてみる
本記事では、ソニーセミコンダクタソリューションズからリリースされている [IMX500 Sample Applications](https://github.com/SonySemiconductorSolutions/aitrios-rpi-sample-apps) の中の [Workout Monitoring](https://github.com/SonySemiconductorSolutions/aitrios-rpi-sample-apps/tree/main/examples/workout-monitor) サンプルアプリをベースにして、開発を進めていきます。  
まずは、Workout Monitoring サンプルアプリをそのまま動かしてみましょう。

uv をインストールします。
```bash
cd ~
curl -LsSf https://astral.sh/uv/install.sh | sh
```

uv のインストールができたら、一度端末 (ターミナル) を再起動してください。

続けて、IMX500 Sample Applications を GitHub からクローンして、Workout Monitoring サンプルアプリのディレクトリへ移動します。
```bash
git clone https://github.com/SonySemiconductorSolutions/aitrios-rpi-sample-apps.git
cd aitrios-rpi-sample-apps/examples/workout-monitor
```

その後、[Workout Monitoring サンプルアプリの README](https://github.com/SonySemiconductorSolutions/aitrios-rpi-sample-apps/blob/main/examples/workout-monitor/README.md) に記載されている手順で、アプリケーションをインストールして、実行します。
```bash
uv venv --system-site-packages
source .venv/bin/activate
uv run app.py --exercise squat
```

アプリケーションの実行が成功すれば、無事に人の骨格情報が取得できていることが確認できるはずです！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3884385/5175db04-4abe-4755-920c-bf6a8e064c9a.png" width="50%">

:::note info 
上記の例では上半身のみを写していますが、全身を写せば顔・下半身を含めた全17個の骨格特徴点 (キーポイント) が取得できます。
:::  

この Working Monitoring アプリケーションは、スクワットなどの運動をカウントしてくれる機能を持っています。  
ぜひこれを使って身体を鍛えてみてください！  

## Workout Monitoring サンプルアプリをアレンジしてバーチャル試着アプリを作ってみる
ここからは、前のセクションで動かしてみた Workout Monitoring サンプルアプリをベースにして、バーチャル試着アプリを作っていきます。  
今回は人の上半身にTシャツの画像を試着 (重畳) させてみましょう！

### 画像を重畳する処理を追加
今回は、4つの骨格特徴点 (＝右肩、左肩、右腰、左腰) の座標をもとにして、重畳画像の位置・高さ・幅を求めていきます。
![algorism_overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3884385/1ddb0ab7-5ce1-4111-b1e0-a75dfa3a770e.png)


では、実際に、Workout Monitoring サンプルアプリの app.py に処理を記述していきます。

まずは、AIモデルから必要な4つの骨格特徴点 (キーポイント) の座標 (0から1の範囲) を受け取り、出力画像の座標に変換します。  

```python
scene = frame.image
height, width = scene.shape[:2]

# Convert normalized keypoint coordinates to actual image coordinates
left_shoulder_x  = int(keypoints[5 * 2 + 1] * width)
left_shoulder_y  = int(keypoints[5 * 2] * height)
right_shoulder_x = int(keypoints[6 * 2 + 1] * width)
right_shoulder_y = int(keypoints[6 * 2] * height)
left_hip_x       = int(keypoints[11 * 2 + 1] * width)
left_hip_y       = int(keypoints[11 * 2] * height)
right_hip_x      = int(keypoints[12 * 2 + 1] * width)
right_hip_y      = int(keypoints[12 * 2] * height)
```

なお、keypoints配列の各要素のインデックスは下の画像のような関係になっているようです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3884385/8468cca0-73c3-4c9e-94e2-d7e2c6e14597.png" width="70%">

続いて、4つの骨格特徴点 (キーポイント) から有効な値を選んで取得し、それらをもとに上半身の右上と左下の座標を求めます。
```python
def select_valid_coordinate(coord_a, coord_b):
    if coord_a == 0 and coord_b == 0:
        return 0  
    elif coord_a == 0:
        return coord_b
    else:
        return coord_a

# Determine upper body bounding box coordinates
right_top_x   = select_valid_coordinate(right_shoulder_x, right_hip_x)
right_top_y   = select_valid_coordinate(right_shoulder_y, left_shoulder_y)
left_bottom_x = select_valid_coordinate(left_shoulder_x,  left_hip_x)
left_bottom_y = select_valid_coordinate(right_hip_y,      left_hip_y)
```

求めた上半身の右上・左下の座標を基準として、それに適切なオフセットや倍率を適用することにより、重畳画像の位置・高さ・幅を求めます。  
このオフセットや倍率は、重畳したい画像の大きさによって変化するため、必要に応じて修正してください。
```python
# Constants defenition
OVERLAY_IMG_X_OFFSET_RATIO = 2.0 # Decrease this to increase the leftward offset of the overlay image
OVERLAY_IMG_Y_OFFSET_RATIO = 3.5 # Decrease this to increase the upward offset of the overlay image
OVERLAY_IMG_HEIGHT_RATIO   = 2.2 # Decrease this to reduce the overlay image height
OVERLAY_IMG_WIDTH_RATIO    = 1.5 # Decrease this to reduce the overlay image width
CONFIDENCE_THRETHORLD      = 0.3 # Detection confidence threshold

# Adjust overlay image position based on reference points
overlay_img_x = int(right_top_x - (left_bottom_x - right_top_x) // OVERLAY_IMG_X_OFFSET_RATIO)
overlay_img_y = int(right_top_y - (left_bottom_y - right_top_y) // OVERLAY_IMG_Y_OFFSET_RATIO)

# Resize overlay image based on reference points
overlay_img_h = int((left_bottom_y - right_top_y) * OVERLAY_IMG_HEIGHT_RATIO)
overlay_img_w = int((left_bottom_x - right_top_x) * OVERLAY_IMG_WIDTH_RATIO)
```

最後に、Tシャツの画像を透過成分も含めて読み込み、上記で求めた位置に配置します。
```python
scene = frame.image
overlay_image = cv2.imread("tshirt.png", cv2.IMREAD_UNCHANGED)

resized_overlay = cv2.resize(overlay_image, (overlay_img_w, overlay_img_h))

# Separate alpha channel
alpha_channel = resized_overlay[:, :, 3] / 255.0
rgb_channels  = resized_overlay[:, :, :3]

# Copy the overlay part to the scene
for c in range(0, 3):
    scene[overlay_img_y:overlay_img_y+overlay_img_h, overlay_img_x:overlay_img_x+overlay_img_w, c] = (alpha_channel * rgb_channels[:, :, c] + (1 - alpha_channel) * scene[overlay_img_y:overlay_img_y+overlay_img_h, overlay_img_x:overlay_img_x+overlay_img_w, c])
```

### 完成コード
フレーム毎の繰り返しの処理部なども含めた完成形のコードはこちらになります。
[GitHub](https://github.com/SonySemiconductorSolutions/aitrios-rpi-sandbox/tree/develop/examples/virtual-fitting) でも公開しています。
```python
import cv2
from modlib.devices import AiCamera
from modlib.models.zoo import Higherhrnet
from modlib.apps.tracker.byte_tracker import BYTETracker

# Constants defenition
OVERLAY_IMG_X_OFFSET_RATIO = 2.0 # Decrease this to increase the leftward offset of the overlay image
OVERLAY_IMG_Y_OFFSET_RATIO = 3.5 # Decrease this to increase the upward offset of the overlay image
OVERLAY_IMG_HEIGHT_RATIO   = 2.2 # Decrease this to reduce the overlay image height
OVERLAY_IMG_WIDTH_RATIO    = 1.5 # Decrease this to reduce the overlay image width
CONFIDENCE_THRETHORLD      = 0.3 # Detection confidence threshold

class BYTETrackerArgs:
    track_thresh: float = 0.25
    track_buffer: int = 30
    match_thresh: float = 0.8
    aspect_ratio_thresh: float = 3.0
    min_box_area: float = 1.0
    mot20: bool = False

def select_valid_coordinate(coord_a, coord_b):
    """
    Select a valid coordinate, prioritizing the first non-zero value
    """
    if coord_a == 0 and coord_b == 0:
        return 0  
    elif coord_a == 0:
        return coord_b
    else:
        return coord_a

def overlay_image_on_upper_body(frame, keypoints, overlay_image):
    """
    Overlay an image on the upper body
    """
    scene = frame.image
    height, width = scene.shape[:2]

    # Convert normalized keypoint coordinates to actual image coordinates
    left_shoulder_x  = int(keypoints[5 * 2 + 1] * width)
    left_shoulder_y  = int(keypoints[5 * 2] * height)
    right_shoulder_x = int(keypoints[6 * 2 + 1] * width)
    right_shoulder_y = int(keypoints[6 * 2] * height)
    left_hip_x       = int(keypoints[11 * 2 + 1] * width)
    left_hip_y       = int(keypoints[11 * 2] * height)
    right_hip_x      = int(keypoints[12 * 2 + 1] * width)
    right_hip_y      = int(keypoints[12 * 2] * height)

    # Determine upper body bounding box coordinates
    right_top_x   = select_valid_coordinate(right_shoulder_x, right_hip_x)
    right_top_y   = select_valid_coordinate(right_shoulder_y, left_shoulder_y)
    left_bottom_x = select_valid_coordinate(left_shoulder_x,  left_hip_x)
    left_bottom_y = select_valid_coordinate(right_hip_y,      left_hip_y)

    # Adjust overlay image position based on reference points
    overlay_img_x = int(right_top_x - (left_bottom_x - right_top_x) // OVERLAY_IMG_X_OFFSET_RATIO)
    overlay_img_y = int(right_top_y - (left_bottom_y - right_top_y) // OVERLAY_IMG_Y_OFFSET_RATIO)

    # Resize overlay image based on reference points
    overlay_img_h = int((left_bottom_y - right_top_y) * OVERLAY_IMG_HEIGHT_RATIO)
    overlay_img_w = int((left_bottom_x - right_top_x) * OVERLAY_IMG_WIDTH_RATIO)

    try:
        resized_overlay = cv2.resize(overlay_image, (overlay_img_w, overlay_img_h))

        # Separate alpha channel
        alpha_channel = resized_overlay[:, :, 3] / 255.0
        rgb_channels  = resized_overlay[:, :, :3]

        # Copy the overlay part to the scene
        for c in range(0, 3):
            scene[overlay_img_y:overlay_img_y+overlay_img_h, overlay_img_x:overlay_img_x+overlay_img_w, c] = (alpha_channel * rgb_channels[:, :, c] + (1 - alpha_channel) * scene[overlay_img_y:overlay_img_y+overlay_img_h, overlay_img_x:overlay_img_x+overlay_img_w, c])
    except:
        pass

    return scene

def start_workout_demo():
    device = AiCamera()
    model = Higherhrnet()
    device.deploy(model)

    # Load the overlay image
    overlay_image = cv2.imread("tshirt.png", cv2.IMREAD_UNCHANGED)

    tracker = BYTETracker(BYTETrackerArgs())

    with device as stream:
        for frame in stream:
            detections = frame.detections[frame.detections.confidence > CONFIDENCE_THRETHORLD]
            detections = tracker.update(frame, detections)

            for k, _, _, _, t in detections:
                frame.image = overlay_image_on_upper_body(frame, k, overlay_image)

            frame.display()

if __name__ == "__main__":
    start_workout_demo()
    exit()
```

### 動作例
[いらすとや様のTシャツ画像](https://www.irasutoya.com/2018/07/t.html)をダウンロードして、それを app.py と同じディレクトリに "tshirt.png" として配置したうえで、実際に動かしてみました！  
いい感じに試着できていますね。  
![virtual-fitting_sample_3sec.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3884385/0148e804-4104-404d-9e21-a6a6b3c13d66.gif)


# 困った時は

もし、記事の途中でうまくいかなかった場合は、お気軽にこの記事へコメントいただくか、下記のサポートページをご活用ください。  
コメントのお返事にはお時間を頂く可能性もありますがご了承ください。
- [Raspberry Pi Forums](https://forums.raspberrypi.com/)

# おわりに
今回は、ソニーセミコンダクタソリューションズから提供されている Workout Monitoring サンプルアプリを動作させ、さらにそれをアレンジしてバーチャル試着アプリを開発しました。  
利用したAIモデルは、顔や下半身を含めた全17個の骨格特徴点を検出してくれるので、例えば、顔の骨格特徴点を使ってメガネを試着させてみるといったアレンジも考えられそうですね！  

また、ソニーセミコンダクタソリューションズは、[IMX500 Sample Applications](https://github.com/SonySemiconductorSolutions/aitrios-rpi-sample-apps) で他にも4つのサンプルアプリを公開しています。  
色々なサンプルアプリをベースにして、色々なアレンジを試してみてください！