みらいテックラボ

音声・画像認識や機械学習など, 管理人が興味のある技術の紹介や実際にトライしてみた様子などメモしていく.

PyTorchでSSDを試してみた(3)

普段物体検知を行うとき, これまで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)


f:id:moonlight-aska:20210914222550j:plain:w400


前回までに, オリジナルデータで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-ssdSSDモデルで, 処理時間を計測するコードは以下の通り.
[コード]

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