(簡易)光学迷彩プログラム

## 目的
映像に映った人を透明化できる簡易的な光学迷彩[^1]プログラムを作成する
(背景問わず透明化できるようにしたい[^2])

[^1]:https://ja.wikipedia.org/wiki/%E5%85%89%E5%AD%A6%E8%BF%B7%E5%BD%A9
[^2]:背景が単色の場合は、クロマキー合成という手法でできるというのはよく聞くが、今回の目的はどんな背景でもできるようにすること(https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AD%E3%83%9E%E3%82%AD%E3%83%BC)

<ins><b>完成イメージ</b></ins>

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/0e25b545-cfed-793f-6666-9b1a7a6aab56.png" width="80%">

今回は上図の<font color="red">赤字部分</font>を対象にしてプログラム作成

## 環境
#### HW
+ PC：Lenovo ThinkBook 13s Gen 3
+ カメラ：PC内蔵のインカメラ

#### SW
+ Python：v3.11.5
+ Anaconda：conda v23.7.4
+ 物体検出モデル：YOLO v8

## 作業メモ

### 環境整備

<b>参考サイト</b>
https://qiita.com/shiganai/items/11537a0083f978e6821d
https://github.com/ultralytics/ultralytics

#### Anaconda の導入
これは、[参考サイト](https://qiita.com/shiganai/items/11537a0083f978e6821d) に記載の手順でPCに導入しただけなので、詳細は省略

#### YOLO v8 の導入
[参考サイト](https://github.com/ultralytics/ultralytics) を参考にして導入していく

+ YOLO v8をclone
```
$ git clone https://github.com/ultralytics/ultralytics.git
$ cd ultralytics
```

+ ultralytics packageのinstall
```
$ pip install ultralytics
・・・
Successfully installed charset-normalizer-3.3.2 contourpy-1.1.1 cycler-0.12.1 filelock-3.13.1 fonttools-4.47.0 fsspec-2023.12.2 importlib-resources-6.1.1 kiwisolver-1.4.5 matplotlib-3.7.4 mpmath-1.3.0 networkx-3.1 nvidia-cublas-cu12-12.1.3.1 nvidia-cuda-cupti-cu12-12.1.105 nvidia-cuda-nvrtc-cu12-12.1.105 nvidia-cuda-runtime-cu12-12.1.105 nvidia-cudnn-cu12-8.9.2.26 nvidia-cufft-cu12-11.0.2.54 nvidia-curand-cu12-10.3.2.106 nvidia-cusolver-cu12-11.4.5.107 nvidia-cusparse-cu12-12.1.0.106 nvidia-nccl-cu12-2.18.1 nvidia-nvjitlink-cu12-12.3.101 nvidia-nvtx-cu12-12.1.105 opencv-python-4.9.0.80 pandas-2.0.3 pillow-10.2.0 psutil-5.9.7 py-cpuinfo-9.0.0 pyparsing-3.1.1 python-dateutil-2.8.2 pytz-2023.3.post1 requests-2.31.0 scipy-1.10.1 seaborn-0.13.1 sympy-1.12 thop-0.1.1.post2209072238 torch-2.1.2 torchvision-0.16.2 tqdm-4.66.1 triton-2.1.0 tzdata-2023.4 ultralytics-8.0.237 zipp-3.17.0

[notice] A new release of pip is available: 23.2.1 -> 23.3.2
[notice] To update, run: /usr/bin/python3 -m pip install --upgrade pip
```

+ jupyter notebookで以下を実行し、動作確認
```
from ultralytics import YOLO
model = YOLO("yolov8n.pt")
results = model(0 , show=True)
```
<font color="red">→ PCのインカメラで映像が取得でき、簡単な物体検出(人とかの検出)ができることを確認</font>


### カメラ映像から人を透過させる
前節の[環境整備](#環境整備)で人の検出ができることは確認したため、人を透過させる方法を検討する

<b>参考サイト</b>
https://docs.ultralytics.com/guides/isolating-segmentation-objects/#recipe-walk-through

#### どうやって透過するか・・
今回は、[参考サイト](https://docs.ultralytics.com/guides/isolating-segmentation-objects/#recipe-walk-through) の内容を利用する
YOLOの検出結果の一部に認識した物体のmask画像に関する要素があるようで、その座標情報を利用して、認識した人を透過し、事前に取得した背景画像を映像に映すようなプログラムとする

#### プログラム
1. 準備段階として背景画像を取得 → `[Esc]キー`を押したタイミングで画像取得
2. 再度映像を取得し、人が映っている場合はその部分を透過した映像を画面に表示
    + 透過した部分には始めに取得した背景画像を表示
3. 終了したい場合は、`[Esc]キー`で抜ける

```python
#!/usr/bin/env python
# coding: utf-8

# In[1]:


from ultralytics import YOLO
import cv2
import numpy as np


# In[2]:


model = YOLO("yolov8n-seg.pt")
cap = cv2.VideoCapture(0)


# In[3]:


# (初期処理)Webカメラから背景画像を取得
try:
    cap.isOpened()
    while True:
        ret, img_bak = cap.read()
        cv2.imshow("background",img_bak)
    
        # 背景画像の再取得が不要な場合、Escキーで抜ける
        if cv2.waitKey(1) == 27:
            cv2.destroyAllWindows()
            break
            
except:
    # Webカメラが起動できない場合の例外処理
    cv2.destroyAllWindows()
    cap.release()
    print("Web camera can't open")


# In[4]:


while True:
    # Webカメラ映像取得
    ret, img_main = cap.read()
    results = model(img_main)

    # 取得した映像の検出結果(の一部)を保持
    for r in results:
        boxes_xyxy = r.boxes.numpy().xyxy

    # 人(person)が映像に映っている場合に以下の処理を実施
    if r.boxes.numpy().cls.size > 0 and r.boxes.numpy().cls[0] == 0:
        # 人をmask(消す)するための画像生成
        # https://docs.ultralytics.com/guides/isolating-segmentation-objects/#recipe-walk-through
        img_main_mask = np.zeros(r.boxes.numpy().orig_shape, np.uint8)
        contour = r[0].masks.xy.pop()
        contour = contour.astype(np.int32)
        contour = contour.reshape(-1, 1, 2)
        
        # 取得した輪郭からmask画像生成
        _ = cv2.drawContours(img_main_mask,
                             [contour],
                             -1,
                             (255, 255, 255),
                             cv2.FILLED)
        
        img_bak_mask  = cv2.bitwise_not(img_main_mask)

        # mask画像はグレースケールのため、BGRに変換
        img_main_mask = cv2.cvtColor(img_main_mask, cv2.COLOR_GRAY2BGR)
        img_bak_mask  = cv2.cvtColor(img_bak_mask, cv2.COLOR_GRAY2BGR)

        # Webカメラ画像をmask(人を消す)する
        img_main_masked = cv2.bitwise_or(img_main, img_main_mask)
        img_bak_masked  = cv2.bitwise_or(img_bak, img_bak_mask)
        img_masked      = cv2.bitwise_and(img_main_masked, img_bak_masked)

        # お試しにmaskした範囲を見える化
#        cv2.rectangle(img_masked, (int(boxes_xyxy[0][0]), int(boxes_xyxy[0][1])), (int(boxes_xyxy[0][2]), int(boxes_xyxy[0][3])), (255, 255, 0))
        _ = cv2.drawContours(img_masked,
                             [contour],
                             -1,
                             (255, 255, 0),
                             3)
        
        img_last = img_masked.copy()
        
    # 人(person)が映像に映っていない場合はWebカメラの画像をそのまま映す
    else:
        img_last = img_main.copy()
        
    # mask済み映像表示
    cv2.imshow("Web camera(masked)",img_last)
    
    # 処理を終了する場合は、Escキーで抜ける
    if cv2.waitKey(1) == 27:
        break
        
cv2.destroyAllWindows()
cap.release()
```

#### デモ
実際に動作させたときのデモ動画[^3]が以下になる
[^3]: 以下のツールを使用して、一部モザイクをかけている(https://jp.cyberlink.com/products/powerdirector-video-editing-software/overview_ja_JP.html)

+ 始めに`background`ウィンドウが開き、そこで`Escキー`を押して背景画像を取得
+ 次に`Web camera(masked)`ウィンドウが開き、人が映っている部分を透過した映像が表示される
    + お試しに透過範囲を見える化している
+ 最後に`Escキー`で抜ける

![output2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248487/7f8e5c20-b9c6-a60b-38b2-2adda009be0e.gif)



## まとめ
簡易だが、目標は達成！
>映像に映った人を透明化できる簡易的な光学迷彩プログラムを作成する
>(背景問わず透明化できるようにしたい)

ただし、まだまだ課題があるので修正は必要と考えられる
+ 人の動きに追従しきれず、透過できないケースがある
  (追従しきれないのは、PCスペックにもよると思うが)
+ 複数人の透過には未対応