【簡単】webカメラでAI自動認識! for Google Colab

# はじめに
「クラウドサービスの`Google Colab`」と「ローカルPCの`webカメラ`」を利用し、AIにより「**`モノの認識`**」が簡単にできたら楽しいと思いませんか？
公開されているモデルを利用することで**簡単**にそんなことができます。
では、早速やってみましょう！

※Google Colabの使用方法は『[0からはじめる「Python AIプログラミング」 for Google Colab](https://qiita.com/kenT100/items/f6b7205c4c20948e9c0d)』を参照して、簡単に理解しておいてください。

# 具体的にどんなことをやるか
webカメラからクラウドに映像を取り込み、そこに映ったものは何か、AIに**`リアルタイム認識`**させ、画面にTOP3までを表示します。今回は、**`学習済みモデル`**を利用しますので、時間のかかるAI学習などはありませんので、**サクッと遊べます**。
![pic7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/69462809-6786-28a6-d769-875c9522c1a5.png)

※ローカルPC版の『[【簡単】webカメラでAI自動認識! for PC](https://qiita.com/kenT100/items/5a43a1c9bad5ff41d336)』は、こちらです

# 開発環境準備
Google Colabが全部用意してくれるので、今回はインストールなどはありません。
「`Google Colabサービス`」を利用するのに「`Googleアカウント`」が必要なだけです。

|No.|開発環境|説明|
|:-:|:-:|:--|
|1|Googleアカウント|Google Colab利用のためにGoogleアカウントを使用します。無料です。|
|2|Google Colabサービス|Googleのクラウド仮想マシン上で動くPython実行環境。使用料は無料です。インストール等の作業などは不要です。GPUも無料で使用でき、AI学習も素早く計算が可能です。|
|3|クラアントPC with webカメラ|クライアントPCのwebカメラを使用するため必要です。|


# DenseNet121
> 簡単って言うからやっているのに、
> いきなり`DenseNet121`と言われて、ワケワカラン！
> もうやめだ！

大丈夫です。落ち着いてください。これは**学習済みモデル**のことで、今回は`DenseNet121`という学習モデルを使用しますということです。細かいこと分からなくてもできます！

![pic8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/17ab806d-7280-f12a-1705-6149f34f0e84.png)

- なぜ`DenseNet121モデル`に選定したのか？

`kerasライブラリ`から`VGG16`、`ResNet50`など様々な画像分類モデルが簡単に使えるようになっていますが、その中から今回使用する画像分類モデルは、**モデルサイズが33MBと比較的小さく認識率も良い**ため`DenseNet121`を選びました。「Keras Documentation」によると、`DenseNet121`でモノを認識させた場合、TOP5までの認識正解率が約92%となるそうです。(TOP1だけだと約75％です)
![FireShot Capture 003 - Applications - Keras Documentation - keras.io.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/ae977466-d22e-d118-f448-dee3057640e6.png)


引用元：Keras Documentation
https://keras.io/ja/applications/#documentation-for-individual-models

# AIプログラム
今回Google Colab上でローカルPCのwebカメラを扱うプログラムとして、@a2kitiさんの記事([Colab上でwebカメラをリアルタイムに処理](https://qiita.com/a2kiti/items/f32de4f51a31d609e5a5))を参考にさせて頂きました😌

下記3つのプログラムを全てGoogle Colabのコード行にコピペし終わったら、[`ランタイム`]→[`すべてのセルを実行`]をクリックして、プログラムを実行してください。

### ●Program1
``` python:colabコード
import IPython
from google.colab import output
from google.colab.patches import cv2_imshow # incompatible with Jupyter notebook
import cv2
import numpy as np
from PIL import Image, ImageDraw
from io import BytesIO
import base64
from keras.applications.densenet import DenseNet121
from keras.applications.densenet import preprocess_input, decode_predictions
from keras.preprocessing import image

model = DenseNet121(weights='imagenet')

def run(img_str):
    #decode to image
    decimg = base64.b64decode(img_str.split(',')[1], validate=True)
    decimg = Image.open(BytesIO(decimg))
    decimg = np.array(decimg, dtype=np.uint8); 
    decimg = cv2.cvtColor(decimg, cv2.COLOR_BGR2RGB)

    #############your process###############
    # DenseNet121画像判定
    resize_frame = cv2.resize(decimg, (300, 224))           # 640x480 -> 300x224(4:3)に画像リサイズ
    trim_x, trim_y = int((300-224)/2), 0                    # 判定用に224x224へトリミング
    trim_h, trim_w = 224, 224
    trim_frame = resize_frame[trim_y : (trim_y + trim_h), trim_x : (trim_x + trim_w)]
    x = image.img_to_array(trim_frame)
    x = np.expand_dims(x, axis=0)
    x = preprocess_input(x)
    preds = model.predict(x)                                # 画像AI判定

    # 認識物体名, 認識率 表示用
    TXT1 = "[DenseNet121 predict]"
    TXT2 = "TOP1 :" + decode_predictions(preds, top=3)[0][0][1] + " : " + str(int(decode_predictions(preds, top=3)[0][0][2] * 100)) + "%"
    TXT3 = "TOP2 :" + decode_predictions(preds, top=3)[0][1][1] + " : " + str(int(decode_predictions(preds, top=3)[0][1][2] * 100)) + "%"
    TXT4 = "TOP3 :" + decode_predictions(preds, top=3)[0][2][1] + " : " + str(int(decode_predictions(preds, top=3)[0][2][2] * 100)) + "%"
 
    img = Image.fromarray(trim_frame)
    draw = ImageDraw.Draw(img)
    draw.text((15, 10), TXT1, fill=(255, 255, 255, 0))
    draw.text((15, 30), TXT2, fill=(255, 255, 255, 0))
    draw.text((15, 50), TXT3, fill=(255, 255, 255, 0))
    draw.text((15, 70), TXT4, fill=(255, 255, 255, 0))
    img = np.array(img)
#   cv2_imshow(img)
    out_img = cv2.resize(img, (224*3, 224*3))               # 拡大表示
    #############your process###############

    #encode to string
    _, encimg = cv2.imencode(".jpg", out_img, [int(cv2.IMWRITE_JPEG_QUALITY), 80])
    img_str = encimg.tostring()
    img_str = "data:image/jpeg;base64," + base64.b64encode(img_str).decode('utf-8')
    return IPython.display.JSON({'img_str': img_str})

output.register_callback('notebook.run', run)
```

### ●Program2
``` python:colabコード
from IPython.display import display, Javascript
from google.colab.output import eval_js

def use_cam(quality=0.8):
  js = Javascript('''
    async function useCam(quality) {
      const div = document.createElement('div');
      document.body.appendChild(div);
      //video element
      const video = document.createElement('video');
      video.style.display = 'None';
      const stream = await navigator.mediaDevices.getUserMedia({video: true});
      div.appendChild(video);
      video.srcObject = stream;
      await video.play();

      //canvas for display. frame rate is depending on display size and jpeg quality.
      display_size = 640 
      const src_canvas = document.createElement('canvas');
      src_canvas.width  = display_size;
      src_canvas.height = display_size * video.videoHeight / video.videoWidth;
      const src_canvasCtx = src_canvas.getContext('2d');
      src_canvasCtx.translate(src_canvas.width, 0);
      src_canvasCtx.scale(-1, 1);
      div.appendChild(src_canvas);

      const dst_canvas = document.createElement('canvas');
      dst_canvas.width  = src_canvas.width;
      dst_canvas.height = src_canvas.height;
      const dst_canvasCtx = dst_canvas.getContext('2d');
      div.appendChild(dst_canvas);

      //exit button
      const btn_div = document.createElement('div');
      document.body.appendChild(btn_div);
      const exit_btn = document.createElement('button');
      exit_btn.textContent = 'Exit';
      var exit_flg = true
      exit_btn.onclick = function() {exit_flg = false};
      btn_div.appendChild(exit_btn);

      // Resize the output to fit the video element.
      google.colab.output.setIframeHeight(document.documentElement.scrollHeight, true);

      var send_num = 0
      // loop
      _canvasUpdate();
      async function _canvasUpdate() {
            src_canvasCtx.drawImage(video, 0, 0, video.videoWidth, video.videoHeight, 0, 0, src_canvas.width, src_canvas.height);     
            if (send_num<1){
                send_num += 1
                const img = src_canvas.toDataURL('image/jpeg', quality);
                const result = google.colab.kernel.invokeFunction('notebook.run', [img], {});
                result.then(function(value) {
                    parse = JSON.parse(JSON.stringify(value))["data"]
                    parse = JSON.parse(JSON.stringify(parse))["application/json"]
                    parse = JSON.parse(JSON.stringify(parse))["img_str"]
                    var image = new Image()
                    image.src = parse;
                    image.onload = function(){dst_canvasCtx.drawImage(image, 0, 0)}
                    send_num -= 1
                })
            }
            if (exit_flg){
                requestAnimationFrame(_canvasUpdate);   
            }else{
                stream.getVideoTracks()[0].stop();
            }
      };
    }
    ''')
  display(js)
  data = eval_js('useCam({})'.format(quality))
```

### ●Program3
``` python:colabコード
use_cam()
```

# AIプログラム実行
![FireShot Capture 008 - webカメラ2.ipynb - Colaboratory - colab.research.google.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405376/d1b337ce-0732-785c-b48f-571b2a6bd4ad.png)

こんな感じにGoogle Colab上でクライアントPCのwebカメラで映した映像が出力され、AIが認識したTOP3の情報が画面に表示されれば成功です。ちなみにうちのワンコ(トイプードル)を映した結果は、下記の様にAIが96%の確率でプードルでしょうと言っている(トイ・プードルでは49%😅)ので、AIの認識はそれなりに正しいものになります😊

> **【結果の一例】AIの認識率TOP3**
> 1. toy_poodle : 49%
> 2. miniature_poodle : 45%
> 3. standard_poodle : 2%
> ※リアルタイムで認識率は変動します。


# 識別可能クラス
「`imagenet_class_index.json`」ファイルを参照すると、イメージデータが以下のNo.0～999の**1000クラスに分類**ができることが分かります。**制限事項**にもなりますが、ここに記載が無いものを認識させても、ここの中のどれかにクラス分類されてしまいます。ここに無いものを分類したい場合は、`新たにモデル`を作るか、`転移学習`、`ファインチューニング`などを調べていただければと思います。

```imagenet_class_index.json
{
	"0": ["n01440764", "tench"],
	"1": ["n01443537", "goldfish"],
	"2": ["n01484850", "great_white_shark"],
	"3": ["n01491361", "tiger_shark"],
	"4": ["n01494475", "hammerhead"],
	"5": ["n01496331", "electric_ray"],
	"6": ["n01498041", "stingray"],
	"7": ["n01514668", "cock"],
	"8": ["n01514859", "hen"],
	"9": ["n01518878", "ostrich"],

          ～ 省略 ～

	"990": ["n12768682", "buckeye"],
	"991": ["n12985857", "coral_fungus"],
	"992": ["n12998815", "agaric"],
	"993": ["n13037406", "gyromitra"],
	"994": ["n13040303", "stinkhorn"],
	"995": ["n13044778", "earthstar"],
	"996": ["n13052670", "hen-of-the-woods"],
	"997": ["n13054560", "bolete"],
	"998": ["n13133613", "ear"],
	"999": ["n15075141", "toilet_tissue"]
}
```

# 以上
Google Colabだと`cv2.imshow()`など、cv2の特定メソッドを起動するとクラッシュしてしまうそうで、画像処理させるときには注意が必要です。`cv2.imshow()`については、Google Colabからパッチが出ている「`from google.colab.patches import cv2_imshow`」で代替できます。他のメソッドも実行してみてダメなら他の方法で代替するしかないみたいです。ちなみに`cv2.resize()`は大丈夫でしたが、`cv2.putText()`はダメでした😥そのため`PIL(Pillow)`で画像にテキストを描き込んでいます。

話は変わりますが、いまだとバウンディングボックスを表示してくれる[`yolo Ver5`](https://qiita.com/PoodleMaster/items/5f2cc3248c03b03821b8)なども公開されていますね。こちらも試してみましたがとても凄いです。AIは発展目覚ましいですね😊

YOLO V5は、[こちらの記事](https://qiita.com/PoodleMaster/items/5f2cc3248c03b03821b8)で、じゃんけん検出を行っていますので、よろしければご覧になってください。

お疲れ様でした！