はじめに
オリジナルモデルを使った物体検出を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のバージョンをしっかり合わせるのがポイントかと思います。
コメント