背景
OpenCVとは、画像処理機能を提供してくれるライブラリです。バージョン3.3からDNN(deep neural network: 多層ニューラルネットワーク)モジュールが追加され、学習済みデータを利用した物体認識ができるようになりました。
そのDNNモジュールを利用して、MobileNetという、携帯端末で素早く動くことを目標にして作られているDNNを使って物体認識をしてみました。
このMobileNetは1000種類の物体を画像認識できるように作られていて、画像を処理すると1000種類それぞれの物体が画像内に存在する確率が出力されます。
今回のプログラムでは、その確率が最も高いと判断した物体名をログに出力します。
(処理速度はかないませんが、ideinさんのデモの真似です。)
OpenCVはいくつかのプログラミング言語をサポートしているので、今回は比較的少ない記述量でプログラムを書けるPythonを利用しました。
プログラムを書いてDNNモジュールを呼び出すのは早々とできたのですが、学習済みデータの用意やパラメータの設定などに時間がかかりました。
備忘録を兼ねて、自分のやった内容を共有します。
全体像
- 使ったもの
- プログラムの説明
- まとめ
使ったもの
python3向けのOpenCVバージョン3.3(またはそれ以後)がインストールされたPC
Ubuntu(17.10)なら下記のコマンドでインストールできます。sudo apt install python3-opencv
aptコマンドでpython3-opencvが無い(Raspbianなど)場合、もしくはaptでインストールできるOpenCVのバージョンが古い場合などは、pipでインストールできることがあります。
sudo apt install pip3 sudo pip3 install opencv_python sudo apt install libcblas-dev libatlas3-base
Raspbian Liteにpipでインストールした場合は、上記のコマンドに加えて下記のコマンドも必要かもしれません。
sudo apt install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev sudo apt install python3-dev python3-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev sudo apt install libcblas-dev libatlas3-base sudo apt install libgtk-3-dev libilmbase-dev libopenexr-dev libgstreamer1.0-dev
pipでも3.3未満のバージョンしかインストールできない場合は、ソースコードからビルドする必要があります。
しかし、ビルドの設定は環境によって異なるので、ここでは説明しません。
インストールできたら、下記のコマンドでOpenCVのバージョンを確認できます。
python3 import cv2 cv2.__version__
自分の環境では、バージョン3.3.0をインストールできました。
webカメラ
PCに内蔵されたカメラでも、USBカメラでも、Raspberry PiならPicameraでも、動くことを確認しました。今回のプログラムの画像取り込みはOpenCVの機能を利用するため、Picameraを使う場合は、raspi-configでカメラを有効にした上で、v4l2デバイスとして認識するコマンドを実行するか、常時v4l2デバイスとしてマウントする設定を行ってください。
MobileNetの学習済みデータ
下記のリポジトリから、CaffeModel形式のMobileNet v2のデータをいただきました。shicai/MobileNet-Caffe
プログラムの説明
下記のプログラムで、MobileNetを利用した画像認識を行いました。
mobilenet_scan_camera.py
import argparse import cv2 from cv2 import dnn import numpy as np import time inWidth = 224 inHeight = 224 WHRatio = inWidth / float(inHeight) inScaleFactor = 0.017 meanVal = (103.94, 116.78, 123.68) prevFrameTime = None currentFrameTime = None if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--video", help="number of video device", default=0) parser.add_argument("--prototxt", default="mobilenet_v2_deploy.prototxt") parser.add_argument("--caffemodel", default="mobilenet_v2.caffemodel") parser.add_argument("--classNames", default="synset.txt") parser.add_argument("--preview", default=True) args = parser.parse_args() net = dnn.readNetFromCaffe(args.prototxt, args.caffemodel) cap = cv2.VideoCapture(args.video) f = open(args.classNames, 'r') classNames = f.readlines() showPreview = (args.preview == True or args.preview == "True" or args.preview == "true") while True: ret, frame = cap.read() rgbFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) blob = dnn.blobFromImage(rgbFrame, inScaleFactor, (inWidth, inHeight), meanVal) net.setInput(blob) detections = net.forward() maxClassId = 0 maxClassPoint = 0; for i in range(detections.shape[1]): classPoint = detections[0, i, 0, 0] if (classPoint > maxClassPoint): maxClassId = i maxClassPoint = classPoint className = classNames[maxClassId] print("class id: ", maxClassId) print("class point: ", maxClassPoint) print("name: ", className) prevFrameTime = currentFrameTime currentFrameTime = time.time() if (prevFrameTime != None): print(1.0 / (currentFrameTime - prevFrameTime), "fps") if (showPreview): font = cv2.FONT_HERSHEY_SIMPLEX size = 1 color = (255,255,255) weight = 2 cv2.putText(frame, className, (10, 30), font, size, color, weight) cv2.putText(frame, str(maxClassPoint), (10, 60), font, size, color, weight) cv2.imshow("detections", frame) if cv2.waitKey(1) >= 0: break
ところどころ解説します。
MobileNetの学習済みデータとして、実行時の引数で指定するファイル名を変えられる形で、下記の3つをファイルを読み込んでいます。
- mobilenet_v2_deploy.prototxt
- mobilenet_v2.caffemodel
- synset.txt
parser = argparse.ArgumentParser() parser.add_argument("--prototxt", default="mobilenet_v2_deploy.prototxt") parser.add_argument("--caffemodel", default="mobilenet_v2.caffemodel") parser.add_argument("--classNames", default="synset.txt") net = dnn.readNetFromCaffe(args.prototxt, args.caffemodel) f = open(args.classNames, 'r') classNames = f.readlines()
MobileNetのprototxtに記載されている数値に応じて、判別に利用する値を設定しています。
name: "MOBILENET_V2" # transform_param { # scale: 0.017 # mirror: false # crop_size: 224 # mean_value: [103.94,116.78,123.68] # } input: "data" input_dim: 1 input_dim: 3 input_dim: 224 input_dim: 224 ..
scaleやmean_valが何なのかよく分かっていませんが、それどおりに記述します。
input_dimは1, 3, 224, 224ということで、1つ目が何を意味するのかは分かりませんが、3次元(RGB)の224x224pxの画像を処理できるということだと思います。
動かしてみたところ、入力画像が224x224px以上の大きさであれば、画像内の物体認識を行ってくれるようです。
inWidth = 224 inHeight = 224 inScaleFactor = 0.017 meanVal = (103.94, 116.78, 123.68)
OpenCVのVidwoCaptureで取得できる画像データはBGR形式なので、そのまま判別に使うと精度が悪くなります。
そのため、mobileNetにはRGB形式に変換したデータを渡しています。
ret, frame = cap.read() rgbFrame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) blob = dnn.blobFromImage(rgbFrame, inScaleFactor, (inWidth, inHeight), meanVal)
MobileNetは藩閥可能な1000種類それぞれの正答率を出力するので、最も値が大きいidを取得し、それの名前をログに出力しています。
maxClassId = 0 maxClassPoint = 0; for i in range(detections.shape[1]): classPoint = detections[0, i, 0, 0] if (classPoint > maxClassPoint): maxClassId = i maxClassPoint = classPoint className = classNames[maxClassId] print("class id: ", maxClassId) print("class point: ", maxClassPoint) print("name: ", className)
上記のプログラムはMobileNetの学習済みデータと一緒にgithubで公開しているので、下記のようなコマンドで実行できます。
sudo apt install git git clone git@github.com:asukiaaa/py_opencv_mobilenet_practice.git cd py_opencv_mobilenet_practice python3 mobilenet_scan_camera.py
カセットコンロの缶を見せるとヘアスプレーと認識してくれました。
Raspberr Piをssh経由で操作するときなど、プレビューを出したくない場合は、--preview=falseのオプションを付けるとプレビュー無しで動かせます。
python3 mobilenet_scan_camera.py --preview=false
処理速度については、GPUを使えるようにしたノートPCだと15fps位、Raspberry Pi Zeroだと0.28fps位で判別を行えました。
まとめ
OpenCVのDNNをPythonで呼び出して、物体認識を行えました。何かの参考になれば嬉しいです。
この記事の内容を参考にして、Raspberry Pi Zeroを使って物体認識装置を作ってみました。
良かったらこちらもご覧ください。
Raspberry Pi ZeroとPicameraと0.96インチLCDで物体認識装置を作ってみた
参考
【Windows】【Python】OpenCV3.3.1のdnnモジュールサンプル(mobilenet_ssd_python.py)opencv/samples/dnn/mobilenet_ssd_accuracy.py
Deep Neural Networks (dnn module) | OpenCV 3.3
Failed to load caffe model in opencv 3.3
opencv3.3.0 with deep learning
models/research/slim
プログラムを学びたてですが、この記事を参考にしながら楽しませていただいております。
返信削除このプログラムの処理をSegmentationに変えることができれば動画のSegmentationもできるでしょうか?
楽しまれているようで嬉しいです。
削除segmentationが何を意味されているのか良くわからないですが(前者は動画の分割、後者は分類?)、動画をTensorflowで分類するのはどうしたらよいかということでしょうか?
動画を複数の画像に分解すれば、この記事の内容を応用できると思います。
動画の動きを分類したい場合は、自分もよく分かりません。
複数の画像を並べて一枚の画像を作るなどをしたら良いのかもしれませんが、どの程度分類できるかは試してみないと分かりません。
また、それ用の学習済みデータが必要になりますので、データの準備と学習から取り組む必要がありそうです。
このような回答でいかがでしょう?