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 インストール済み
- JetPack 4.4.1
GPU プラグインのサンプルを探す
DeepStream で GPU を使う方法はあまりメジャーじゃないのか、探すのに苦労しました。。
ようやく見つけた 参照 (1) の記事によると、「nvivafilter」というプラグインを使って、CUDA 実装されたライブラリを呼び出すようです。
「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 の知識があれば色々カスタマイズできそうなので、とても便利に感じました。
一通りの動作を見ることができたので、入門記事としては完了です。今後、引き続き使っていきながら、新たな知見を得られたら情報公開していきたいです。