PyTorchとYOLOV5を使って、カメラ付ノートPC（Windows）をオフラインのAI監視カメラにしてみました

#はじめに  

- 従来AIは学習させて使用するものでしたが、最近は汎用の学習済みモデルをダウンロードして使えるようになってます。この1つに__YOLOV5__があります。  
- これを利用して、カメラ付きノートPCをAI監視カメラ（ = 物体認識するカメラ）にしてみました。  
- CPUだけでどれだけの速さになるか、試したかったためです。

###工夫したこと
1. ローカル化  
　ネットワーク環境に縛られず、使えるようにしました。
1. PCカメラから映像取り込み  
　カメラからの映像を処理するようにしました。
1. 推定検出の処理軽量化  
　ハイスペックでないPCで動かすために、処理を減らしました。
 - 正確性は劣るが軽量なモデル__yolov5s__を使用  
 - 推定時の画像を小さくして軽量化  
1. 検出結果の変数取得  
　検出結果の矩形を取得し、ヒットエリアに入ったら、表示を変更するようにしました。


#内容
###1.PC環境
　実施したPC環境は以下のとおりです。GPUは計算に使ってません。  

| | |
|:--:|:--:|
|CPU|Celeron N4100|
|メモリ|8GB LPDDR4|

###2.前準備
　Windowsで使えるようにするため、以下のツール / システムをインストールしました。
インストール時に参考にしたサイトも記載します。  
1.python（使用したのはVer.3.7 ）
　実行時のベースシステムです。
（参考）https://qiita.com/ssbb/items/b55ca899e0d5ce6ce963
2.pip（使用したのはVer.21.2.4 ）
　他のツールをダウンロードする際に使うツールです。
（python3系ではバージョン3.4以降であれば、pythonのインストールと共にpipもインストールされます。）
（参考）https://gammasoft.jp/python/python-library-install/
3.OpenCV（使用したのはVer.4.5.3 ）
　画像系処理するためのライブラリです。
（参考）https://qiita.com/ideagear/items/3f0807b7bde05aa18240
4.PyTorch（使用したのはVer.1.9.1+cpu ）
　DeepLearningのライブラリです。__CPUだけでやるため、Compute PlatformはCPUにしました。__
（参考）https://qiita.com/thinknee/items/0c0c466928356c9a55af
5.YOLOV5（使用したのはVer.5.0 ）
　物体検出のアルゴリズムです。
（参考）https://github.com/ultralytics/yolov5
5.yolov5s.pt
　使用する学習済みモデルです。
YOLOV5のフォルダで、例えば以下のコードを実行すると自動でダウンロードされます。
`detect.py --source data/images/bus.jpg --weights yolov5s.pt`
　次に組むpyファイルと同じフォルダに置きます。
　（このptファイルは、pyファイルを実行したときになければ、自動でダウンロードされます。）

###3.pyファイルの作成
 組んだコードは次のとおりです。

~~~python
import sys
import cv2
import torch

#--- 検出する際のモデルを読込 ---
#model = torch.hub.load('ultralytics/yolov5','yolov5s')#--- webのyolov5sを使用
model = torch.hub.load("../yolov5",'yolov5s',source='local')#--- localのyolov5sを使用

#--- 検出の設定 ---
model.conf = 0.5 #--- 検出の下限値（<1）。設定しなければすべて検出
model.classes = [0] #--- 0:person クラスだけ検出する。設定しなければすべて検出
#print(model.names) #--- （参考）クラスの一覧をコンソールに表示

#--- 映像の読込元指定 ---
#camera = cv2.VideoCapture("../pytorch_yolov3/data/sample.avi")#--- localの動画ファイルを指定
camera = cv2.VideoCapture(0)                #--- カメラ：Ch.(ここでは0)を指定


#--- 画像のこの位置より左で検出したら、ヒットとするヒットエリアのためのパラメータ ---
pos_x = 240

while True:

#--- 画像の取得 ---
#  imgs = 'https://ultralytics.com/images/bus.jpg'#--- webのイメージファイルを画像として取得
#  imgs = ["../pytorch_yolov3/data/dog.png"] #--- localのイメージファイルを画像として取得
  ret, imgs = camera.read()              #--- 映像から１フレームを画像として取得

#--- 推定の検出結果を取得 ---
#  results = model(imgs) #--- サイズを指定しない場合は640ピクセルの画像にして処理
  results = model(imgs, size=160) #--- 160ピクセルの画像にして処理

#--- 出力 ---
#--- 検出結果を画像に描画して表示 ---
  #--- 各検出について
  for *box, conf, cls in results.xyxy[0]:  # xyxy, confidence, class

      #--- クラス名と信頼度を文字列変数に代入
      s = model.names[int(cls)]+":"+'{:.1f}'.format(float(conf)*100)

      #--- ヒットしたかどうかで枠色（cc）と文字色（cc2）の指定
      if int(box[0])>pos_x :
        cc = (255,255,0)
        cc2 = (128,0,0)
      else:
        cc = (0,255,255)
        cc2 = (0,128,128)

      #--- 枠描画
      cv2.rectangle(
          imgs,
          (int(box[0]), int(box[1])),
          (int(box[2]), int(box[3])),
          color=cc,
          thickness=2,
          )
      #--- 文字枠と文字列描画
      cv2.rectangle(imgs, (int(box[0]), int(box[1])-20), (int(box[0])+len(s)*10, int(box[1])), cc, -1)
      cv2.putText(imgs, s, (int(box[0]), int(box[1])-5), cv2.FONT_HERSHEY_PLAIN, 1, cc2, 1, cv2.LINE_AA)

  #--- ヒットエリアのラインを描画
  cv2.line(imgs, (pos_x, 0), (pos_x, 640), (128,128,128), 3)

  #--- 描画した画像を表示
  cv2.imshow('color',imgs)

#--- （参考）yolo標準機能を使った出力 ---
#  results.show()#--- yolo標準の画面表示
#  results.print()#--- yolo標準のコンソール表示

#--- （参考）yolo標準の画面を画像取得してopencvで表示 ---
#  pics = results.render()
#  pic = pics[0]
#  cv2.imshow('color',pic)

  #--- 「q」キー操作があればwhileループを抜ける ---
  if cv2.waitKey(1) & 0xFF == ord('q'):
    break
~~~

###4.説明

####1. ローカル化  
 　以下のhub関数のコードのところを1行目から2行目のように書き換え、ローカル処理にしました。

~~~python
#model = torch.hub.load('ultralytics/yolov5','yolov5s')#--- webのyolov5sを使用
model = torch.hub.load("../yolov5",'yolov5s',source='local')#--- localのyolov5sを使用
~~~


####2. PCカメラから映像取り込み  
　以下のコードのところを1行目から2行目のように書き換え、カメラ画像を取り込むようにしました。

~~~python
#camera = cv2.VideoCapture("../pytorch_yolov3/data/sample.avi")#--- localの動画ファイルを指定
camera = cv2.VideoCapture(0)                #--- カメラ：Ch.(ここでは0)を指定
~~~


####3. 推定検出の処理軽量化  
 - 正確性は劣るが軽量なモデル__yolov5s__を使用  
 　以下のコードで__yolov5s__を使うようにしました。

~~~python
model = torch.hub.load("../yolov5",'yolov5s',source='local')#--- localのyolov5sを使用
~~~
　YOLOV5のモデルには、yolov5n、yolov5s、yolov5m、yolov5l、yolov5xなどがあります。
（参考）https://github.com/ultralytics/yolov5/releases


 - 推定時の画像を小さくして軽量化  
　以下のコードのところを1行目から2行目のように書き換え、処理画像を荒く小さくしました。

~~~python
#  results = model(imgs) #--- サイズを指定しない場合は640ピクセルの画像にして処理
  results = model(imgs, size=160) #--- 160ピクセルの画像にして処理
~~~


####4. 検出結果の変数取得  
　以下のコードで各検出の変数をfor文で取得しました。

~~~python
  for *box, conf, cls in results.xyxy[0]:  # xyxy, confidence, class
~~~
　変数種類とこのコードにて代入される変数名の対応は下表のとおりです。

|変数種類|変数名|
|:--:|:--:|
|矩形の左上角のX座標|box[0]|
|矩形の左上角のY座標|box[1]|
|矩形の右下角のX座標|box[2]|
|矩形の右下角のY座標|box[3]|
|クラスの番号|cls|
|推定の信頼度|conf|

　これらの変数を利用して、後処理を変えることができます。
今回はヒットエリア（画面の左側）で検出したものは枠の色を変更するようにしました。

###5.結果
　カメラ映像でも遅延が気にならない速さで処理できました。
ちなみに、`results = model(imgs, size=160)`のところで`size=160`を`size=80`にするともっと速く処理できますが、検出ミスがひどくなりました。逆に`size=320`にすると遅延が気になるところまで遅くなりました。

#参考

参考コード
https://github.com/ultralytics/yolov5/issues/36