Jetson nanoで、オリジナルの学習モデルを使った物体検出[5/5]  〜Jetson nanoを使った物体検出〜

スマートホーム
スポンサーリンク

はじめに

オリジナルモデルを使った物体検出をJetson nanoで行うべく、以下を実現しようとしています。

これを実現する方法を5回に分けて記事を記載してきましたが、今回はいよいよ最終回です!!

 第1回:IBM Cloud Annotationsを用いたアノテーション
 第2回:Google Colabを用いたモデルの学習
 第3回:Jetson nanoの環境構築
 第4回:DeepStreamアプリを使いこなす
 第5回:学習モデルの変換と物体検出 ←この記事

第5回:学習モデルの変換と物体検出

前回はDeep Stream SDKに添付されている学習済モデルを使って物体検出をやってみました。しかし、Deep Stream SDKに添付されている学習モデルは4種類の物体しか検知できません。今回は、90種類の物体を検出可能な、COCOデータセットを使った学習済モデルであるSSD Mobilenet v2 cocoによる物体検出の方法と、第2回でGoogle Colabで作成したオリジナルの学習モデルを使った物体検出の方法をご紹介します。

SSD_mobilenet_v2を使った物体検出

まずは、SSD Mobilenet v2 cocoを使って物体を検知してみたいと思います。

①SSD Mobilenet v2 cocoのダウンロード

まずは、SSD Mobilenet v2 cocoのモデルをダウンロードします。ちなみに学習済のモデルは「Model Zoo」と呼ばれていて「TensorFlow 1 Detection Model Zoo」のサイトから、ダウンロード可能です。

$ cd
$ wget http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v2_coco_2018_03_29.tar.gz
・・・省略・・・
$

②アーカイブの解凍

ダウンロードが完了したら、tar.gzアーカイブを解凍します。frozen_inference_graph.pbなど、オリジナルの学習モデルを作成した時と同じようなファイルがありますね。解凍したらディレクトリを移動します。

$ tar xfvz ssd_mobilenet_v2_coco_2018_03_29.tar.gz 
ssd_mobilenet_v2_coco_2018_03_29/checkpoint
ssd_mobilenet_v2_coco_2018_03_29/model.ckpt.meta
ssd_mobilenet_v2_coco_2018_03_29/pipeline.config
ssd_mobilenet_v2_coco_2018_03_29/saved_model/saved_model.pb
ssd_mobilenet_v2_coco_2018_03_29/frozen_inference_graph.pb
ssd_mobilenet_v2_coco_2018_03_29/saved_model/
ssd_mobilenet_v2_coco_2018_03_29/saved_model/variables/
ssd_mobilenet_v2_coco_2018_03_29/model.ckpt.index
ssd_mobilenet_v2_coco_2018_03_29/
ssd_mobilenet_v2_coco_2018_03_29/model.ckpt.data-00000-of-00001

$ cd ssd_mobilenet_v2_coco_2018_03_29

③uff変換用configファイルの作成

Tensorflowのpbファイルは、そのままではJetson nanoのTensorRTでは利用できません。このため、pbファイルをTensorRT用のuffファイルに変換する必要があります。この変換に必要な設定ファイル「config.py」を以下のように作成します。この内容はNVIDIA Developper Forumのココで紹介されているものです。

import graphsurgeon as gs
import tensorflow as tf

Input = gs.create_node("Input",
    op="Placeholder",
    dtype=tf.float32,
    shape=[1, 3, 300, 300])
PriorBox = gs.create_plugin_node(name="GridAnchor", op="GridAnchor_TRT",
    numLayers=6,
    minSize=0.2,
    maxSize=0.95,
    aspectRatios=[1.0, 2.0, 0.5, 3.0, 0.33],
    variance=[0.1,0.1,0.2,0.2],
    featureMapShapes=[19, 10, 5, 3, 2, 1])
NMS = gs.create_plugin_node(name="NMS", op="NMS_TRT",
    shareLocation=1,
    varianceEncodedInTarget=0,
    backgroundLabelId=0,
    confidenceThreshold=1e-8,
    nmsThreshold=0.6,
    topK=100,
    keepTopK=100,
    numClasses=91,
    ###########################################
    #inputOrder=[0, 2, 1],
    inputOrder=[1, 0, 2],
    ###########################################
    confSigmoid=1,
    isNormalized=1,
    scoreConverter="SIGMOID")
concat_priorbox = gs.create_node(name="concat_priorbox", op="ConcatV2", dtype=tf.float32, axis=2)
concat_box_loc = gs.create_plugin_node("concat_box_loc", op="FlattenConcat_TRT", dtype=tf.float32, axis=1, ignoreBatch=0)
concat_box_conf = gs.create_plugin_node("concat_box_conf", op="FlattenConcat_TRT", dtype=tf.float32, axis=1, ignoreBatch=0)

namespace_plugin_map = {
    "MultipleGridAnchorGenerator": PriorBox,
    "Postprocessor": NMS,
    "Preprocessor": Input,
    "ToFloat": Input,
    "image_tensor": Input,
#   "MultipleGridAnchorGenerator/Concatenate": concat_priorbox,
    "Concatenate": concat_priorbox,
    "concat": concat_box_loc,
    "concat_1": concat_box_conf
}

def preprocess(dynamic_graph):
    all_assert_nodes = dynamic_graph.find_nodes_by_op("Assert")
    dynamic_graph.remove(all_assert_nodes, remove_exclusive_dependencies=True)

    all_identity_nodes = dynamic_graph.find_nodes_by_op("Identity")
    dynamic_graph.forward_inputs(all_identity_nodes)

    dynamic_graph.collapse_namespaces(namespace_plugin_map)
    dynamic_graph.remove(dynamic_graph.graph_outputs, remove_exclusive_dependencies=False)
    dynamic_graph.find_nodes_by_op("NMS_TRT")[0].input.remove("Input")

④uffファイルへの変換

config.pyが作成できたら、NVIDIA版Tensorflowに含まれる「convert_to_uff.py」を使って以下のように、pbファイルをuffファイルに変換します。私はここでつまずいて2週間ぐらい悩みました💦

$ python3 /usr/lib/python3.6/dist-packages/uff/bin/convert_to_uff.py frozen_inference_graph.pb -o frozen_inference_graph.uff -O NMS -p config.py
・・・省略・・・
NOTE: UFF has been tested with TensorFlow 1.15.0.
WARNING: The version of TensorFlow installed on this system is not guaranteed to work with UFF.
UFF Version 0.6.9
=== Automatically deduced input nodes ===
[name: "Input"
op: "Placeholder"
attr {
  key: "dtype"
  value {
    type: DT_FLOAT
  }
}
attr {
  key: "shape"
  value {
    shape {
      dim {
        size: 1
      }
      dim {
        size: 3
      }
      dim {
        size: 300
      }
      dim {
        size: 300
      }
    }
  }
}
]
=========================================

Using output node NMS
Converting to UFF graph
・・・省略・・・
No. nodes: 1094
UFF Output written to frozen_inference_graph.uff
$ ls -l *.uff
-rw-rw-r-- 1 test test 67790274 12月 12 18:47 frozen_inference_graph.uff
$

⑤nvinfer_custom_implのコピー

次にSSDモデル用のnvinferカスタムプラグインを作成します。カスタムプラグインといっても、NVIDIAのサンプルをそのまま使います。まずは、NVIDIAのソースをコピーします。コピーしたソース内の「objectDetector_SSD」ディレクトリにnvinfer_custom_implが入っています。

$ cp -r /opt/nvidia/deepstream/deepstream/sources ./
$ cd sources/objectDetector_SSD/
$ ls -l nvdsinfer_custom_impl_ssd/
total 24
-rw-r--r-- 1 test test  1865 12月 12 19:05 Makefile
-rw-r--r-- 1 test test 11515 12月 12 19:05 nvdsiplugin_ssd.cpp
-rw-r--r-- 1 test test  4670 12月 12 19:05 nvdsparsebbox_ssd.cpp

⑥nvinfer_custom_implのビルド

次にnvinfer_custom_implをビルドします。なお、ビルドには「CUDA_VER」環境変数が必要なので、設定してからビルドします。うまくビルドできると「libnvdsinfer_custom_impl_ssd.so」が作成されます。

作成された「libnvdsinfer_custom_impl_ssd.so」を、uffファイルと同じディレクトリにコピーします。

$ export CUDA_VER=10.2
$ echo $CUDA_VER
10.2

$ make -C nvdsinfer_custom_impl_ssd
make: Entering directory '/home/test/ssd_mobilenet_v2_coco_2018_03_29/sources/objectDetector_SSD/nvdsinfer_custom_impl_ssd'
g++ -o libnvdsinfer_custom_impl_ssd.so nvdsparsebbox_ssd.cpp nvdsiplugin_ssd.cpp -Wall -Werror -std=c++11 -shared -fPIC -Wno-error=deprecated-declarations -I../../includes -I/usr/local/cuda-10.2/include -Wl,--start-group -lnvinfer -lnvparsers -L/usr/local/cuda-10.2/lib64 -lcudart -lcublas -Wl,--end-group
make: Leaving directory '/home/test/ssd_mobilenet_v2_coco_2018_03_29/sources/objectDetector_SSD/nvdsinfer_custom_impl_ssd'

$ ls -l nvdsinfer_custom_impl_ssd/
total 120
-rw-r--r-- 1 test test  1865 12月 12 19:18 Makefile
-rwxrwxr-x 1 test test 97888 12月 12 19:22 libnvdsinfer_custom_impl_ssd.so
-rw-r--r-- 1 test test 11515 12月 12 19:18 nvdsiplugin_ssd.cpp
-rw-r--r-- 1 test test  4670 12月 12 19:18 nvdsparsebbox_ssd.cpp

$ cp nvdsinfer_custom_impl_ssd/libnvdsinfer_custom_impl_ssd.so ../../

⑦ラベルファイルの作成

次に、検出物体の名前が記載されたラベルファイルを作成します。COCOモデルのラベル一覧は、ここからダウンロードできます。ただし1行目は「Undefined」にしないとダメらしいので、1行目に「Undefined」を追加します。

$ cd ../../
$ wget https://raw.githubusercontent.com/amikelive/coco-labels/master/coco-labels-paper.txt
$ ls -l coco-labels-paper.txt 
-rw-rw-r-- 1 test test 702 12月 12 21:42 coco-labels-paper.txt
$sed -i '1s/^/Undefined\n/' coco-labels-paper.txt
$

⑧nvinfer設定ファイルの作成

nvinferの設定ファイル「config_infer.txt」を以下のように作成します。このファイルの中に、ここまでで用意した3つのファイル(uff、so、ラベル)のパス記載します。ちなみに、model-engine-fileは自動生成されるので、この時点でファイルはなくてOKです。

[property]
gpu-id=0
net-scale-factor=0.0078431372
offsets=127.5;127.5;127.5
model-color-format=0
model-engine-file=frozen_inference_graph.uff_b1_gpu0_fp32.engine
labelfile-path=coco-labels-paper.txt
uff-file=frozen_inference_graph.uff
infer-dims=3;300;300
uff-input-order=0
uff-input-blob-name=Input
batch-size=1
## 0=FP32, 1=INT8, 2=FP16 mode
network-mode=0
num-detected-classes=91
interval=0
gie-unique-id=1
is-classifier=0
output-blob-names=NMS
parse-bbox-func-name=NvDsInferParseCustomSSD
custom-lib-path=libnvdsinfer_custom_impl_ssd.so

[class-attrs-all]
threshold=0.6
roi-top-offset=0
roi-bottom-offset=0
detected-min-w=0
detected-min-h=0
detected-max-w=0
detected-max-h=0

⑨アプリの設定ファイルの作成

最後にDeep Streamアプリ用の設定ファイル「app_config.txt」を作成します。config-fileの部分がポイントですね。[source0]や[sink0]は前回の記事を参考に、カスタマイズして下さい。

[application]
enable-perf-measurement=1
perf-measurement-interval-sec=5

[tiled-display]
enable=1
rows=1
columns=1
width=640
height=480
gpu-id=0
nvbuf-memory-type=0

[source0]
enable=1 #Type 1=CameraV4L2 2=URI 3=MultiURI 4=RTSP
type=3
uri=file:///opt/nvidia/deepstream/deepstream-5.0/samples/streams/sample_1080p_h264.mp4
num-sources=1
gpu-id=0
cudadec-memtype=0

[sink0]
enable=1
type=3
sync=1
source-id=0
gpu-id=0
qos=0
nvbuf-memory-type=0
overlay-id=1
container=1 #1=mp4,2=mkv
codec=1 #1=h264,2=h265
output-file=./out.mp4

[osd]
enable=1
gpu-id=0
border-width=1
text-size=15
text-color=1;1;1;1;
text-bg-color=0.3;0.3;0.3;1
font=Serif
show-clock=0
clock-x-offset=800
clock-y-offset=820
clock-text-size=12
clock-color=1;0;0;0
nvbuf-memory-type=0

[streammux]
gpu-id=0
live-source=0
batch-size=1
batched-push-timeout=40000
width=640
height=480
enable-padding=0
nvbuf-memory-type=0

[primary-gie]
enable=1
gpu-id=0
model-engine-file=frozen_inference_graph.uff_b1_gpu0_fp32.engine
batch-size=1
#Required by the app for OSD, not a plugin property
bbox-border-color0=1;0;0;1
bbox-border-color1=0;1;1;1
bbox-border-color2=0;0;1;1
bbox-border-color3=0;1;0;1
interval=4
gie-unique-id=1
nvbuf-memory-type=0
config-file=config_infer.txt

[tracker]
enable=0

[tests]
file-loop=0

⑩必要ファイルの確認

ここまでの手順を完了すると、以下の5個ファイルがあると思いますので、確認しましょう!!

frozen_inference_graph.uff
libnvdsinfer_custom_impl_ssd.so
coco-labels-paper.txt
app_config.txt
config_infer.txt

11.DeepStreamアプリの実行

それでは、DeepStreamアプリを実行して物体検出できるかテストしてみましょう‼️

$ deepstream-app -c app_config.txt
・・・省略・・・

以下が物体検出結果の動画です。車も人もバスも検出できています‼️

以上でSSD Mobilenet v2 cocoを使った物体検出は完了です。

オリジナルモデルを使った物体検出

ここからは、IBM Cloud Annotationsを使ってアノテーションし、Google Colabを使って学習したオリジナルモデルを使った物体検出をやっていきます。手順はSSD Mobilenet v2の場合と同じですが、オリジナルモデルは検出できる物体の数が違うので、この部分を変更する必要があります。

①モデルファイルのアップロード

まずは、作業ディレクトリ「myModel」を作成して、Google Colabで作成したモデルをアップロードして解凍しましょう。またテスト用の動画ファイル「input.mp4」もアップロードしましょう。

$ mkdir myModel
$ cd myModel/
$ unzip flower_model.zip 
Archive:  flower_model.zip
  inflating: checkpoint              
  inflating: frozen_inference_graph.pb  
  inflating: model.ckpt.data-00000-of-00001  
  inflating: model.ckpt.index        
  inflating: model.ckpt.meta         
  inflating: pipeline.config         
   creating: saved_model/
   creating: saved_model/variables/
  inflating: saved_model/saved_model.pb  
$

②必要ファイルのコピー

SSD Mobilenet v2 cocoの時に作成した「config.py」「app_config.txt」「config_infer.txt」ファイルと「sources」ディレクトリをコピーします。

$ cp ../ssd_mobilenet_v2_coco_2018_03_29/config.py ./
$ cp ../ssd_mobilenet_v2_coco_2018_03_29/config_infer.txt ./
$ cp ../ssd_mobilenet_v2_coco_2018_03_29/app_config.txt ./
$ cp -r ../ssd_mobilenet_v2_coco_2018_03_29/sources ./
$

③uff変換用configファイルの編集

まずは「config.py」の23行目に記載の「numClasses=91」の記述を「numClasses=3」に変更します。この「3」という数字はラベルの数+1の値です。オリジナルモデルでは「Untitled Label」と「flower」の2つのラベルを定義しているので「3」となります。

また、オリジナルのモデルには「Cost」オペレーションが入ってしまいエラーとなるので、「config.py」の最終行に「Cast」オペレーションを削除するための2行を追加します。

import graphsurgeon as gs
import tensorflow as tf

Input = gs.create_node("Input",
    op="Placeholder",
    dtype=tf.float32,
    shape=[1, 3, 300, 300])
PriorBox = gs.create_plugin_node(name="GridAnchor", op="GridAnchor_TRT",
    numLayers=6,
    minSize=0.2,
    maxSize=0.95,
    aspectRatios=[1.0, 2.0, 0.5, 3.0, 0.33],
    variance=[0.1,0.1,0.2,0.2],
    featureMapShapes=[19, 10, 5, 3, 2, 1])
NMS = gs.create_plugin_node(name="NMS", op="NMS_TRT",
    shareLocation=1,
    varianceEncodedInTarget=0,
    backgroundLabelId=0,
    confidenceThreshold=1e-8,
    nmsThreshold=0.6,
    topK=100,
    keepTopK=100,
    numClasses=3,
    ###########################################                                                                                      
    #inputOrder=[0, 2, 1],                                                                                                           
    inputOrder=[1, 0, 2],
    ###########################################                                                                                      
    confSigmoid=1,
    isNormalized=1,
    scoreConverter="SIGMOID")
concat_priorbox = gs.create_node(name="concat_priorbox", op="ConcatV2", dtype=tf.float32, axis=2)
concat_box_loc = gs.create_plugin_node("concat_box_loc", op="FlattenConcat_TRT", dtype=tf.float32, axis=1, ignoreBatch=0)
concat_box_conf = gs.create_plugin_node("concat_box_conf", op="FlattenConcat_TRT", dtype=tf.float32, axis=1, ignoreBatch=0)

namespace_plugin_map = {
    "MultipleGridAnchorGenerator": PriorBox,
    "Postprocessor": NMS,
    "Preprocessor": Input,
    "ToFloat": Input,
    "image_tensor": Input,
#   "MultipleGridAnchorGenerator/Concatenate": concat_priorbox, 
    "Concatenate": concat_priorbox,
    "concat": concat_box_loc,
    "concat_1": concat_box_conf
}

def preprocess(dynamic_graph):
    all_assert_nodes = dynamic_graph.find_nodes_by_op("Assert")
    dynamic_graph.remove(all_assert_nodes, remove_exclusive_dependencies=True)

    all_identity_nodes = dynamic_graph.find_nodes_by_op("Identity")
    dynamic_graph.forward_inputs(all_identity_nodes)


    dynamic_graph.collapse_namespaces(namespace_plugin_map)
    dynamic_graph.remove(dynamic_graph.graph_outputs, remove_exclusive_dependencies=False)
    dynamic_graph.find_nodes_by_op("NMS_TRT")[0].input.remove("Input")
    
    cast_nodes = dynamic_graph.find_nodes_by_op("Cast")
    dynamic_graph.remove(cast_nodes, remove_exclusive_dependencies=False)

④uffファイルへの変換

「config.py」の編集ができたら、pbファイルをuffファイルに変換します。

$python3 /usr/lib/python3.6/dist-packages/uff/bin/convert_to_uff.py frozen_inference_graph.pb -o frozen_inference_graph.uff -O NMS -p config.py
・・・省略・・・
UFF Version 0.6.9
=== Automatically deduced input nodes ===
[name: "Input"
op: "Placeholder"
input: "Cast"
attr {
  key: "dtype"
  value {
    type: DT_FLOAT
  }
}
attr {
  key: "shape"
  value {
    shape {
      dim {
        size: 1
      }
      dim {
        size: 3
      }
      dim {
        size: 300
      }
      dim {
        size: 300
      }
    }
  }
}
]
=========================================

Using output node NMS
Converting to UFF graph
・・・省略・・・
No. nodes: 644
UFF Output written to frozen_inference_graph.uff
$ ls -l *.uff
-rw-rw-r-- 1 test test 18839555 12月 13 10:08 frozen_inference_graph.uff
$

⑤nvinfer_custom_implの編集

次にnvinfer_custom_implを編集します。「sources/objectDetector_SSD/nvdsinfer_custom_impl_ssd/nvdsparsebbox_ssd.cpp」の52行目にある「NUM_CLASSES_SSD = 91;」を「NUM_CLASSES_SSD = 3;」に書き換えます。

//static const int NUM_CLASSES_SSD = 91;
static const int NUM_CLASSES_SSD = 3;

⑥nvinfer_custom_implのビルド

書き換えたらビルドして「libnvdsinfer_custom_impl_ssd.so」を再作成し、これをuffファイルと同じディレクトリにコピーします。

$ cd sources/objectDetector_SSD/
$ export CUDA_VER=10.2
$ make -C nvdsinfer_custom_impl_ssd
make: Entering directory '/home/test/myModel/sources/objectDetector_SSD/nvdsinfer_custom_impl_ssd'
g++ -o libnvdsinfer_custom_impl_ssd.so nvdsparsebbox_ssd.cpp nvdsiplugin_ssd.cpp -Wall -Werror -std=c++11 -shared -fPIC -Wno-error=deprecated-declarations -I../../includes -I/usr/local/cuda-10.2/include -Wl,--start-group -lnvinfer -lnvparsers -L/usr/local/cuda-10.2/lib64 -lcudart -lcublas -Wl,--end-group
make: Leaving directory '/home/test/myModel/sources/objectDetector_SSD/nvdsinfer_custom_impl_ssd'

$ ls -l nvdsinfer_custom_impl_ssd/
total 128
-rw-r--r-- 1 test test  1865 12月 13 10:02 Makefile
-rwxrwxr-x 1 test test 97888 12月 13 10:15 libnvdsinfer_custom_impl_ssd.so
-rw-r--r-- 1 test test 11515 12月 13 10:02 nvdsiplugin_ssd.cpp
-rw-r--r-- 1 test test  4669 12月 13 10:14 nvdsparsebbox_ssd.cpp

$ cp nvdsinfer_custom_impl_ssd/libnvdsinfer_custom_impl_ssd.so ../../

$ cd ../../

⑦ラベルファイルの作成

次に、ラベルファイル「mylabels.txt」をuffファイルと同じディレクトリに作成します。以下のような感じですね。

unlabeled
Untitled Label
flower

⑧nvinfer設定ファイルの編集

ラベルファイルの名称を変更したので「config_infer.txt」のラベルファイル名を変更します。また、インプット動画のファイル名も変更しましょう。

#labelfile-path=coco-labels-paper.txt
labelfile-path=mylabels.txt

#num-detected-classes=91
num-detected-classes=3

⑨アプリの設定ファイルの編集

アプリ設定ファイル「app_config.txt」への変更は、入力する動画のファイル名の変更のみで大丈夫です。

#uri=file:///opt/nvidia/deepstream/deepstream-5.0/samples/streams/sample_1080p_h264.mp4
uri=file://./input.mp4

⑩必要ファイルの確認

以上の作業により、以下の6つのファイルが揃っていることを確認します。

input.mp4 <--テスト用の動画ファイル
frozen_inference_graph.uff  <--pbファイルから変換
libnvdsinfer_custom_impl_ssd.so  <--再ビルド
mylabels.txt <--新規に作成
config_infer.txt <--ラベル名の変更、num-detected-classesの変更
app_config.txt <--入力ファイル名の変更

11.DeepStreamアプリの実行

それでは、この連載の最終目的であるオリジナルモデルを使った物体検出の実行です!以下のようにして、DeepStreamアプリを起動しましょう‼️‼️

$deepstream-app -c app_config.txt
・・・省略・・・
$

以下が物体検出を行なった動画です。ちゃんと「ひまわり」が検出されています!!感動ですね😭

ちなみに、物体がうまく検出できない場合は、信頼度の値「app_config.txt」の「threshold」の値を下げてみると、信頼度の低い物体も四角の枠がつくので、調整してみてください。

おわりに

今回は、SSD Mobilenet v2 cocoとオリジナルのモデルを使って、Jetson nanoで物体検出ができるようにしてみました。TensorflowのモデルをTensorRTのモデルに変換するところで、何度もくじけそうになりましたが、ようやくできるようになったので、ここにまとめました。Tensorflowは、開発が盛んでバージョンアップの度に色々変わるので、Tensorflowのバージョンをしっかり合わせるのがポイントかと思います。

関連記事

記事が参考になったら、ブログランキングに協力(クリック)して貰えると嬉しいです。

昼間はIT企業に勤めてますが、プライベートでは「育児×家事×IoT」をテーマに家のスマートホーム化に取り組んでいます。Androidアプリも作っているので使って下さい。質問・コメントは、↓のコメント蘭でもFacebookメッセンジャーでもどちらでも大丈夫です。
E-mail:naka.kazz.d@gmail.com

naka-kazzをフォローする
スマートホーム開発者向け
スポンサーリンク
naka-kazzをフォローする
スマートホーム×DIY

コメント