AIによる背景判定処理の実験 前編 Python、PyTorch編

# はじめに
VRデバイスのMeta Quest3でどの程度のAI処理ができるか知るために実験したので記事にしました。
前編はPCで期待する効果が得られるかテストします。
具体的にはPythonとPyTorchを使ってAI（CNN、画像認識）により背景か前景かを判定します。
[後編](https://qiita.com/amaman/items/812d8bb3b8549a5b11cd)ではMeta Quest3で実際に動作させます。
アマレコVR※の背景透過処理※への応用を想定した実験となっています。

:::note info
※ アマレコVR
私が制作しているMeta Quest用 VR動画プレイヤー

※背景透過処理
再生中の動画の画像をリアルタイムで解析し 変化のない部分を透明にする機能
他の動画やパススルー映像と合成することができます
:::



# 参考記事
この実験は私個人のAIの勉強を兼ねています。以下の記事から多くを学び参考にさせていただきました。

https://qiita.com/kenichiro-yamato/items/b64c70882473904600bf

PyTorchではなくkerasの使用例です。正解率が高かったのでCNNモデルを参考にしました。
<br>
<br>

https://qiita.com/Rowing0914/items/e9c0ef560d71b83f57aa

PyTorchの使用例です。こちらの記事にそって実験しています。


# 環境
- Windows10
- Python Ver 3.11.4
- PyTorch Ver 2.2.0.dev20230915+cu121



# 実験内容
あらかじめ犬や猫、車などの写真（一般例）を学習してから 入力された画像（未知の写真）に何が写っているかを判定する 画像処理系AIの代表例であるCNN（Convolutional Neural Network）を使います。

最初に、犬や猫の代わりに[背景](#背景画像)に属する画像と[前景（被写体）](#前景画像)に属する画像を[CNNモデル](#cnnモデル)を使って学習し保存します。
次に入力された画像を縦横16分割（全部で256分割）した各エリアに対し 保存した[CNNモデル](#cnnモデル)を使って背景と前景のどちらの特徴が強いか推論します。

上手くいけば次のような結果が得られるはずです。

:::note info
- 背景（被写体が含まれないエリア）
どちらの数値（背景、前景）も低いか 背景の数値が高くなる

- 前景（被写体が含まれるエリア）
含まれる被写体の面積により 前景の数値が高くなる
:::



# 結果

https://x.com/amarectv/status/1740977811907850533?s=20







使用したサンプル動画

https://pixabay.com/ja/videos/%E8%B8%8A%E3%82%8A%E5%AD%90-%E3%83%88%E3%83%B3%E3%83%8D%E3%83%AB-%E9%80%9A%E8%B7%AF-%E3%83%80%E3%83%B3%E3%82%B9-84047/



# 学習に使った画像
## 背景画像
動画のスクリーンショットから被写体が映っていないところを切り取って背景画像にしました。
<img width="240" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/12c4c7e5-e92b-6834-36b2-f2576d3d4be9.png"> <img width="185"  src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/8de298b1-881c-dd48-0252-ed4d7e28dd2d.jpeg">

## 前景画像
動画から被写体だけを切り取り、さらにできるだけ背景が残らないようにマスクします。こんな画像を14枚作成しました。（ピンクの部分は学習しません）
<img width="120" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/bc577c02-80c5-4b63-b18f-4a6727a1a595.png"> <img width="86"  src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/ec0d64f0-6745-c418-01be-d896581345d1.png">


## 実際に学習する画像
それぞれの画像から32x32の領域を切り出して 一つ一つを小さい1枚の画像として学習します。
![chip_bg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/c292b8e1-9bd3-3197-5aaa-00cea0fd8eec.png)
![chip_fg.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3624096/642363e6-5e12-66d7-4326-fe9a95272bbd.png)


# CNNモデル
```python
import torch
import torch.nn as nn
from torch import Tensor

# ニューラルネットワークの定義
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()
    
        # 画像処理
        self.feature = nn.Sequential(
            # ブロック1
            nn.Conv2d( 3, 16, kernel_size=3, padding=(1,1), padding_mode="replicate"), #448
            nn.ReLU(),
            nn.Conv2d(16, 16, kernel_size=3, groups=16, padding=(1,1), padding_mode="replicate"), #160
            nn.ReLU(),
            nn.Conv2d(16, 16, kernel_size=1), #272
            nn.ReLU(),
            nn.MaxPool2d((2,2)),
            nn.Dropout(0.25),
        
            # ブロック2
            nn.Conv2d(16, 16, kernel_size=3, groups=16, padding=(1,1), padding_mode="replicate"), #160
            nn.ReLU(),
            nn.Conv2d(16, 32, kernel_size=1), #544
            nn.ReLU(),
            nn.Conv2d(32, 32, kernel_size=3, groups=32, padding=(1,1), padding_mode="replicate"), #320
            nn.ReLU(),
            nn.Conv2d(32, 32, kernel_size=1), #1056
            nn.ReLU(),
            #nn.MaxPool2d((2,2)),
            nn.AdaptiveAvgPool2d((1,1)),
            nn.Dropout(0.25)
        )
        
        # 平滑化
        self.flatten = nn.Flatten()
        
        # 全結合
        self.classifier = nn.Sequential(
            nn.Linear(32, 256), #8448
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(256, 2), #514
            #nn.Softmax()
        )
    
    
    def forward(self, input_data:Tensor):
        input_data = self.feature(input_data)
        input_data = self.flatten(input_data)
        input_data = self.classifier(input_data)
        #input_data = F.softmax(input_data)
        return input_data
```

最終的に非力なスマホで使うので 性能（画像認識能力）を多少削ってでも計算コストが低くなるように設計しています。具体的には コンボリューション回数やパラメーター数が少なくなるようにしています。

コンボリューションは最初のRGBの3ch入力、16ch出力のフィルターを除き[Depthwise Separable Convolution](https://developers.agirobots.com/jp/depthwise-pointwise-convolution/)を採用しました。
性能はあまり変わらず、劇的にコンボリューション回数を減らすことができます。

続いて、画像処理のブロック2の最後をMaxPoolから[GAP](https://qiita.com/mine820/items/1e49bca6d215ce88594a)へ変更しました。これもパラメータ数を劇的に減らすことができます。
約50万パラメータ（8x8x32x256+256）を8000程度(32x256+256)まで減らしています。そのかわり性能が低下します。

参考記事

https://developers.agirobots.com/jp/depthwise-pointwise-convolution/

https://qiita.com/mine820/items/1e49bca6d215ce88594a




後編ではこのCNNモデルをMeta Quest3で実行します。