2019年12月3日火曜日

Raspberry Pi で YOLO v3-Tiny / YOLO v3 による物体検出を試してみよう

以下は、古くなったのでサポートを停止した情報です。代替ページとして「Raspberry Pi 5 でリアルタイムな姿勢推定と物体検出」をお勧めします。

1. 物体検出とは何か

本ページでは、Rapsberry Pi で「物体検出」を行う方法を紹介します。「物体検出」とは何かを知るには、下図の画像を見るのがわかりやすくて良いでしょう。


机の上にキーボード、カッター、カップ、マウスが置かれており、その写真に物体検出を適用した結果、物体の位置、大きさ、種類が表示されています。 図ではわかりにくいですが、検出された物体にそれぞれキーボード、ナイフ、カップ、マウスという名称が英語で記されています。

なお、「カッター」が「ナイフ」と検出されている理由は後に解説しますが、一言で言えば「学習データにカッターが含まれていないから」です。

物体検出には様々な方法がありますが、本ページで用いる物体検出は、本書で紹介した CNN (畳み込みニューラルネットワーク) の技術が使われたものです。
しかし、下図に示したように通常のCNNと物体検出では大きく異なる点があります。


通常のCNNでは、上図(A)のように画像中に一つの対象が記されており、その画像が属するクラス(「2」や「カップ」と言った分類先)が出力されます。

一方、物体検出では上図(B)のように、一枚の画像中に複数の物体が存在することがあり得ます。そして、それぞれの物体のクラスだけではなく位置や大きさも出力されねばなりません。

2. YOLOとは何か

この物体検出に CNN (畳み込みニューラルネットワーク) の考え方を応用した手法がいくつか提案されており、本ページで紹介するのはそのうちの一つである YOLO です。
YOLO(You Only Look Once、一度だけ見る)は 2016 年に J. Redmon らにより提案された手法です。 オリジナルの論文はこちらから入手できます。

You only look once とは、英語表現 You only live once(一度だけの人生)をもじったものと考えられます。「一度だけ見る」とは、YOLO では一枚の画像に対する認識に CNN の計算を一度だけ実行すれば良いことを示しています。
物体検出に CNN を用いる他の手法では、一枚の画像に対する認識時に何度も CNN の計算を行わねばならなかったことと対比しているのだと思われます。

通常の CNN の出力層ではソフトマックス関数により入力が属するクラスを出力します。詳細は省略しますが、YOLO では出力層に機械学習の回帰の考え方を適用し、物体の位置や大きさに関わる量も出力できるようにします。それにより、物体検出を可能にしているのです。

なお、執筆時点で YOLO にはバージョン 1 からバージョン 3 が存在します。本ページでは YOLO バージョン 3 を実行する方法を紹介します。

バージョンごとに性能が向上されている他に、学習時に用いられたデータが異なるという特徴があります。それにより、認識される物体の種類が異なるのです。
YOLO バージョン 1 ではクラス数 20 の Pascal VOC 2007 といううデータセットが用いられています。20 種類の物体を検出できるということです。その性能を向上させた YOLO バージョン 2 ではImageNet と COCO という 2 つのデータセットを組み合わせてクラス数 9000 の認識が可能な YOLO9000 というネットワークを実装しています。

そして、本ページで用いる用いた YOLO バージョン 3 では COCO というデータセットを用いてクラス数 80 の認識が可能です。80 種類のクラスをこちらでみることができます。

上で見た図で、カッターを「ナイフ」と認識しているのは「カッター」のデータがデータセットに含まれていなかったであると既に述べましたが、それは上のリンクにあるクラス一覧で確認することができます。

なお、これらの「学習済ネットワーク」から出発して新たなデータを学習させる「転移学習」という方法もありますが、本ページでは取り扱いません。

また、通常 YOLO で用いる CNN は、何十個もの畳み込み層と 2 個の全結合層を持ちます。これをそのまま Raspberry Pi の計算能力で実行するのはやや荷が重いといえます。そのため、本ページでは畳み込み層の数を十個程度に減らした YOLO の簡易版である Tiny YOLO の実行方法も紹介します。

Raspberry Pi 3までをお使いの方はこの Tiny YOLO を利用すると良いでしょう。 高速な Raspberry Pi 4 をお使いの方は通常の YOLO もあわせてチャレンジしてみましょう。

また、本ページの続編的なページとして、物体検出の高速化に関連する「Raspberry Pi + Coral USB Accelerator + TensorFlow Lite で物体検出と姿勢推定を試してみよう」もありますので合わせてご覧ください。Coral USB Acceleratorをお持ちでない場合も演習を実行できます。

3. Python3 用の TensorFlow と OpenCV のインストール

さて、YOLO および Tiny YOLO を実行するためには、Python3 用の TensorFlow および OpenCV のインストールが必要です。本書の補足ページの下記ページに従い、Python3 用の TensorFlow および OpenCV をインストールして下さい。 なお、本ページの内容を Anaconda で実行したい方は「本書の演習を Anaconda の Spyder で実行する方法」を参考にしてください。

さて、ここからは以下の流れで解説が進みます。 TensorFlow 1 系に比べて TensorFlow 2 系の解説は簡易的となっておりますのでご了承ください。

1. [TensorFlow 2 系] プログラムのダウンロードから実行まで

TensorFlow 2 系で行うための方法を記します。簡易的な解説となっていますのでご了承ください。

まず、プログラムをダウンロードするには下記のコマンドを実行して下さい。cedrickchee氏のプログラムに筆者が手を加えたファイルがダウンロードされます。TensorFlow 1 系とは別のプログラムを用いていることにもご注意ください。
git clone https://github.com/neuralassembly/tensorflow2-yolo-v3
プログラムのダウンロードが終わったら、下記コマンドを実行し、tensorflow2-yolo-v3 ディレクトリ(フォルダ)内に移動します。
cd tensorflow2-yolo-v3
そして、下記の4つのコマンドを実行し、YOLOv3用の重みをダウンロ―ドし、さらにそれを TensorFlow 用に変換します。前半 2 つが通常の YOLO 用のコマンド、後半 2 つが tiny YOLO 用のコマンドです。
wget https://pjreddie.com/media/files/yolov3.weights -O data/yolov3.weights
python3 convert.py

wget https://pjreddie.com/media/files/yolov3-tiny.weights -O data/yolov3-tiny.weights
python3 convert.py --weights ./data/yolov3-tiny.weights --output ./checkpoints/yolov3-tiny.tf --tiny
Raspberry Pi 4 登場以前の Raspberry Pi 3 などで、コマンド「python3 convert.py」実行時にエラーが出る場合、 「2. [TensorFlow 1 系] プログラムのダウンロードと準備」で解説されているスワップ領域の増加を試してみると良いかもしれません。

次に、静止画に対して物体検出を行うコマンドが下記の 2 つです。1 つ目のコマンドは、通常の YOLO を画像「./data/meme.jpg」に対して適用します。 「./data/meme.jpg」は、「現在のディレクトリ(./)にある data ディレクトリ(data/)にある画像 meme.jpg」という意味です。 同様に、2 つ目のコマンドは、tiny YOLO を画像「./data/street.jpg」に対して適用します。
どちらの場合も、検出結果が描かれた output.jpg というファイルが保存されます。
python3 detect.py --image ./data/meme.jpg

python3 detect.py --weights ./checkpoints/yolov3-tiny.tf --tiny --image ./data/street.jpg
最後に、ウェブカメラから取得した映像への物体検出は以下で行います。1 つ目が通常のYOLO、2 つ目が tiny YOLO 用のコマンドです。どちらも、終了するにはウインドウ上でキーボードの「q」をタイプしてください。
なお、Bullseye 64-bit で、Legacy Camera 有効 + カメラモジュールで使っている場合、カメラモジュールでの認識が可能です。 Bookworm 64-bit の場合カメラモジュールでの認識はできません。USBで接続するウェブカメラが必要です。その場合、detect.py の 57行目の「cap = cv2.VideoCapture(0)」のカッコ内の数字を 0 から 1 に変更する必要があるかもしれません。
python3 detect.py --webcam

python3 detect.py --weights ./checkpoints/yolov3-tiny.tf --tiny --webcam


2. [TensorFlow 1 系] プログラムのダウンロードと準備

ここからは、TensorFlow 1 系用の YOLO バージョン 3 (YOLOv3) のダウンロードと準備を行いましょう。
公式の YOLO は、darknet というライブラリでネットワークを実現しています。 それを別のディープラーニング用ライブラリで実行するためのプログラムを様々な方が公開しています。

ここでは、kcosta42 氏が公開しているプロクラムを用います。 kcosta42 氏のプログラムに、カメラ映像に対する物体検出機能を筆者が追加したプログラムをダウンロードします。 ターミナルを開き、下記のコマンドでプログラムをダウンロードしましょう。
git clone https://github.com/neuralassembly/Tensorflow-YOLOv3
次に、学習済のパラメーター(結合係数)ファイルをダウンロードして TensorFlow 用のファイルに変換します。下記の3つのコマンドを順に一つずつ実行します。
cd Tensorflow-YOLOv3

curl https://pjreddie.com/media/files/yolov3-tiny.weights > ./weights/yolov3-tiny.weights

python3 convert_weights.py --tiny
一つ目のコマンドはディレクトリの移動を、二つ目のコマンドははTiny YOLO用のパラメーターファイル (35MB程度) のダウンロードを表します。
三つ目のコマンドはファイル変換を行います。Pi 3 B+で 1 分 30 秒程度、Pi 4 B で40秒程度の時間がかかります。このときコンソールに大量の WARNING が出ますが、変換は適切に行われますので気にする必要はありません。

Raspberry Pi 4 をお使いの方で Tiny ではない通常の YOLO の実行もしたい方は、引き続き下記の2コマンドを実行してください。
curl https://pjreddie.com/media/files/yolov3.weights > ./weights/yolov3.weights

python3 convert_weights.py
それぞれ YOLO 用のパラメーターファイルのダウンロードと変換を行っています。Pi 4 B で 2 分程度の時間がかかります。
なお、パラメータファイルは250MB程度ありますので、SDカードの容量に注意してください。
なお、ファイルの変換には多くのメモリが必要とされるため、 Pi 3 B+までのRaspberry Piでは途中で強制終了してしまいます。

なお、2019.12末に試したところ、2つめのコマンドで Raspberry Pi 4 でもメモリに関するエラーが出て終了してしまいました(std::bad_alloc)。
これは、この時期にインストールされた tensorflow-1.14.0 に問題があるからのようです。
sudo pip3 install tensorflow==1.13.1
上記のコマンドにより tensorflow 1.13.1 にバージョンを落とすと上の変換コマンド「python3 convert_weights.py」はRaspberry Pi 4 メモリ 8GB 版と 4GB 版とでは問題なく実行でき、「Model Saved at "./weights/model.ckpt" 」と表示されて正常終了します。その場合、「3. プログラムの実行 (静止画の場合)」に進んでください。

なお、Raspberry Pi 4 メモリ 2GB 版ではメモリが足りず tensorflow-1.13.1 にバージョンを落としても「python3 convert_weights.py」コマンドに失敗することがあります。 その場合、SDカードの領域をメモリの一部として使う「スワップ」という領域のサイズを増やす必要があります。スワップ領域のデフォルトのサイズは 100MB であるので、これを 600MB に増やしてみましょう。 下記コマンドにより、スワップ領域のサイズを決めるファイル /etc/dphys-swapfile を管理者権限のテキストエディタで開きます。
sudo mousepad /etc/dphys-swapfile
下記の行がありますので、
CONF_SWAPSIZE=100
この数値 100 を下記のように 600 に変更します。
CONF_SWAPSIZE=600
変更したらテキストエディタでファイルを上書き保存してから、Raspberry Pi を再起動します。すると、スワップ領域の量が 600MB に増えており、変換コマンド「python3 convert_weights.py」が「Model Saved at "./weights/model.ckpt" 」と表示されて正常終了するようになります。
正常終了を確認したら、再び /etc/dphys-swapfile を管理者権限で開き、CONF_SWAPSIZE の値を 100 に戻して構いません。

以上が終わったら物体検出を実行してみましょう。

3. [TensorFlow 1 系] プログラムの実行 (静止画の場合)

まず、静止画に対する物体検出を試してみましょう。プログラムをダウンロードすることでユーザー pi のホームディレクトリに Tensorflow-YOLOv3 というディレクトリ(フォルダ)が出来ていますが、
その中の data ディレクトリ→ imagesディレクトリとたどった中にある person.jpg というファイルに対して物体検出をおこないます。あらかじめ、Raspberry Pi上のファイルマネージャで画像をダブルクリックしてどのような画像か確認しておきましょう。

そして、 Tensorflow-YOLOv3 というディレクトリに移動してからプログラムを実行します。 結合係数の変換からターミナルを閉じていなければそのまま実行してよいのですが、新たにターミナルを起動した場合はあらかじめ
cd Tensorflow-YOLOv3
というコマンドを実行し、ディレクトリを移動してからプログラムを実行します。

Tiny YOLO (YOLOv3-tiny) によりperson.jpgに対して物体検出を行うには下記のコマンドを実行します。
python3 detect.py --tiny image 0.5 0.5 ./data/images/person.jpg
Pi 3 B+で 45秒程度(パラメータファイルからのモデルの構築に36秒、入力から検出結果を得るのに9秒)、Pi 4 B で 24 秒程度(パラメータファイルからのモデルの構築に20秒、入力から検出結果を得るのに4秒)の時間待つと、結果が保存されます。
結果は、Tensorflow-YOLOv3 ディレクトリ内の detections ディレクトリにある image_output.png というファイルに保存されています(ファイル名は毎回固定です)。 下記のようなファイルが保存されているはずです。


犬に対する枠が2重に表示されていたり、馬が sheep と判定されているなど若干問題はありますが、Tinyではない通常の YOLO を用いるとこの問題はどちらも解決されます(その方法は後述します)。
なお、犬に対する枠が2重に表示されている問題は Tiny の範囲で解決できます。実行コマンド中の「0.5 0.5」のうち、一つ目の数字は「同じクラスの物体を示す枠が複数あって重なっているとき、重なりが大きい枠を除去する」という処理に使われます。 この数字を 0 から 1 の範囲で小さくすると(例えば 0.3)、犬の枠は一つになります。すなわち「python3 detect.py --tiny image 0.3 0.5 ./data/images/person.jpg」ですので興味のある方は試してみてください。

ここで、実行コマンドの中の「./data/images/person.jpg」について解説しておきましょう。これは物体検出を行う対象となる画像の位置を指定しています。このとき、下記の記号を覚えておくと便利です。

   .   ユーザーが現在いるディレクトリ
   /   ディレクトリの区切り

この表によると、「./data/images/person.jpg」は「ユーザーが現在いるディレクトリ内にあるdataディレクトリ→imagesディレクトリにあるperson.jpgという画像」という意味になります。 それ以外には下記の記号を覚えておくと良いでしょう。

   ..   ユーザーが現在いるディレクトリの一つ上のディレクトリ

この記号を用いると、例えば「../image.jpg」と書くことで「ユーザーが現在いるディレクトリの一つ上のディレクトリにあるimage.jpgという画像」という意味になります。

ここで学んだ知識を確認するため、別の画像に対しても Tiny YOLO で物体検出を行ってみましょう。本ページの冒頭に示した机の上にキーボード、カッター、カップ、マウスを置いた画像を試してみましょう。下記のコマンドで画像をダウンロードします。
wget https://raw.githubusercontent.com/neuralassembly/raspi/master/desk-image.jpg
ファイルマネージャで Tensorflow-YOLOv3 ディレクトリにダウンロードされた desk-image.jpg の存在を確認し、どんな画像か確認しておきましょう。 そして、下記のコマンドにより物体検出を実行します。
python3 detect.py --tiny image 0.5 0.5 ./desk-image.jpg
先ほど学んだように「./desk-image.jpg」は「現在のディレクトリにある desk-image.jpg という画像ファイル」という意味になるのでしたね。

実行結果は先程と同じく detections ディレクトリの image_output.png として保存されます。以下のような図になっているはずです。


本ページ冒頭の図と異なり、カッターが検出されていません。これは、物体かどうかを判定する基準が厳しいからです。実行コマンド中の「0.5 0.5」のうち二つ目の数字を0.2にすると、基準が緩くなり、カッターがナイフと判定されるようになります (基準は0から1の範囲の数値で定めます)。すなわち「python3 detect.py --tiny image 0.5 0.2 ./desk-image.jpg」です。このコマンドで本ページ冒頭の図と同じものが detections ディレクトリの image_output.png として保存されます。

さて、ここまで Tiny YOLO での物体検出を解説してきました。通常の YOLO で物体検出を行いたい場合は、コマンドから「--tiny」を除いて下記を実行します。Pi 3 B+までではパラメータファイルの変換に失敗するので、実行可能なのは Raspberry Pi 4 以降のみでしたね(メモリ 4GB 版と 2GB版とで動作を確認)。
python3 detect.py image 0.5 0.5 ./data/images/person.jpg
Pi 4 B で 1 分弱(パラメータファイルからのモデルの構築に45秒、入力から検出結果を得るのに8秒)待つと、これまで通り detections ディレクトリの image_output.png として保存されます。
認識率はこちらの通常の YOLO を用いることで格段に上がりますが、そのぶん認識結果を得るまでの時間がかかるようになっています。

4. [TensorFlow 1 系] プログラムの実行 (カメラの映像の場合)

最後に、Raspberry Pi に接続したカメラから得られた映像に対して物体検出を行う方法を紹介します。

カメラは、Raspberry Pi 公式のカメラモジュールおよび USB 接続のウェブカメラのどちらでも構いません。USB 接続のウェブカメラとしてこちらで動作確認したのは ロジクール社の C270 および C920 です。

本ページでは「××社の○○と言うカメラで動くか」という質問や「××社の○○と言うカメラで動くようにして欲しい」という要望には応えることができませんのでご了承ください。

実行方法は、静止画の場合と同様に Tensorflow-YOLOv3 ディレクトリに「cd Tensorflow-YOLOv3」コマンドで移動してから下記を実行します。

Tiny YOLOの場合はこちらです。
python3 detect.py --tiny webcam 0.5 0.5 
利用状況は下図のようになります。Pi 3 B+ では、約1秒に一回くらい映像が更新されましたが、5秒くらいの映像遅れがあるので実用は厳しいところです。
Pi 4 B では、1秒に複数回映像が更新され、2秒くらいの映像遅れがあります。



通常の YOLO の場合はコマンドから「--tiny」を除いた下記のコマンドを実行します。こちらは Raspberry Pi 4 でのみ実行できます(メモリ 4GB 版と 2GB 版とで動作を確認)。 こちらは Tiny YOLO よりも認識率は良いのですが、映像の遅れが25秒程度あるので使いどころは難しいところです。
python3 detect.py webcam 0.5 0.5 


8. まとめ

Raspberry Pi で YOLO v3-Tiny / YOLO v3 による物体検出を試してみました。カメラ映像に対する例を試してみたところ、実用するためには Raspberry Pi 4 + YOLO v3-Tiny くらいの動作速度は最低限欲しいところです。

また、YOLO v3-Tiny は認識精度が低いので、実用するにはなんらかの工夫が必要と思われます。

この物体検出の高速化に関して、「Raspberry Pi + Coral USB Accelerator + TensorFlow Lite で物体検出と姿勢推定を試してみよう」ページを作成しましたので、引き続きご覧ください。Coral USB Acceleratorをお持ちでない場合も演習を実行できます。

10 件のコメント:

  1. kcosta42 氏のREADMEに書かれているようにビデオの検出を行いたく

    python3 detect.py video 0.5 0.5 data/videos/champs-elysees.mp4

    のコマンドを実行しましたが以下のようにエラーが起きてしまいます。パソコン初心者で素人質問で申し訳ありませんが原因について何かご示唆いただければ幸いです。

    detect.py: error: unrecognized arguments: data/videos/champs-elysees.mp4

    返信削除
    返信
    1. ホームディレクトリの直下にTensorflow-YOLOv3ディレクトリがあるという前提で解説します。
      以下の2つのコマンドを順に実行すると、videoモードに対しても実行が可能になります。

      wget https://raw.githubusercontent.com/neuralassembly/Tensorflow-YOLOv3/master/detect.py

      mv detect.py ~/Tensorflow-YOLOv3/

      修正済の detect.py をダウンロードし、
      古いファイルを上書きする、というのがこの2コマンドでやっていることです。

      削除
    2. できるようになりました。ありがとうございました。

      削除
  2. YOLOv3にオリジナルのモデルを入れて判別する方法はありますか?

    返信削除
    返信
    1. 自分で用意した学習データを用いる、ということでしょうか?

      難しい内容となりますので本ページではサポート外です。

      ご理解ください。よろしくお願いします。

      削除
    2. YOLOv3に新しいカテゴリーとして81種類目として入れるという意味です。
      変なことを聞き誠に申し訳ございませんでした。お返、事感謝申し上げます。

      削除
  3. 上で動画の検出を行った方と同じコマンドを実行したのですが

    ValueError : The passed save_path is not a valid checkpoint: ./weights/model.ckpt

    とエラーが出ました。

    Takashi Kanamaruさんの返信の二つのコマンドは実行済みですが、実行したらdetect.pyに加えてdetect.py1が作られていてそれのせいなのかなとも思いましたが解消法がわかりませんでした。

    プログラミング初心者で申し訳ありませんが何か教えていただければ幸いです。

    返信削除
    返信
    1. tinyではないYOLOを試していらっしゃるようですが、そのためには重みの変換コマンド「python3 convert_weights.py」の実行に成功していなければなりません。

      そのためには、下記コマンドによりtensorflow のバージョンを1.14.0 から1.13.1に落とさねばなりません。

      sudo pip3 install tensorflow==1.13.1

      上記のコマンドにより tensorflow 1.13.1 にバージョンを落とすと上の変換コマンド「python3 convert_weights.py」はRaspberry Pi 4 メモリ 8GB 版と 4GB 版とでは問題なく実行でき、「Model Saved at "./weights/model.ckpt" 」と表示されて正常終了するようになります。

      ただし、Raspberry Pi 4 メモリ 2GB 版ではメモリが足りず tensorflow-1.13.1 にバージョンを落としても「python3 convert_weights.py」コマンドに失敗することがあります。 その場合、SDカードの領域をメモリの一部として使う「スワップ」という領域のサイズを増やす必要があります。

      詳細は、本ページ「5. プログラムの実行 (静止画の場合)」の直前あたりの箇所をご覧ください
      (本日更新しました)。

      削除
  4. personだけを検知するようにしたいのですがどこをいじればいいんでしょうか。
    またカメラで検知した情報をwebサイトに表示したいのですがどういった手順や方法があるか教えていただければ幸いです。この記事にあまり関係のない質問で申し訳ないです。

    返信削除
    返信
    1. core/utils.py というファイルの末尾にある draw_boxes_frame という関数において、
      クラス番号を表す cls が 0 のときのみ結果を描画すればよいのだろうと思います。

      クラス番号 0 が person を表す理由は、
      data/coco.names
      というテキストファイルにおいて、person が先頭(0 番目)
      に現れるからです。

      これ以上は、Pythonの文法の解説が必要となりますので、
      解説はこの程度とさせてください。

      また、カメラで検知した情報をwebサイトに表示するというのも、
      技術的なハードルが高くサポート外の内容となりますので、
      解説はできませんのでご了承ください。

      なお、人物を表示する、という内容に限定するならば、

      Raspberry Pi + Coral USB Accelerator + TensorFlow Lite で物体検出と姿勢推定を試してみよう
      https://mlbb1.blogspot.com/2020/01/raspberry-pi-coral-usb-accelerator.html

      末尾の「姿勢推定」の方が現実的ではあります。

      ただし、こちらもプログラムの内容についての解説はできませんのでご了承ください。

      削除