BFT名古屋 TECH BLOG

日々の業務で得た知識を所属するエンジニアたちがアウトプットしていきます。

【機械学習】Raspberry Pi+SSD_Kerasで物体検出してみた

こんにちは!BFT名古屋支店の猫です。梅雨が明けたら一気に暑くなって溶けてしまいました。
今日はラズパイで物体検出をしてみた話です! ながいよ!

機械学習,画像認識,物体検出,tensorflow,keras

はじめに

ラズパイで物体検出したいな~と思ってググった(死語?)ところ、物体検出の手法にはR-CNN、YOLO、SSDの3つがよく使われていることが分かりました。
私は機械学習ド素人のため、3つのうちで「やってみた」系の記事が多く見つかった SSD ( Single Shot MultiBox Detector ) の学習済みモデルを使ってまずは物体検出を体験してみることに。
今回はその環境構築の手順と検証結果をご紹介したいと思います。

SSDがどのようなアルゴリズムなのかについては、猫には説明が難しいため分かりやすく説明してくださっているサイトを参考に貼っておきます。
ソースは rykov8 さんが公開されている ssd_keras を使用しました。
github.com

使用環境

Raspberry Pi

$ uname -a
Linux raspberrypi 5.10.17-v7l+ #1421 SMP Thu May 27 14:00:13 BST 2021 armv7l GNU/Linux
$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description:    Raspbian GNU/Linux 10 (buster)
Release:        10
Codename:       buster

Webカメラ

パッケージ等

version
Python 3.7.3
pip 21.1.3
tensorflow 1.15.0
Keras 1.2.2
numpy 1.21.1
matplotlib 3.4.2
scipy 1.1.0
opencv-python 4.5.3.56

前提

  • Raspberry Pi にOSがインストールされていること
  • Raspberry Pi がインターネットに接続していること
  • 実行ユーザがsudo権限を持っていること

環境構築~物体検出実行手順

1.環境構築

まずはTensorflow+Kerasの環境を構築していきます。
ssd_kerasはtensorflow1系でしか動かないため、tensorflow1.15.0とそれに合わせたパッケージをインストールしました。
(tensorflow2系で動くように修正した!という方を真似してやってみたのですが上手くいかず断念しました)

また、pipでtensorflowをインストールしようとするとtensorflow1.14.0までしかインストールできなかったので、tensorflow1.15.0をインストールするためにPINT0309 さんのビルド済パッケージを使用しました。 手順1-2,1-3は下記サイトのUsageを参考にしています。

github.com

1-1.パッケージ一覧・パッケージアップデート

$ sudo apt update
$ sudo apt upgrade

1-2.Tensorflow前提パッケージインストール&余分なパッケージをアンインストール

Usageの手順のままだと手順1-3.で ERROR: Cannot uninstall 'wrapt'. It is a distutils installed project and thus we cannot accurately determine which files belong to it which would lead to only a partial uninstall. というエラーが出るため、wraptのアンインストールを追加しています。
Kerasやmatplotlibのインストールは一気にやるより一つずつ実行する方が成功率高めでした。

$ sudo apt install python3-dev python3-pip python3-venv
$ sudo apt-get install -y libhdf5-dev libc-ares-dev libeigen3-dev gcc gfortran libgfortran5 \
                          libatlas3-base libatlas-base-dev libopenblas-dev libopenblas-base libblas-dev \
                          liblapack-dev cython3 openmpi-bin libopenmpi-dev libatlas-base-dev python3-dev
$ sudo apt-get install python3-scipy
$ sudo apt-get purge python3-wrapt
$ sudo apt autoremove
$ sudo pip3 install pip --upgrade
$ sudo pip3 install keras_applications==1.0.8 --no-deps
$ sudo pip3 install keras_preprocessing==1.1.0 --no-deps
$ sudo pip3 install h5py==2.9.0
$ sudo pip3 install pybind11
$ sudo pip3 install -U --user six wheel mock

1-3.Tensorflowインストール

gitからファイルをダウンロードするのでお好みでディレクトリを作成するなどしてください。
実行の際は$ wget ~の対象ファイル名をご自身の環境に合わせて書き換えてください。

$ mkdir /tmp/tensorflow1.15  #お好みで
$ cd /tmp/tensorflow1.15     #お好みで
$ sudo pip3 uninstall tensorflow
$ wget "https://raw.githubusercontent.com/PINTO0309/Tensorflow-bin/master/tensorflow-1.15.0-cp37-cp37m-linux_armv7l_download.sh"
$ sudo chmod +x tensorflow-1.15.0-cp37-cp37m-linux_armv7l_download.sh
$ ./tensorflow-1.15.0-cp37-cp37m-linux_armv7l_download.sh
$ sudo pip3  install tensorflow-1.15.0-cp37-cp37m-linux_armv7l.whl

1-4.その他SSD_Kerasに必要なパッケージインストール

$ sudo pip3 install -U Keras==1.2.2
$ sudo pip3 install -U opencv-python
$ sudo pip3 install -U matplotlib
$ sudo pip3 install -U numpy

2.SSD_Kerasの準備

続いてSSD_Kerasの実行に必要なソースコードたちを配置していきます。
ソースコードや重みファイルの配置はラズパイ上で直接ダウンロードしても、ご自身のPCでダウンロードしたものをラズパイに配置しても大丈夫です。お好みの方法で配置してください。
今回はラズパイ上でダウンロードする手順を記載しています。

2-1.ソースコードダウンロード

gitからファイルをダウンロードするのでお好みでworkディレクトリを作成するなどしてください。

$ sudo apt-get install git
$ mkdir /path/to/work/directory  #お好みで
$ cd /path/to/work/directory     #お好みで
$ git clone https://github.com/rykov8/ssd_keras.git

gitコマンドを実行したディレクトリ下にssd_kerasディレクトリが作成されていることを確認します。

$ ls -la ./ssd_keras/

total 207084
drwxrwxrwx 1 user_name user_name       512 Jul  6 11:44 .
drwxrwxrwx 1 user_name user_name       512 Jul 17 11:31 ..
drwxrwxrwx 1 user_name user_name       512 Jun 23 09:27 .git
-rwxrwxrwx 1 user_name user_name      1070 Jun 23 09:27 .gitignore
drwxrwxrwx 1 user_name user_name       512 Jun 23 13:48 .ipynb_checkpoints
-rwxrwxrwx 1 user_name user_name         6 Jul  2 14:00 .python-version
・
・
・

2-2.重みファイルダウンロード

ブラウザでこちらのサイトにアクセスし、[ here ]をクリックします (画像1)。

f:id:bftnagoya:20210721175738p:plain
画像1:重みファイルのダウンロード1

リンク先のページで[ weights_SSD300.hdf5 ]を選択し、[ … ]>[ ダウンロード ]>[ 標準ダウンロード ]をクリックします (画像2)。(結構大きいファイルなので、お好みでZIPダウンロードを選択してください。その場合は解凍してから使用してください。)

f:id:bftnagoya:20210721175937p:plain
画像2:重みファイルのダウンロード2

ダウンロードした重みファイルをssd_kerasディレクトリ下に配置します。

2-3.実行プログラムの作成

SSD_Kerasには物体検出お試し用の「SSD.ipynb」というソースコードが用意されています。
「.ipynb」はJupyterというツールで実行する際のファイル形式ですが、今回はJupyterを使用しないため、こちらのコード (SSD.ipynb) をmain.py (任意の名前のpyファイル) にコピー&ペーストし、ssd_kerasディレクトリ下に配置します。

その際%matplotlib inline%%timeはJupyter特有の書き方ですのでコメントアウトします。
また、今回は最終行の標準出力させる処理をファイル出力に変更しました。

- plt.show()

+ fname = 'recognized_' + str(i) + '.jpg'
+ plt.savefig(fname)

main.py 全文はこちら

3.物体検出実行

さあ、いよいよ実行するときがやってきました! ssd_kerasディレクトリで以下のコマンドを実行すると……

$ python3 main.py

f:id:bftnagoya:20210721213423j:plain:w500
画像3:サンプル画像の検出結果1

おお~!!

f:id:bftnagoya:20210721213505j:plain:w500
画像4:サンプル画像の検出結果2

おお~??

画像4のPersonが謎ですが、一応動いているようです!

Webカメラで撮影した画像で検出してみた

ついでにWebカメラで撮影した画像でも物体検出を行ってみました!

f:id:bftnagoya:20210721220234j:plain
画像5:Webカメラで取得した画像の検出結果1
真ん中の椅子だけ検出できればいいと思っていましたが、他の椅子もちゃんと検出してくれました。

f:id:bftnagoya:20210721220410j:plain
画像6:Webカメラで取得した画像の検出結果2
腰~首しか見えていなくても人だと検出できました!すごい!
座面と足しか見えていなくても椅子だと検出できました!すごいすごい!

f:id:bftnagoya:20210721221000j:plain
画像7:Webカメラで取得した画像の検出結果3

画角は何?って感じですが、遠くのモニターもちゃんと検出してます!すごい!あんたえらいよ~!

~ そしてその日の夜 ~

f:id:bftnagoya:20210721221300j:plain
画像8:Webカメラで取得した画像の検出結果4

何?!誰もいないはずなのに人が検出されてる!!こわ!!





おわりに

環境構築は苦労しましたが、オープンソースだけでそれなりの精度の物体検出をすることができました!(歓喜)
最後の暗闇Human検出は暗いと精度が下がってしまうということなんでしょうか……? 引き続き調査・検証していきたいと思います。

今回の 遊び 検証で使用したSSD+Kerasによる物体検出は、現在BFT名古屋支店で作っている「混雑状況可視化サービス」で使用する予定です。
混雑状況可視化サービスで使用する他の技術については山口さんが記事にしてくださっているので、そちらもぜひご覧ください!

最後まで読んでくださりありがとうございました!

「混雑状況可視化サービス」の関連記事

bftnagoya.hateblo.jp bftnagoya.hateblo.jp

参考

avinton.com techblog.cccmk.co.jp qiita.com

メインプログラム

以下 main.py の全文です。

import cv2
import keras
from keras.applications.imagenet_utils import preprocess_input
from keras.backend.tensorflow_backend import set_session
from keras.models import Model
from keras.preprocessing import image
import matplotlib.pyplot as plt
import numpy as np
from scipy.misc import imread
import tensorflow as tf

from ssd import SSD300
from ssd_utils import BBoxUtility

#%matplotlib inline
plt.rcParams['figure.figsize'] = (8, 8)
plt.rcParams['image.interpolation'] = 'nearest'

np.set_printoptions(suppress=True)

config = tf.ConfigProto()
config.gpu_options.per_process_gpu_memory_fraction = 0.45
set_session(tf.Session(config=config))

voc_classes = ['Aeroplane', 'Bicycle', 'Bird', 'Boat', 'Bottle',
               'Bus', 'Car', 'Cat', 'Chair', 'Cow', 'Diningtable',
               'Dog', 'Horse','Motorbike', 'Person', 'Pottedplant',
               'Sheep', 'Sofa', 'Train', 'Tvmonitor']
NUM_CLASSES = len(voc_classes) + 1

input_shape=(300, 300, 3)
model = SSD300(input_shape, num_classes=NUM_CLASSES)
model.load_weights('weights_SSD300.hdf5', by_name=True)
bbox_util = BBoxUtility(NUM_CLASSES)

inputs = []
images = []
img_path = './pics/fish-bike.jpg'
img = image.load_img(img_path, target_size=(300, 300))
img = image.img_to_array(img)
images.append(imread(img_path))
inputs.append(img.copy())
img_path = './pics/cat.jpg'
img = image.load_img(img_path, target_size=(300, 300))
img = image.img_to_array(img)
images.append(imread(img_path))
inputs.append(img.copy())
img_path = './pics/boys.jpg'
img = image.load_img(img_path, target_size=(300, 300))
img = image.img_to_array(img)
images.append(imread(img_path))
inputs.append(img.copy())
img_path = './pics/car_cat.jpg'
img = image.load_img(img_path, target_size=(300, 300))
img = image.img_to_array(img)
images.append(imread(img_path))
inputs.append(img.copy())
img_path = './pics/car_cat2.jpg'
img = image.load_img(img_path, target_size=(300, 300))
img = image.img_to_array(img)
images.append(imread(img_path))
inputs.append(img.copy())
inputs = preprocess_input(np.array(inputs))

preds = model.predict(inputs, batch_size=1, verbose=1)

results = bbox_util.detection_out(preds)

#%%time
a = model.predict(inputs, batch_size=1)
b = bbox_util.detection_out(preds)

for i, img in enumerate(images):
    # Parse the outputs.
    det_label = results[i][:, 0]
    det_conf = results[i][:, 1]
    det_xmin = results[i][:, 2]
    det_ymin = results[i][:, 3]
    det_xmax = results[i][:, 4]
    det_ymax = results[i][:, 5]

    # Get detections with confidence higher than 0.6.
    top_indices = [i for i, conf in enumerate(det_conf) if conf >= 0.6]

    top_conf = det_conf[top_indices]
    top_label_indices = det_label[top_indices].tolist()
    top_xmin = det_xmin[top_indices]
    top_ymin = det_ymin[top_indices]
    top_xmax = det_xmax[top_indices]
    top_ymax = det_ymax[top_indices]

    colors = plt.cm.hsv(np.linspace(0, 1, 21)).tolist()

    plt.imshow(img / 255.)
    currentAxis = plt.gca()

    for i in range(top_conf.shape[0]):
        xmin = int(round(top_xmin[i] * img.shape[1]))
        ymin = int(round(top_ymin[i] * img.shape[0]))
        xmax = int(round(top_xmax[i] * img.shape[1]))
        ymax = int(round(top_ymax[i] * img.shape[0]))
        score = top_conf[i]
        label = int(top_label_indices[i])
        label_name = voc_classes[label - 1]
        display_txt = '{:0.2f}, {}'.format(score, label_name)
        coords = (xmin, ymin), xmax-xmin+1, ymax-ymin+1
        color = colors[label]
        currentAxis.add_patch(plt.Rectangle(*coords, fill=False, edgecolor=color, linewidth=2))
        currentAxis.text(xmin, ymin, display_txt, bbox={'facecolor':color, 'alpha':0.5})

    #plt.show()
    fname = 'recognized' + str(i) + '.jpg'
    plt.savefig(fname)