DeepStream プラグイン入門3 ~GPU側で動作するプラグインを構築する~

DeepStream プラグイン入門3 ~GPU側で動作するプラグインを構築する~

DeepStream プラグイン入門第3回。
前回の内容までで、OpenCV を組み込み、入力映像に CPU で処理を行い、出力映像を返すことができるようになりました。
今回は GPU 側で処理を動かす方法を調べ、第1回同様、GPU 版の『何もしない』プラグインを作り、そこから発展させてみます。

シリーズ記事 :
DeepStreamプラグイン入門1 ~サンプルプラグインを実行してみる~
DeepStreamプラグイン入門2 ~独自のプラグインを構築する~
DeepStreamプラグイン入門3 ~GPU側で動作するプラグインを構築する~ (本記事)

環境

本シリーズ記事では以下の環境で動作確認を行っています。

  • Jetson Nano
    • JetPack 4.4.1
      • CUDA 8.0
      • OpenCV 4.3
    • DeepStream 5.0 インストール済み

GPU プラグインのサンプルを探す

DeepStream で GPU を使う方法はあまりメジャーじゃないのか、探すのに苦労しました。。
ようやく見つけた 参照 (1) の記事によると、「nvivafilter」というプラグインを使って、CUDA 実装されたライブラリを呼び出すようです。

nvivafilter (ドキュメント)

「nvivafilter」と繋げて書いてあると分かりにくいですが、nv-iva-filter。つまり IVA = Intelligent Video Analytics 向けの処理ということみたいです。

Jetson Nano 上で検索をかけてみると、

/usr/lib/aarch64-linux-gnu/libnvsample_cudaprocess.so

にビルド済みのサンプルがおかれていて、こちらはそのまま使用可能です。

サンプルのソースコードを取得したい

ビルド済みのサンプルは見つけたのですが、ソースコードが見つからない。
——色々調べた結果、DeepStream のサンプル群ではなく、Jetson Nano のカーネルソースコードに含まれているそうです…。

参照 (2) の記事を参考にカーネルソースを取ってきて、 その中にある nvsample_cudaprocess_src.tbz2 を展開すると、目的のものが得られます。
コードのライセンスは BSD-3-Clause です。

サンプルを動かす

まずはサンプルをそのまま動かしてみます。
Web カメラの映像を入力として、nvsample_cudaprocess に通し、結果を表示させてみます。

nvivafilter は入力として「CSI カメラの入力」もしくは「h264 データ入力」しかつながらないとのこと。
手元には h264 送信できるカメラが無いので、以下のストリームで、YUYV カメラ入力を h264 変換した上でプラグインに流します。

gst-launch-1.0 -e \
    v4l2src device=/dev/video0 ! 'video/x-raw, width=640, height=480, format=YUY2, framerate=30/1' ! \
    nvvideoconvert ! 'video/x-raw, width=640, height=480, format=I420' ! \
    omxh264enc ! h264parse ! \
    omxh264dec ! nvivafilter cuda-process=true customer-lib-name="libnvsample_cudaprocess.so" ! \
    'video/x-raw(memory:NVMM), format=(string)NV12' ! \
    nvegltransform ! nveglglessink sync=0

実行すると、左上に緑色の矩形が表示されます。
サンプルのコードにもコメントがあるので、これがサンプルプラグインの動作のようです。

『何もしない』プラグイン

手元の環境で実行することができたので、まず『何もしない』プラグインを構築してみます。
以降、サンプルのコード nvsample_cudaprocess を「nvsimple_cudaprocess」とリネームして使うこととします。

最終的に色々削って『何もしない』状態にしたものをこちらに置きました。

ISP-Kazuki-Nagasawa/deepstream_plugin_samples

サンプルのメインコードである nvsimple_cudaprocess.cu を見ると、4つの処理

  • init : 初期化処理
    • 前処理、本処理、後処理の処理を登録。
  • pre_process : 前処理
    • init で登録した前処理の実装。
  • gpu_process : 本処理
    • init で登録した本処理の実装。
  • post_process : 後処理
    • init で登録した後処理の実装。

が実装されていて、 前処理、後処理の使い方がまだ分かっていませんが、とりあえず本処理の部分だけ実装すればよさそうです。

最低限のコードにしてビルド、後は動かして映像入力と同じものが出力されれば OK ですが、 このままでは動いているのか分からないので、OpenCV を使ってちょっとしたフィルタを入れてみます。

『何もしない』プラグイン適用結果

簡単なフィルタを追加

本処理は GPU で動作するので、OpenCV も GPU 版の処理を有効にします。
Jetson Nano に始めから入っている OpenCV には GPU 版の処理が入っていないので、アンインストール後、独自で OpenCV を入れなおします。( ここでは OpenCV 4.1.0 を導入。)

sudo su - 
cd /opt
export OPENCV_VER=4.1.0

# Contribe
git clone https://github.com/opencv/opencv_contrib.git && cd opencv_contrib && git checkout $OPENCV_VER
cd ../

# OpenCV
wget https://github.com/opencv/opencv/archive/$OPENCV_VER.zip
unzip $OPENCV_VER.zip
rm $OPENCV_VER.zip
cd opencv-$OPENCV_VER && mkdir build && cd build
cmake -DWITH_LIBV4L=ON -DWITH_CUDA=ON -DPYTHON2_PACKAGES_PATH=/usr/local/lib/python2.7/dist-packages -DPYTHON3_PACKAGES_PATH=/usr/local/lib/python3.6/dist-packages -DOPENCV_EXTRA_MODULES_PATH=../../opencv_contrib/modules ..
make -j4
make install

nvsimple_cudaprocess.cu の本処理部分「gpu_process」内で RGBA 映像に対してバイラテラルフィルタをかける処理を追加します。

  if (eglFrame.frameType == CU_EGL_FRAME_TYPE_PITCH) {
    if (eglFrame.eglColorFormat == CU_EGL_COLOR_FORMAT_ABGR) {

      // bilateral filter x 3
      cv::cuda::GpuMat gpuMat(eglFrame.height, eglFrame.width, CV_8UC4, eglFrame.frame.pPitch[0]);
      cv::cuda::bilateralFilter(gpuMat, gpuMat, 15, 30, 30);  
      cv::cuda::bilateralFilter(gpuMat, gpuMat, 15, 30, 30);  
      cv::cuda::bilateralFilter(gpuMat, gpuMat, 15, 30, 30);  
    } else {
      printf ("Invalid eglcolorformat, ¥"RGBA¥" format only.¥n");
    }
  }

後は make して実行。
ここで、カメラを入力としてプラグイン実行、表示するストリームは

gst-launch-1.0 \
    -e \
    v4l2src device=/dev/video0 ! 'video/x-raw, width=640, height=480, format=YUY2, framerate=30/1' ! \
    nvvideoconvert ! 'video/x-raw, width=640, height=480, format=I420' ! \
    omxh264enc ! h264parse ! \
    omxh264dec ! nvivafilter cuda-process=true customer-lib-name="./gpu_plugins/nvsimple_cudaprocess/libnvsimple_cudaprocess.so" ! \
    'video/x-raw(memory:NVMM), format=(string)RGBA' ! \
    nvegltransform ! nveglglessink sync=0

となりますが、プラグインが求めている映像のフォーマットが ABGR であるため、ストリーム

gst-launch-1.0 \
    -e \
    v4l2src device=/dev/video0 ! 'video/x-raw, width=640, height=480, format=YUY2, framerate=30/1' ! \
    nvvideoconvert ! 'video/x-raw, width=640, height=480, format=I420' ! \
    omxh264enc ! h264parse ! \
    omxh264dec ! nvivafilter cuda-process=true customer-lib-name="./gpu_plugins/nvsimple_cudaprocess/libnvsimple_cudaprocess.so" ! \
    'video/x-raw(memory:NVMM), format=(string)RGBA' ! \
    nvegltransform ! nveglglessink sync=0

の 6 〜 7 行目のように、

nvivafilter cuda-process=true customer-lib-name="./gpu_plugins/nvsimple_cudaprocess/libnvsimple_cudaprocess.so" ! 'video/x-raw(memory:NVMM), format=(string)RGBA'

とプラグインへの入力フォーマットを指定する必要があります。

実行すると、次のようにバイラテラルフィルタがかかり、油絵風の映像になりました。
速度計測はしていませんが、高速に動いている様子はありませんでした。( もともとバイラテラルフィルタは速い処理ではないので、十分速度が出ているかもしれませんが…。)

バイラテラルフィルタ適用結果

トラブルシューティング

nvivafilter 読み込みエラー出ない。

nvifilter はパスが間違ってもエラーが発生せず、入力ストリームをそのまま出力に流す処理になるようです。
なので、例えば

nvivafilter cuda-process=true customer-lib-name="xxx.so"

のように存在しないパスを指定しても動いてしまいます。
( ので、『何もしない』GPU プラグインは、実はそのままでは動いているんだかエラーだか分からないのです。)

なお、gst-launch-1.0 は -v オプションで詳細が出ますが、それでも出力されませんでした。。

OpenCV のパスが通っていなかった。

また、今回 OpenCV を新しくインストールしなおしたため、デフォルトで /usr/local/lib へのパスが通っていないとうまく OpenCV を読み込んでくれません。
前述のエラーが出ない問題との合わせ技で、OpenCV が使われていないことに気づくのに時間がかかりました。。
パスの追加を行って問題解決。

まとめ

GPU で動作するプラグインを作成し、何もしないもの、簡単なフィルタを加えたもので動作確認を行いました。
あまり使われていないのか情報が少ないですが、CUDA の知識があれば色々カスタマイズできそうなので、とても便利に感じました。
一通りの動作を見ることができたので、入門記事としては完了です。今後、引き続き使っていきながら、新たな知見を得られたら情報公開していきたいです。


参照