DeepStream プラグイン入門2 ~独自のプラグインを構築する~

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 インストール済み

プラグインの動作を決める

「入門」なので、適当にフィルタかけるくらいでも良いのですが、せっかくなので OpenCV の機能を生かした処理にしてみます。
——こんな感じにしてみました。中心を起点にして、複数個分割した画像を右回り or 左回りでクルクル回します。

pic1

実装

今回のコードは 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

以下のように、想像通りの出力がされました。

02_rotation_3

分割数は dsrotate のパラメータ divide-count で変更可能にしているので、3 → 8 にしてみると、

03_rotation_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 サイズで出力できるものを使用 )

04_rotation_camera

6 分割で窓際の植木鉢を撮ってみましたが、何だかよくわからない映像になってしまいました。。
画像サイズが 640 × 480 と小さいのが効いているようで、意外とサクサク動いています。

まとめ

オリジナルの処理をDeepStreamに組み込んでみました。
コード上、処理を実際に行う部分が分かってきたので、いろんな処理を組み込めそうです。

今回までは NvBufSurfaceSyncForCpu と NvBufSurfaceSyncForDevice を使って、
『一旦 CPU 側に映像を持ってきて処理し、再び GPU 側に戻す』形式で進めていますが、やっぱりそのまま GPU だけで処理してみたいですね。
次回は GPU 側のみで処理を行う方法について調べてみます。