JUGEMテーマ:趣味

JUGEMテーマ:電子工作

画像認識して画像に映っている物体の数をリアルタイムに数える

 

Webカメラの画像からリアルタイムにその場にいる人の数を数えるとか、
熊を検出するとか、鹿を検出するとかそう言った事が最近は出来るらしいです。

かなり進歩したもんですね。

技術的な要素としてはyoloとかOpenCVとかを使うと良いとの事で、
色々な所にハマりながらたどり着いたのがこちら。

https://cpp-learning.com/chainercv_yolo/
とっても素敵なページです。こういった事を惜しげもなく公開しているオーナーさんには感謝です。
今ページの記事は基本的に上記ページの再現になります。

上記ページによりますと、chainerからyoloが使用可能で、以下の機能分担になります。
 openCV : ビデオ入力
 ChainerCV : yoloを使用して画像認識
 openCV : 結果を画面出力

以下、環境構築から実際に物体を認識するまでの記録(自分用メモ)として残したいと思います。

 

■chainerの環境構築
※参考サイト
 https://cpp-learning.com/anaconda_windows_chainer/
手順は以下の通り
 ・Anacondaインストール
 ・Visual C++ Build Toolsインストール
 ・Chainerインストール
 ※「Visual C++ Build Tools」についてはChainerをインストールするのに必要との事。

・Anacondaインストール
 ダウンロード
  https://www.anaconda.com/download/#windows
  ここから WindowsのPython3.7のをダウンロードして
  画面の指示に従ってデフォルトでインストールする。

・Visual C++ Build Toolsインストール
 ダウンロード
  https://www.visualstudio.com/ja/downloads
  ここの下のほうから「Tools for Visual Studio 2017」をダウンロードして
  画面の指示に従ってデフォルトでインストールする。

・Chainerインストール
 anacondaの仮想環境を一つ追加してそこにChainerをインストールしていきます。
 スタートメニューからAnaconda→「anaconda prompt」を起動して以下のコマンドを投入していきます。
 conda create -n chainer Anaconda
 activate chainer
 pip install chainer
 pip install chainercv
 pip install opencv-python
 
■カメラ画像を画面にリアルタイム表示
ここでは画像の解析の前にカメラでから入力した画像をほぼそのまま画面に表示するプログラムになります。
WindowsなのでLogicoolのWEBカメラをUSB接続してそのまま画面出力するサンプルです。
画面の上部に現在のフレーム数とFPSを表示していますが、私に環境では30FPSで処理できているようです。
※参考サイト
 https://cpp-learning.com/python_opencv_video/
・ソース そのままパクっております。自分メモのため・・・汗;;;

 

Test_Cam.py

 

import argparse
import cv2
from timeit import default_timer as timer

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('video')
    args = parser.parse_args()

    if args.video == "0":
        vid = cv2.VideoCapture(0)
    else:
        vid = cv2.VideoCapture(args.video)
    if not vid.isOpened():
        raise ImportError("Couldn't open video file or webcam.")

    # Compute aspect ratio of video
    vidw = vid.get(cv2.CAP_PROP_FRAME_WIDTH)
    vidh = vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
    vidar = vidw / vidh
    print(vidw)
    print(vidh)

    accum_time = 0
    curr_fps = 0
    fps = "FPS: ??"
    prev_time = timer()

    frame_count = 1
    while True:
        ret, frame = vid.read()
        if ret == False:
            print("Done!")
            return

        # Resized
        im_size = (300, 300)
        resized = cv2.resize(frame, im_size)

        # =================================
        # Image Preprocessing
        # =================================

        # =================================
        # Main Processing
        result = resized.copy() # dummy
        # result = frame.copy() # no resize
        # =================================

        # Calculate FPS
        curr_time = timer()
        exec_time = curr_time - prev_time
        prev_time = curr_time
        accum_time = accum_time + exec_time
        curr_fps = curr_fps + 1
        if accum_time > 1:
            accum_time = accum_time - 1
            fps = "FPS:" + str(curr_fps)
            curr_fps = 0

        # Draw FPS in top right corner
        cv2.rectangle(result, (250, 0), (300, 17), (0, 0, 0), -1)
        cv2.putText(result, fps, (255, 10), 
        cv2.FONT_HERSHEY_SIMPLEX, 0.35, (255, 255, 255), 1)

        # Draw Frame Number
        cv2.rectangle(result, (0, 0), (50, 17), (0, 0, 0), -1)
        cv2.putText(result, str(frame_count), (0, 10), 
        cv2.FONT_HERSHEY_SIMPLEX, 0.35, (255, 255, 255), 1)

        # Output Result
        cv2.imshow("Result", result)

        # Stop Processing
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

        frame_count += 1

if __name__ == '__main__':
    main()

 

■カメラ画像表示の動作確認
動作確認
 WEBカメラからの画像の場合はパラメーターに0を指定
  python Test_Cam.py 0
 動画ファイルを指定する場合はパラメーターに動画ファイル名を指定
  python Test_Cam.py 動画ファイル名

 

■画像認識

後からわかったんですがyoloのVersion3の場合で最初から学習済みの物体は以下の20種類の様です。
つまりこれ以外はちゃんと認識されません。
 aeroplane
 bicycle
 bird
 boat
 bottle
 bus
 car
 cat
 chair
 cow
 diningtable
 dog
 horse
 motorbike
 person
 pottedplant
 sheep
 sofa
 train
 tvmonitor
とりあえずは人を認識できれば今のところはそれで良いかなーとは思います。
もっと認識を増やしたい場合は学習させればよいという事ですよね。

 

■ソース
※参考サイト
 https://cpp-learning.com/chainercv_yolo/
・ソース ほぼそのままパクっております。自分メモのため・・・汗;;;

 

Yolo_Chainer_Video.py

 

import time
import argparse
import matplotlib.pyplot as plt
import cv2
import numpy as np
from timeit import default_timer as timer
import chainer
from chainercv.datasets import voc_bbox_label_names, voc_semantic_segmentation_label_colors
from chainercv.links import YOLOv2
from chainercv.links import YOLOv3
from chainercv import utils
from chainercv.visualizations import vis_bbox

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '--model', choices=('yolo_v2', 'yolo_v3'),
        default='yolo_v3')
    parser.add_argument('--gpu', type=int, default=-1)
    parser.add_argument('--pretrained-model', default='voc0712')
    parser.add_argument('video')
    args = parser.parse_args()

    if args.model == 'yolo_v2':
        model = YOLOv2(
            n_fg_class=len(voc_bbox_label_names),
            pretrained_model=args.pretrained_model)
    elif args.model == 'yolo_v3':
        model = YOLOv3(
            n_fg_class=len(voc_bbox_label_names),
            pretrained_model=args.pretrained_model)

    for name in voc_bbox_label_names:
      print(name)

    if args.gpu >= 0:
        chainer.cuda.get_device_from_id(args.gpu).use()
        model.to_gpu()

    if args.video == "0":
        vid = cv2.VideoCapture(0)
    else:
        vid = cv2.VideoCapture(args.video)
    if not vid.isOpened():
        raise ImportError("Couldn't open video file or webcam.")

    # Compute aspect ratio of video
    vidw = vid.get(cv2.CAP_PROP_FRAME_WIDTH)
    vidh = vid.get(cv2.CAP_PROP_FRAME_HEIGHT)
    # vidar = vidw / vidh
    print(vidw)
    print(vidh)

    accum_time = 0
    curr_fps = 0
    fps = "FPS: ??"
    prev_time = timer()

    frame_count = 1
    while True:
        ret, frame = vid.read()
        if ret == False:
            time.sleep(5)
            print("Done!")
            return

        # BGR -> RGB
        rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

        # Result image
        result = frame.copy()

        # (H, W, C) -> (C, H, W)
        img = np.asarray(rgb, dtype = np.float32).transpose((2, 0, 1))

        # Object Detection
        bboxes, labels, scores = model.predict([img])
        bbox, label, score = bboxes[0], labels[0], scores[0]

        print("----------")
        nPerson = 0
        nBottle = 0
        if len(bbox) != 0:
            for i, bb in enumerate(bbox):
                # print(i)
                lb = label[i]
                conf = score[i].tolist()
                ymin = int(bb[0])
                xmin = int(bb[1])
                ymax = int(bb[2])
                xmax = int(bb[3])

                class_num = int(lb)

                # Draw box 1
                cv2.rectangle(result, (xmin, ymin), (xmax, ymax),
                voc_semantic_segmentation_label_colors[class_num], 2)

                # Draw box 2
                # cv2.rectangle(result, (xmin, ymin), (xmax, ymax), (0,255,0), 2)

                text = voc_bbox_label_names[class_num] + " " + ('%.2f' % conf)
                print(text)
                if(voc_bbox_label_names[class_num] == 'person'):
                  nPerson = nPerson + 1
                if(voc_bbox_label_names[class_num] == 'bottle'):
                  nBottle = nBottle + 1
                
                text_top = (xmin, ymin - 10)
                text_bot = (xmin + 80, ymin + 5)
                text_pos = (xmin + 5, ymin)

                # Draw label 1
                cv2.rectangle(result, text_top, text_bot, 
                voc_semantic_segmentation_label_colors[class_num], -1)
                cv2.putText(result, text, text_pos, 
                cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 0), 1)

                # Draw label 2
                # cv2.rectangle(result, text_top, text_bot, (255,255,255), -1)
                # cv2.putText(result, text, text_pos, 
                # cv2.FONT_HERSHEY_SIMPLEX, 0.35, (0, 0, 0), 1)
            print("==========")
            print("Number of people : " + str(nPerson))
            print("Number of bottle : " + str(nBottle))
        # Calculate FPS
        curr_time = timer()
        exec_time = curr_time - prev_time
        prev_time = curr_time
        accum_time = accum_time + exec_time
        curr_fps = curr_fps + 1
        if accum_time > 1:
            accum_time = accum_time - 1
            fps = "FPS:" + str(curr_fps)
            curr_fps = 0

        # Draw FPS in top right corner
        cv2.rectangle(result, (590, 0), (640, 17), (0, 0, 0), -1)
        cv2.putText(result, fps, (595, 10), 
        cv2.FONT_HERSHEY_SIMPLEX, 0.35, (255, 255, 255), 1)

        # Draw Frame Number
        cv2.rectangle(result, (0, 0), (50, 17), (0, 0, 0), -1)
        cv2.putText(result, str(frame_count), (0, 10), 
        cv2.FONT_HERSHEY_SIMPLEX, 0.35, (255, 255, 255), 1)

        # Output Result
        cv2.imshow("Yolo Result", result)

        # Stop Processing
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

        frame_count += 1

if __name__ == '__main__':
    main()

 

■解説
 ・OpenCVでカメラ画像を取得→frame
 ・frameの中身はBGRで入っているそうなので、Chainerで扱えるRGBに変換→rgb
 ・画像サイズとチャンネルの順番がOpenCV(H, W, C)とChainer(C, H, W)で異なる事と
  Chainerではnumpy配列のfloat32しか扱えないのでChainerで扱えるように変換→img
 ・結果出力ではOpenCVを使うのでframeをコピーしておく→result
 ・imgに対し物体検出(Yolo)を実施し、boxとlabelの算出結果を取得
 ・boxとlabelの結果をresultに描画して映像出力

 

■画像認識動作確認
動作確認
 WEBカメラからの画像の場合はパラメーターに0を指定
  python Yolo_Chainer_Video.py 0
 動画ファイルを指定する場合はパラメーターに動画ファイル名を指定
  python Yolo_Chainer_Video.py 動画ファイル名
 より細かい指定をしたい場合はyoloのバージョン、GPU指定、学習モデルなどの指定が可能
  python Yolo_Chainer_Video.py --model yolo_v3 --gpu 0 --pretrained-model voc0712 0
   --model  yoloのバージョン指定  YoloV3 または YoloV2
   --gpu  プロセッサ設定  CPUなら-1 GPUなら0
   --pretrained-model 学習モデル指定  voc0712など
   最後の数字 WEBカメラは0、動画ならファイル名

 

・性能について
 私の環境はCPU:Codei5-8400、2.8GHz RAM:32GByte、gpu無しなのですが
 1FPSの速度です。CPU使用率は100%に張り付きます。


・認識精度について
 私の手はcatとかdogとか間違われてしまったりしましたが、
 全体であれば私をpersonと認識してくれました。むしろ部分でもpersonなのね。という感じです。
 牛とか車とか学習済みのものは正確に認識してくれます。
 熊の写真とかであれば同じ動物の仲間のdogとかcow等と認識しておりまずまずの結果かと思います。