静止画でオブジェクト検出 【Raspberry Pi & Tensorflow Lite】

Machine Learning

Raspberry Pi にインストールした Tensorflow Lite のライブラリを使って、静止画1枚に対してオブジェクト検出をする Python のスクリプトを作ってみます。

以下の環境で動作を確認しています。
環境:
・ Raspberry Pi (bullseye)
・ OpenCV、Tensorflow Lite (object_detection)をインストール済み
・ 学習済みデータ(*.tflite)はインストール時のまま使用

背景 Tensorflow Lite のスクリプトを作ってみる

以前に、Tensorflow Lite のオブジェクト検出のスクリプトを Raspberry Pi にインストールする方法についてまとめています(下記の関連リンク参照)。
Raspberry Pi に USB カメラをつなぐと、カメラのライブ画像上で、物体検出のプログラムがすぐに動きます。

ところが、このスクリプトは USB カメラのライブ画像で動くサンプルとなっています。
写真など静止画を指定してオブジェクト検出をすることはできない(?)ようです。
そこで、静止画1枚に対しても物体検出のプログラムが動くよう Python のスクリプトを作り、まとめておくことにします。
ライブ画像ではオブジェクト検出は実際に機能しており、スクリプトも公開されているので、何とでもなるはずです。

手順

① あらかじめ事前に、Raspberry Pi に Tensorflow Lite のオブジェクト検出のフォルダを作成し(下記参照)、プログラムをインストールしておきます。インストールの手順は、関連リンクを参照してください。
例: /home/pi/TFLite1/object_detection1
※ インストールするフォルダを上記とします。インストールしたフォルダが上記と異なる場合は、以後、フォルダの位置を読み替え、必要によりプログラムを修正してください。
※ 正常に Tensorflow Lite のインストールが終わると、上記のフォルダに、以下のファイル等が入っていると思います。
・ detect.py (USB カメラをつなぐとライブ画像上で物体検出する Python のスクリプト)
・ utils.py (検出結果を描画するユーティリティ用のスクリプト )
・ efficientdet_lite0.tflite (Tensorflow Lite 用の学習済みファイル)
・ README.md
② ①のフォルダ内に、あらたに、detect_image1.py という名前でテキストファイルを作成し、末尾のスクリプトをコピー&ペーストして保存してください。
例: /home/pi/TFLite1/object_detection1/detect_image1.py
※ 末尾で “…1.py” などの付番を入れたファイル名や関数名などが、このサイトで独自に作っているものです。付番がついていないものが原則、公式サイトなどで配布されているファイル等としています。スクリプトをアレンジした場合は、付番を 2, 3 にして保存するなど自由に活用してください。
③ 以下を参考に、任意の場所にフォルダを作成し、画像を入れてください。
例: /home/pi/screen_shot1/image01/240120_120000.png
※ フォルダの場所と画像は、特に制約はありません。
※ 上記の事例はスクリーンショットを撮った画像を使ってオブジェクト認識をさせる例です(下記参照)。
④ ターミナルを起動し、以下を参考に、Python のスクリプトを実行してください。
コマンドの形式: “python [detect_image1.pyのパス] [画像のパス]
コマンドの例: python /home/pi/TFLite1/object_detection1/detect_image1.py /home/pi/screen_shot1/image01/240120_120000.png

→ オブジェクト検出の実行結果が表示されたら成功です!
表示画面上で任意のキーを押すと、プログラムが終了します。

※ オブジェクト検出は、画像によってはうまくいかないことがあります。画像をいくつか変えて試してみてください。
※ なお、上記のコマンドの例は、「ウェブサイトのスクリーンショットを自動化する 【Raspberry Pi 版】」で取得したネット上のライブカメラの画像に対して、オブジェクト検出をする事例としています。こういったスクリプトを複数組み合わせることで、例えば、定点観測をしているライブカメラの画像に対し、オブジェクト検出をする等が可能となります。

スクリプトの説明

・ スクリプトの冒頭で os, sys など、必要なモジュールをインポートしています。画像の変換に cv2(OpenCV)を使います。
・ from tflite_… となっているところで、Tensorflow Lite をインポートしています。
・ utils (utils.py) は、元の画像上に、物体検出の結果を矩形や文字で描くためのユーティリティです。Tensorflow Lite をインストールするとついてくると思います。
・ def get_model1() としたところは、機械学習の学習済みモデルのありかを返す関数です。Python のスクリプトと同じ場所にある学習済みモデル “efficientdet_lite0.tflite” のパスを返します。
・ def detection1() としたところが物体検出を行うメインの関数です。
学習済み教師モデル model1 と、物体検出を行う画像 image1 を引数で渡すと、画像を Tensorflow 用に色変換し、さらに、”テンソルイメージ” tsr_image1 に変換します。
物体検出を行う際のオプションの設定を option1, 2, 3 で作り、detector1 = … とした行でデテクターのオブジェクトを作ります。
このデテクターにテンソルイメージ tsr_image1 を流すことで、物体検出の結果 result1 を取得し、関数の戻り値として返します。
・ def object_counter0()、object_counter1() は、物体検出の結果 result1 を調べて、どういう物体をいくつ見つけたかをカウントする関数です。自作しています。
・ object_counter0( result1 ) 関数は、物体検出の結果 result1 を渡すと、見つかったオブジェクトの中から、欲しい情報を引き抜いてリストにして返します。
ここでは、見つかった物体の名前、スコア、矩形を描画する座標(4つ)が欲しいので、計6つの要素をリストにしてアペンドし、すべてのオブジェクトに対して調べた結果を返します。取り出す情報を変えたい場合は、この関数を修正してください。
・ object_counter1() は、見つかったオブジェクトがいくつあるかカウントする関数です。object_counter0() で、何が見つかったのか(person, person, car, …)がわかります。そこで、見つかった物体の数をカウントして返します。
途中でソートを入れているのは、多い順に並べるためです。
また、結果の表示のためには、カウント値などは最終的にすべて文字列に変換して結合したいです。そこで、lambda 関数と map を使って文字列への変換を行いました。for ループで個々、文字列にして結合していってもよいです。
・ if __name__ == “__main__”: とした行以降で、detect_image1.py を直接実行した処理を記載しています。
・ python detect_image1.py … を実行する際、引数として画像のリンクを入力することにします。
・ file1 で、その画像のリンクを取得し、image1 として OpenCV で画像を読み込みます。
・ model1 は、上記の学習済み教師モデルのリンクです。
・ モデル model1 と画像 image1 を detection1() に入れると、物体認識が実行されて結果 result1 が得られます。この結果を上述の object_counter1() に入れて、どのような物体がいくつ見つかったかを取得します。
・ 見つかった物体と数を表示させたあと、image2 = … の行で、画像上に検出した領域の描画をしています。
物体検出の結果を表示した後、任意のキーを待ち、キー入力があればスクリプトを終了します。

・ detection1() 関数内部の max_results=10、score_threshold=0.3 としたところで、検出する物体の最大数、検出する際の閾値(感度)を設定しています。
うまく動いたら、数値を変えてみて、使いたい環境に応じて微調整・最適化をしてみてください。
・ num_threads=4 とした行では、Raspberry Pi で使えるコア数などを指定しています。Raspberry Pi 5, 4, 3 あたりは、コア数は4つとなっています。
・ object_counter1() 関数で、検出結果 result1 を print させているところをコメントアウトしています。
検出結果をカスタマイズしたいときは、このコメントアウト部分を出力させてみて、取り出したい情報が取得できるよう、検出結果の出力フォーマットを調整してみてください。
たとえば、Raspberry Pi でサーボモーターなどと組み合わせることで、見つけた物体に応じて動きを変えるロボットなども製作できると思います。

まとめ

TensorFlow Lite のライブラリを参照し Python のスクリプトを作成することで、任意の静止画に対しても物体検出のプログラムが動くようになりました。
この程度のプログラミングに慣れてくると、あとはどのようにでもカスタマイズできそうな気がしてきます。

関連リンク
・ TensorFlow Lite を Raspberry Pi にインストール
・ ウェブサイトのスクリーンショットを自動化する 【Raspberry Pi 版】
・ ネット上のライブカメラの動画を自作のHTMLに埋め込む
・ サーボモーターを動かす 【Raspberry Pi】

外部リンク [PR] 
・ Raspberry Pi 5 (8GB RAM) 技適対応品
・ Raspberry Pi 5 8GB 技適対応品/アクティブクーラー
・ Raspberry Pi 5 用 27W USB-C PD(電源アダプタ)

サンプルスクリプト

静止画に対しオブジェクト検出を行うスクリプト detect_image1.py

import os 
import sys
from operator import itemgetter 
import cv2
from tflite_support.task import core
from tflite_support.task import processor
from tflite_support.task import vision
import utils

def get_model1(): 
  path1 = os.path.dirname( __file__ ) + "/" 
  model1 = path1 + "efficientdet_lite0.tflite" 
  return model1 

def detection1( model1, image1 ): 
  rgb_image1 = cv2.cvtColor( image1, cv2.COLOR_BGR2RGB )                            # rgb image 
  tsr_image1 = vision.TensorImage.create_from_array( rgb_image1 )                   # tensor image 
  options1 = core.BaseOptions( file_name=model1, use_coral=False, num_threads=4 )   # GPU:False CPU_threads:4 
  options2 = processor.DetectionOptions( max_results=10, score_threshold=0.3 )
  options3 = vision.ObjectDetectorOptions( base_options=options1, detection_options=options2 )
  detector1 = vision.ObjectDetector.create_from_options( options3 )
  result1 = detector1.detect( tsr_image1 )
  return result1 

def object_counter0( result1 ): 
# print( result1 ) 
  a1 = [] 
  for det1 in result1.detections: 
    cat1 = det1.categories[0] 
    box1 = det1.bounding_box 
    a1.append( [cat1.category_name, cat1.score, box1.origin_x, box1.origin_y, box1.width, box1.height] ) 
  return a1 

def object_counter1( result1 ): 
  a1 = object_counter0( result1 ) 
  a1.sort( key=itemgetter(0) ) 
  a2 = [] 
  if len(a1) > 0: 
    str1 = a1[0][0].replace(" ", "_")
    n1 = 1 
    for i1 in range(len(a1)-1): 
      str2 = a1[i1+1][0].replace(" ", "_")
      if str1 == str2: 
        n1 = n1 + 1 
      else: 
        a2.append( [str1, n1] ) 
        str1 = str2 
        n1 = 1 
    a2.append( [str1, n1] ) 
    a2.sort( key=itemgetter(0) ) 
    a2.reverse() 
    a2.sort( key=itemgetter(1) ) 
    a2.reverse() 
  a1 = a2 
  a2 = map( lambda x1: " ".join(map(str, x1)), a1 ) 
  str1 = "; ".join(a2) 
  return str1 

if __name__ == "__main__": 
  file1 = sys.argv[1]                           # image file (*.png etc.) 
  image1 = cv2.imread( file1 )                  # bgr image 
  model1 = get_model1() 
  result1 = detection1( model1, image1 )        # object detection 
  a1 = object_counter1( result1 ) 
  print( a1 ) 
  image2 = utils.visualize( image1, result1 )   # bgr image 
  cv2.imshow( 'object_detector', image2 )
  cv2.waitKey(0)                                # any key 



タイトルとURLをコピーしました