普段物体検知を行うとき, これまでYOLOv3[1]やssd_keras[2][3]を使ってきた.
しかし, これらの物体検知をJetson Nanoなど組み込みボードで処理しようとすると, フレーム毎の処理に結構時間がかかり, 秒数フレームほどしか処理できなかったりした.
JetsonでDNN処理の高速化を考えた場合, Pythonの実装を頑張って高速化を目指すより, NVIDIAのTensorRTを活用する方が容易そうだ.
そこで, PyTorchのSSD(Single Shot MultiBox Detector)モデルをPCで学習し, 学習済みモデルをONNX形式に変換してJetsonで活用することを試してみることにした.
関連記事:
・PyTorchでSSDを試してみた(1)
・PyTorchでSSDを試してみた(2)
・PyTorchでSSDを試してみた(3)
・PyTorchでSSDを試してみた(4)
前回までに, オリジナルデータでPyTorchのSSDモデルを転移学習し, ONNX形式に変換する方法について紹介した.
今回は, Jetson Nanoで物体検知する場合のフレーム処理の処理時間などを確認してみた.
1. Jetson Nanoの状態確認
処理時間の計測を始める前に, Jetson Nanoの状態等を確認しておく.
まずは, 実行中の電力モード. 10Wモード(=MAXN)であることがわかる.
$ sudo nvpmodel -q [sudo] password for aska: NVPM WARN: fan mode is not set! NV Power Mode: MAXN
もし, 10Wモードでない場合には, - mオプションを利用することで電力モードを変更可能.
10Wモード:0, 5Wモード:1
$ sudo nvpmodel -m 0
次に, 有効なコア数, 動作周波数も確認しておく.
$ sudo jetson_clocks --show SOC family:tegra210 Machine:NVIDIA Jetson Nano Developer Kit Online CPUs: 0-3 cpu0: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1479000 CurrentFreq=1036800 IdleStates: WFI=1 c7=1 cpu1: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1479000 CurrentFreq=921600 IdleStates: WFI=1 c7=1 cpu2: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1479000 CurrentFreq=825600 IdleStates: WFI=1 c7=1 cpu3: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1479000 CurrentFreq=825600 IdleStates: WFI=1 c7=1 GPU MinFreq=76800000 MaxFreq=921600000 CurrentFreq=76800000 EMC MinFreq=204000000 MaxFreq=1600000000 CurrentFreq=1600000000 FreqOverride=0 Fan: PWM=0 NV Power Mode: MAXN
最後に, メモリやスワップも確認しておく.
$ free -h total used free shared buff/cache available Mem: 3.9G 611M 2.4G 27M 865M 3.1G Swap: 1.9G 0B 1.9G
2. 処理時間計測
それでは, PyTorchのSSDモデルをそのまま使用した場合と, ONNX形式に変換した場合について, 画像(640x480)1枚の物体検知を行う際の実行時間を計測してみる.
測定は, 11枚の画像を読み込んだ状態から, 物体検知の経過時間をtimeモジュールを使って計測し, 画像1枚の平均処理時間を求める.
ただし, 初回コール時は内部でのモデル展開や変換等のため, 極端に処理に時間がかかる場合があるので, 2回目以降のコールで計測する.
2.1 PyTorchのSSDモデルの計測
pytorch-ssdのSSDモデルで, 処理時間を計測するコードは以下の通り.
[コード]
from glob import glob import time import cv2 from vision.ssd.mobilenetv1_ssd import create_mobilenetv1_ssd, create_mobilenetv1_ssd_predictor model_path = './models/mb1-ssd-Epoch-48-Loss-1.7370146930217742.pth' label_path = './models/labels.txt' class_names = [name.strip() for name in open(label_path).readlines()] # 学習済みモデル net = create_mobilenetv1_ssd(len(class_names), is_test=True) net.load(model_path) predictor = create_mobilenetv1_ssd_predictor(net, candidate_size=200) # データ準備(RGBモード) files = glob('./data/test/*.jpg') images = [] for f in files: img = cv2.imread(f) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) images.append(img) print('Data : {} images'.format(len(images))) # 初回を除く10回の平均 for i, img in enumerate(images): if i == 1: start = time.time() boxes, labels, probs = predictor.predict(img, 10, 0.5) end = time.time() print('Ave. time = {:.4f} msec'.format((end - start)*1000 / 10))
[実行結果]
$ python3 test-pytorch-ssd.py Data : 11 images Inference time: 9.823330879211426 Inference time: 0.03374624252319336 Inference time: 0.033875465393066406 Inference time: 0.03377366065979004 Inference time: 0.03353381156921387 Inference time: 0.03349614143371582 Inference time: 0.03371143341064453 Inference time: 0.03353261947631836 Inference time: 0.033322811126708984 Inference time: 0.0336604118347168 Inference time: 0.03356575965881348 Ave. time = 94.1300630569458 msec
2.2 ONNX形式に変換したモデルのの計測
jetson-inferenceの物体検知+ONNX形式モデルで, 処理時間を計測するコードは以下の通り.
[コード]
from glob import glob import time import cv2 import jetson.inference import jetson.utils import numpy as np # 学習済みモデル # 注) ssd-mobilent.onnxは, mb1-ssd-Epoch-48-Loss-1.7370146930217742.pthを変換. network = "ssd-mobilenet-v1" argv = ["--model=models/ssd-mobilenet.onnx", "--labels=models/labels.txt", "--input-blob=input_0", "--output-cvg=scores", "--output-bbox=boxes" ] net = jetson.inference.detectNet(network, argv, threshold=0.5) # データ準備(RGBAモード) files = glob('./data/test/*.jpg') images = [] for f in files: img = cv2.imread(f) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGBA).astype(np.float32) images.append(img) print('Data : {} images'.format(len(images))) # 初回を除く10回の平均 for i, img in enumerate(images): if i == 1: start = time.time() img = jetson.utils.cudaFromNumpy(img) detections = net.Detect(img) end = time.time() print('Ave. time = {:.4f} msec'.format((end - start)*1000 / 10))
[実行結果]
$ python3 test-detect.py detectNet -- loading detection network model from: -- prototxt NULL -- model models/ssd-mobilenet.onnx -- input_blob 'input_0' -- output_cvg 'scores' -- output_bbox 'boxes' -- mean_pixel 0.000000 -- mean_binary NULL -- class_labels models/labels.txt -- threshold 0.500000 -- batch_size 1 [TRT] TensorRT version 8.0.1 [TRT] loading NVIDIA plugins... [TRT] Registered plugin creator - ::GridAnchor_TRT version 1 [TRT] Registered plugin creator - ::GridAnchorRect_TRT version 1 [TRT] Registered plugin creator - ::NMS_TRT version 1 (省略) [TRT] device GPU, models/ssd-mobilenet.onnx initialized. [TRT] detectNet -- number object classes: 2 [TRT] detectNet -- maximum bounding boxes: 3000 [TRT] detectNet -- loaded 2 class info entries [TRT] detectNet -- number of object classes: 2 Data : 11 images Ave. time = 52.4386 msec
PyTorchのSSDモデルで平均94.1ms/枚, ONNX形式モデルで平均52.4ms/枚であった.
内部のパラメータ等の違いの影響はあると思うが, それでも思ったほど処理が速くなっていない.
2.3 画像読み込み変更
さらなる高速化ができないかと, 少しネットを調べてみると, jetson.utils.cudaFromNumpy()が遅いといった内容[4]のものがあった.
jetson-inferenceの物体検知を使用する際には, 画像をCPU/GPU共有のメモリに載せて渡す必要がある.
そこで, jetson.utils.loadImageRGBA()を使用した画像読み込みを試してみる.
[コード]
from glob import glob import time import jetson.inference import jetson.utils # 学習済みモデル # 注) ssd-mobilent.onnxは, mb1-ssd-Epoch-48-Loss-1.7370146930217742.pthを変換. network = "ssd-mobilenet-v1" argv = ["--model=models/ssd-mobilenet.onnx", "--labels=models/labels.txt", "--input-blob=input_0", "--output-cvg=scores", "--output-bbox=boxes" ] net = jetson.inference.detectNet(network, argv, threshold=0.5) # データ準備(RGBAモード) files = glob('./data/test/*.jpg') images = [] for f in files: img, width, height = jetson.utils.loadImageRGBA(f) images.append(img) print('Data : {} images'.format(len(images))) # 初回を除く10回の平均 for i, img in enumerate(images): if i == 1: start = time.time() detections = net.Detect(img) end = time.time() print('Ave. time = {:.4f} msec'.format((end - start)*1000 / 10))
[実行結果]
$ python3 test-detect2.py detectNet -- loading detection network model from: -- prototxt NULL -- model models/ssd-mobilenet.onnx -- input_blob 'input_0' -- output_cvg 'scores' -- output_bbox 'boxes' -- mean_pixel 0.000000 -- mean_binary NULL -- class_labels models/labels.txt -- threshold 0.500000 -- batch_size 1 [TRT] TensorRT version 8.0.1 [TRT] loading NVIDIA plugins... [TRT] Registered plugin creator - ::GridAnchor_TRT version 1 [TRT] Registered plugin creator - ::GridAnchorRect_TRT version 1 (省略) [TRT] detectNet -- loaded 2 class info entries [TRT] detectNet -- number of object classes: 2 [image] loaded './data/test/FCam9_00036350.jpg' (640x480, 4 channels) [image] loaded './data/test/FCam9_00036304.jpg' (640x480, 4 channels) [image] loaded './data/test/FCam9_00036356.jpg' (640x480, 4 channels) [image] loaded './data/test/FCam9_00036358.jpg' (640x480, 4 channels) [image] loaded './data/test/FCam9_00036300.jpg' (640x480, 4 channels) [image] loaded './data/test/FCam9_00036354.jpg' (640x480, 4 channels) [image] loaded './data/test/FCam9_00036306.jpg' (640x480, 4 channels) [image] loaded './data/test/FCam9_00036352.jpg' (640x480, 4 channels) [image] loaded './data/test/FCam9_00036308.jpg' (640x480, 4 channels) [image] loaded './data/test/FCam9_00036310.jpg' (640x480, 4 channels) [image] loaded './data/test/FCam9_00036302.jpg' (640x480, 4 channels) Data : 11 images Ave. time = 40.4562 msec
jetson.utils.cudaFromNumpy()の使用をやめることで, 約12ms/枚ほど速くなり, 平均40.5ms/枚で処理できるようになった.
静止画の場合はこの手が使えるが, 動画のフレーム毎の処理ではそうもいかない.
他にも, torch2trt[5]なるものもあるようだ.
少し調べて見ようと思う!!
---
[1] YOLO: Real-Time Object Detection
[2] GitHub - pierluigiferrari/ssd_keras: A Keras port of Single Shot MultiBox Detector
[3] GitHub - rykov8/ssd_keras: Port of Single Shot MultiBox Detector to Keras
[4] CudaFromNumpy Bottleneck Performance Issues #557
[5] NVIDIA-AI-IOT/torch2trt: An easy to use PyTorch to TensorRT converter