masato-ka's diary

日々思ったこととか、やったことの備忘録。

Android ThingsとRaspberry Pi 3Bでお手軽物体検出

この記事について

この記事ではRaspberry Pi 3B にAndroidThings 1.0.3をインストールした環境で、TensorFlow Liteを使った物体検出を実行してみた。AndroidThingsを問わずTensorFlown Liteを使った物体認識ではClassificationのモデルを使った方法がサンプルとして公開されている。Classificationは画像そのものをラベリングするもので、例えば猫の画像や犬の画像といった具合に画像自体をクラス分けしていく。これに対して物体検出(object detection)とは一枚の画像中に写っている物体を検出する手法となっている。今回は'''coco-ssd-mobilenet_v1'''と呼ばれるモデルをTensorFlow liteを使って動かしてみた。動かすだけならディープラーニングの知識は必要なく、学習済みのモデルとそのモデルに沿った入力と出力に合わせたデータを渡してあげれば良い。

f:id:masato-ka:20180826202603j:plain

物体検出周りについて

coco-ssd-mobilenet_v1について

技術的な詳細は専門ではないため割愛する。Mobilenetは計算量を削減するように工夫された物体識別用のDNN(Deep Nural Network)のネットワーク構造を指している。また、SSDは'''Single Shot multibox Detector'''の略で、1枚の画像から複数の物体を切り出すDNNのネットワーク構造を指す。cocoは物体検出タスク用の学習セットの名前だ。それぞれの詳細について詳しく知りたい方は専門的な書籍や論文を参照し欲しい。

TensorFlow Lite

今回はAndroidThings上でTensorFlow Liteと言うはDNN用の数値計算ライブラリを使った。TensorFlow LiteはTensorFlowをモバイル環境(AndroidiOS)でも動くようにカスタマイズしたものだ。普通のTensorFlowの学習済みモデルを量子化という形で圧縮して使用する。量子化とはモデルのパラメータを浮動小数点から整数に置き換え計算負荷を抑えたものらしい。専門的なところは微妙に違うかもしれないが理解はそんな感じだ。

物体検出の実行

実行結果

以下はRaspberry Pi 3B上で動かした結果だ。10FPSくらいで動いている。工夫すればもう少し早くなるかもしれない。カメラはRaspberry Pi CAMERA V2を使用している。SONY製センサが搭載されているモデルを購入した。一度640X480で画像を撮影して、300X300にリサイズしている。リサイズ後の画像をディスプレイに表示させているので表示が潰れたようになっている。

youtu.be

使用したモデルファイル

モデルファイルはTensorFlowで学習したものが公開されている。このモデルを元にTensorFlow Liteで利用できるように量子化されたモデルも公開されている。TensorFlow LiteのAndroidサンプルでも利用されているモデルだ。ツールを使い自分でモデルを圧縮する方法もあるが今回は実施していない。オリジナルのモデルを試す場合は必須となる。

https://storage.googleapis.com/download.tensorflow.org/models/tflite/coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip

ソースコードのポイント

ソースコードは以下のGitHubに掲載している。これをgradeleでビルドすればAndroidThings上で実行できるはずだ。ところどころフォルダがないと言われるかもしれないが、その場合は自分で当該のフォルダを作成すれば問題ない。

github.com

プロジェクトはモジュールに分かれている。

  • アプリケーション部のappモジュール
  • カメラ制御のcameraモジュール
  • 物体検出部のobjectdetectionモジュール

解説というほどでもないが物体検出処理のポイントについて記載する。TensorFlow Liteで実装する場合はモデルにあった入力を与え、モデルが吐き出す出力に合わせて結果を受け取る必要がある。しかし、それだけ合わせればどんなモデルでも利用するだけなら簡単に実装できそうである。今回の場合、入力は300X300pixelのカラー画像となる。つまり270000次元のデータ(300300RGB)となる。またそれぞれは1byteのデータになる。つまり270000要素の1次元のバイト配列を与えることになる。(配列のデータ構造の次元と与えるデータの次元が違うことに注意)

TensorFlowLiteのライブラリはbuild.gradleファイルに以下を設定するだけでいい。その他の環境設定は不要。

dependencies {
   implementation 'org.tensorflow:tensorflow-lite:1.9.0'
    compileOnly 'com.google.android.things:androidthings:+'
}

入力データは300X300にリサイズした画像(Bitmapオブジェクト)を1次元のByteByfferに以下のように書き出す。

private static int TF_INTPU_IMAGE_WIDTH = 300;
private static int TF_INTPU_IMAGE_HEIGHT = 300;
private static int DIM_BATCH_SIZE = 1;
private static int DIM_PIXELE_SIZE = 3;

private ByteBuffer convertBitmapToByteBuffer(Bitmap bitmap) {

        int[] intValues = new int[TF_INPUT_IMAGE_WIDTH * TF_INPUT_IMAGE_HEIGHT];
        ByteBuffer imgData = ByteBuffer.allocateDirect(DIM_BATCH_SIZE * TF_INPUT_IMAGE_WIDTH
                * TF_INPUT_IMAGE_HEIGHT * DIM_PIXELE_SIZE);
        imgData.order(ByteOrder.LITTLE_ENDIAN);
        TensorFlowHelper.convertBitmapToByteBuffer(bitmap, intValues, imgData);
        return imgData;
}

出力データ用のアウトプットとして以下のようなデータ構造を作成する。numDetectionは検出させたい物体数の最大値を定義する。

Map<Integer,Object> outputData = new HashMap<>();
float outputLocations = new float[1][numDetection][4];
float outputClasses = new float[1][numDetection];
float outputScores = new float[1][numDetection];
float numDetections = new float[1];
outputData.put(0, outputLocations);
outputData.put(1, outputClasses);
outputData.put(2, outputScores);
outputData.put(3, numDetections);

あらかじめモデルを読み込ませたmTensorFlowLiteのInterpreter#runForMultipleInputsOutputsを実行して画像検出処理を実行する。 認識結果はoutputDataに格納されている。

Object[] inputArray = {imgData};
mTensorFlowLite.runForMultipleInputsOutputs(inputArray, outputData);

実行した結果を以下のように処理する。

private stattic final int LABEL_OFFSET = 1;

final ArrayList<Recognition> recognitions = new ArrayList<>(numDetection);
        for(int i=0; i < outputData.size(); i++){
            final RectF location = new RectF(
                    outputLocations[0][i][1] * TF_INPUT_IMAGE_WIDTH,
                    outputLocations[0][i][0] * TF_INPUT_IMAGE_HEIGHT,
                    outputLocations[0][i][3] * TF_INPUT_IMAGE_WIDTH,
                    outputLocations[0][i][2] * TF_INPUT_IMAGE_HEIGHT
            );
            Recognition recognition =
                    new Recognition(
                            "" + i,
                            label.get((int) outputClasses[0][i] + LABEL_OFFSET),
                            outputScores[0][i],
                            location);
            recognitions.add(recognition);
        }

        return recognitions;

認識した物体ごとに以下の結果が格納されている。

  • outputLocationsは画像上での物体の位置 3次配列で2番目の添字が物体、3番目の添字が物体の位置を表す2点の座標となっている。
  • outputClassesは認識した物体のクラスとなっている。値はラベルファイルの行数-1になっているので、ラベルファイルを行ごとにArrayListか何かに格納し、参照する。
  • outputScoresは信頼度Accuracyを表している。

ちなみに今回作ったRecognitionオブジェクトはこんなかんんじ

public class Recognition {

    private final String id;
    private final String title;
    private final Float confidence;
    private final RectF location;

    public Recognition(
            final String id, final String title, final float confidence, final RectF location) {
        this.id = id;
        this.title = title;
        this.confidence = confidence;
        this.location = location;
    }

    public String getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public Float getConfidence() {
        return confidence == null ? 0f : confidence;
    }

    public RectF getLocation(){return location;}

    @Override
    public String toString() {
        String resultString = "";
        if (id != null) {
            resultString += "[" + id + "] ";
        }

        if (title != null) {
            resultString += title + " ";
        }

        if (confidence != null) {
            resultString += String.format("(%.1f%%) ", confidence * 100.0f);
        }

        return resultString.trim();
    }

}

まとめ

AndroidThingsを使うことで、TensorFlowの環境構築をすっ飛ばしてコードを書くだけで物体検出を動かすことができる。今回作成したサンプルのうち、カメラ部分と物体検出部分をライブラリとしてまとめて、bintrayで公開している。自分のアプリケーションに使ってみたい場合はそれを使うのもあり。性能と品質は保証できないけど。TensorFlow Liteは自分で目的に合わせた認識モデルを作れる様になったらより活用の幅が広がると思う。しかし、そこが活用する上での大きな課題だったりも。