DeepStream プラグイン入門第2回。
前回 は DeepStream の簡単な紹介と、サンプルプラグインの簡単なカスタムを行ってみました。
今回は、前回の『何もしない』サンプルプラグインをベースにして、何らかの処理が入った独自のプラグインを構築し、実際にストリームに組み込んで動かしてみます。
シリーズ記事 :
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
プラグインの動作を決める
「入門」なので、適当にフィルタかけるくらいでも良いのですが、せっかくなので OpenCV の機能を生かした処理にしてみます。
——こんな感じにしてみました。中心を起点にして、複数個分割した画像を右回り or 左回りでクルクル回します。
実装
今回のコードは github 上に置きましたので、興味ある方はこちらで確認してみてください。
ISP-Kazuki-Nagasawa/deepstream_plugin_samples
前回作成した『何もしないプラグイン』である gst-dspluginsample を改造する形で構築します。
( 内部の構造体の名前などに使われている「PluginSample」を「Rotate」に変換した上で使います。)
まず、コアとなる回転処理は rotation.cpp & rotation.h で実装して、
( rotation.h より抜粋 )
class VideoRotator
{
public:
VideoRotator(float width, float height, int divide_count);
public:
void rotate(cv::Mat& image);
private:
float width_;
float height_;
int divide_count_;
};
- コンストラクタで対象となる画像の幅および高さ、分割数 (divide_count) を設定
- rotate 処理で入力画像を変換
とします。
このクラス VideoRotator をプラグインの処理である gstrotate.cpp & gstrotate.h から呼び出します。
プラグインに必要な変数を格納している _GstDsRotate 構造体に VideoRotator のポインタを追加し、
( gstdsrotate.h より抜粋 )
struct _GstDsRotate
{
GstBaseTransform base_trans;
( 中略 )
// Rotator
VideoRotator* rotator = nullptr;
};
動画毎フレームの処理である gst_dsrotate_transform_ip に VideoRotator の構築と実行処理を追加します。
( gstdsrotate.cpp より抜粋。gst_dsrotate_transform_ip 内。)
/* Cache the mapped data for CPU access */
NvBufSurfaceSyncForCpu (surface, frame_meta->batch_id, 0);
// Pointer to cv::Mat
in_mat =
cv::Mat (surface->surfaceList[frame_meta->batch_id].planeParams.height[0],
surface->surfaceList[frame_meta->batch_id].planeParams.width[0], CV_8UC4,
surface->surfaceList[frame_meta->batch_id].mappedAddr.addr[0],
surface->surfaceList[frame_meta->batch_id].planeParams.pitch[0]);
// Image size
float width = surface->surfaceList[frame_meta->batch_id].planeParams.width[0];
float height = surface->surfaceList[frame_meta->batch_id].planeParams.height[0];
// Video rotator
if (dsrotate->rotator == nullptr) {
dsrotate->rotator = new VideoRotator(width, height, dsrotate->divide_count);
}
dsrotate->rotator->rotate(in_mat);
/* Cache the mapped data for device access */
NvBufSurfaceSyncForDevice(surface, frame_meta->batch_id, 0);
確保した dsrotate->rotator ポインタの削除は、プラグインの終了時処理である gst_dsrotate_stop で行います。
static gboolean
gst_dsrotate_stop (GstBaseTransform * btrans)
{
GstDsRotate *dsrotate = GST_DSROTATE (btrans);
if (dsrotate->rotator != nullptr) {
delete dsrotate->rotator;
dsrotate->rotator = nullptr;
}
return TRUE;
}
これで目的の実装が完了です。
ビルドして実際に使ってみます。
$ make
$ sudo make install
使ってみる
前回同様、DeepStream のサンプル用動画で試してみます。
動画に対してプラグイン処理をかけ、表示するストリームを実行すると、
gst-launch-1.0 \
-e \
filesrc location = /opt/nvidia/deepstream/deepstream/samples/streams/sample_1080p_h264.mp4 ! \
qtdemux ! h264parse ! nvv4l2decoder ! m.sink_0 nvstreammux name=m width=1280 height=720 batch_size=1 ! \
nvvideoconvert ! 'video/x-raw(memory:NVMM), format=RGBA' ! \
dsrotate divide-count=3 ! \
nvegltransform ! nveglglessink sync=0
以下のように、想像通りの出力がされました。
分割数は dsrotate のパラメータ divide-count で変更可能にしているので、3 → 8 にしてみると、
8分割で表示されましたが、やっぱり処理は大変なようです (遅い)。
使ってみる (カメラ)
同じ仕組みで入力を USB カメラにしてみます。
ストリームの入力部分を変えて
gst-launch-1.0 \
-e \
v4l2src device=/dev/video0 ! 'video/x-raw, format=YUY2, width=640, height=480, framerate=30/1' ! \
nvvideoconvert ! 'video/x-raw(memory:NVMM), format=RGBA' ! \
m.sink_0 nvstreammux name=m width=640 height=480 batch_size=1 \
! nvvideoconvert ! 'video/x-raw(memory:NVMM), format=RGBA' ! \
dsrotate divide-count=6 ! \
nvegltransform ! nveglglessink sync=0
とすることで、USB カメラからの映像を同様に dsrotate で変換かけて出力するようになります。
( USB カメラは YUY2 フォーマット、640 × 480 サイズで出力できるものを使用 )
6 分割で窓際の植木鉢を撮ってみましたが、何だかよくわからない映像になってしまいました。。
画像サイズが 640 × 480 と小さいのが効いているようで、意外とサクサク動いています。
まとめ
オリジナルの処理をDeepStreamに組み込んでみました。
コード上、処理を実際に行う部分が分かってきたので、いろんな処理を組み込めそうです。
今回までは NvBufSurfaceSyncForCpu と NvBufSurfaceSyncForDevice を使って、
『一旦 CPU 側に映像を持ってきて処理し、再び GPU 側に戻す』形式で進めていますが、やっぱりそのまま GPU だけで処理してみたいですね。
次回は GPU 側のみで処理を行う方法について調べてみます。