实现带有 OpenCV 集成示例的自定义 GStreamer 插件#

DeepStream SDK 支持一种机制,通过修改示例插件 (gst-dsexample) 在参考应用程序中添加第三方或自定义算法。该插件的源代码位于 SDK 的 sources/gst-plugins/gst-dsexample 目录中。此插件是为 GStreamer 1.14.1 编写的,但与较新版本的 GStreamer 兼容。此插件派生自 GstBaseTransform 类:https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gstreamer-libs/html/GstBaseTransform.html

注意

要启用 OpenCV 功能,请在插件 Makefile 中使用标志 WITH_OPENCV=1 编译 dsexample 插件。

示例插件的描述:gst-dsexample#

GStreamer 示例插件 (gst-dsexample) 演示了以下内容

  • 处理整个帧,并在需要时进行缩小/颜色转换。

  • 处理主检测器检测到的对象,具体来说,从帧中裁剪这些对象,然后处理裁剪后的对象。

  • 使用 OpenCV 原位修改缓冲区帧内容

  • 包含该插件的两个版本。请参阅插件的 Makefile 和 README 以在它们之间切换

    • 简单 (gstdsexample.cpp) - 顺序预处理和处理

    • 优化 (gstdsexample_optimized.cpp) - 并行批处理预处理和处理

此版本包含一个简单的静态库 dsexample_lib,该库演示了自定义库与此 Gstreamer 插件之间的接口。该库生成 Obj_label 形式的简单标签。该库实现了以下函数

  • DsExampleCtxInit — 初始化自定义库

  • DsExampleCtxDeinit — 反初始化自定义库

  • DsExampleProcess – 处理输入帧

GStreamer 插件本身是一个标准的就地转换插件。由于它不生成新缓冲区,而只是添加/更新现有元数据,因此该插件实现了就地转换。一些代码是标准的 GStreamer 插件样板代码(例如,plugin_initclass_initinstance_init)。其他感兴趣的函数如下

GstBaseTransfrom 类函数#

  • start — 获取资源,分配内存,初始化示例库。

  • stop — 反初始化示例库,并释放资源和内存。

  • set_caps — 获取流经此元素的视频的功能(即分辨率、颜色格式、帧速率)。可以在此处完成依赖于输入视频格式的分配/初始化。

  • transform_ip — 在简单版本中实现。当插件从上游元素接收到缓冲区时调用。

    • 查找主检测器的元数据。

    • 使用 get_converted_mat 预处理帧/对象裁剪,以获取推送到库所需的缓冲区。将数据推送到示例库。弹出示例库输出。

    • 使用 attach_metadata_full_frameattach_metadata_object 附加/更新元数据。

    • 或者,就地修改帧内容,以使用 blur_objects 模糊对象。

  • submit_input_buffer — 在优化版本中实现。当插件从上游元素接收到缓冲区时调用。与 gst_dsexample_output_loop 并行工作以提高性能。

    • 查找主检测器的元数据。

    • 创建要预处理的帧/对象批次。预处理批次并将预处理后的输出推送到处理线程。

    • 在前一批次上预处理时,处理线程处理较旧的批次。

其他支持函数#

  • get_converted_mat — 缩放、转换或裁剪输入缓冲区,可以是完整帧,也可以是对象(基于其在主检测器元数据中的坐标)。

  • attach_metadata_full_frame — 显示插件如何为其插件检测到的对象附加自己的元数据。

  • attach_metadata_object — 显示插件如何更新主检测器检测到的对象的标签。

  • blur_objects — 就地修改缓冲区帧内容,以使用 OpenCV GaussianBlur 模糊对象。在 dGPU 上运行时,请确保插件的输入内存类型为 NVBUF_MEM_CUDA_UNIFIED

  • gst_dsexample_output_loop — 与 submit_input_buffer 并行工作以提高性能。

    • 等待批次的预处理完成

    • 使用 dsexample_lib API 处理批次

    • 使用 attach_metadata\_* 函数之一附加输出

注意

在 Jetson 设备上,自定义 GStreamer 插件必须导出环境变量 DS_NEW_BUFAPI 并将其值设置为 1。有关插件 (Gst-dsexample) 中的示例,请参阅 gst_dsexample_class_init()

启用和配置示例插件#

预编译的 deepstream-app 二进制文件已具有解析配置并将示例元素添加到管道的功能。要启用和配置插件,请将以下部分添加到现有配置文件(例如,source4_720p_dec_infer-resnet_tracker_sgie_tiled_display_int8.txt

[ds-example]
enable=1
processing-width=640
processing-height=480
full-frame=0
blur-objects=0
unique-id=15

在自定义应用程序/管道中使用示例插件#

示例插件可以在 gst-launch 管道中使用。管道也可以在自定义应用程序中构建。

构建用于在全帧模式下运行插件的管道 构建用于在全帧模式下运行插件的管道,命令如下。

  • 对于 Jetson

    $ gst-launch-1.0 filesrc location= <mp4-file> ! qtdemux ! h264parse ! nvv4l2decoder ! m.sink_0 nvstreammux name=m batch-size=1 width=1280 height=720 ! nvvideoconvert ! dsexample full-frame=1 <other-properties> ! nvdsosd ! nv3dsink
    
  • 对于 Tesla

    $ gst-launch-1.0 filesrc location= <mp4-file> ! qtdemux ! h264parse ! nvv4l2decoder ! m.sink_0 nvstreammux name=m batch-size=1 width=1280 height=720 ! nvvideoconvert ! dsexample full-frame=1 <other-properties> ! nvdsosd ! nveglglessink
    

构建用于运行插件以处理主模型检测到的对象的管道 构建用于运行插件以处理主模型检测到的对象的管道,命令如下。

  • 对于 Jetson

    $ gst-launch-1.0 filesrc location= <mp4-file> ! qtdemux ! h264parse ! nvv4l2decoder ! m.sink_0 nvstreammux name=m batch-size=1 width=1280 height=720 ! nvinfer config-file-path= <primary-detector-config> ! nvvideoconvert ! dsexample full-frame=0 <other-properties> ! nvdsosd ! nv3dsink
    
  • 对于 Tesla

    $ gst-launch-1.0 filesrc location= <mp4-file> ! qtdemux ! h264parse ! nvv4l2decoder ! m.sink_0 nvstreammux name=m batch-size=1 width=1280 height=720 ! nvinfer config-file-path= <primary-detector-config> ! nvvideoconvert ! dsexample full-frame=0 <other-properties> ! nvdsosd ! nveglglessink
    

构建用于运行插件以模糊主模型检测到的对象的管道 构建用于运行插件以模糊主模型检测到的对象的管道,命令如下

  • 对于 Jetson

    $ gst-launch-1.0 filesrc location= <mp4-file> ! qtdemux ! h264parse ! nvv4l2decoder ! m.sink_0 nvstreammux name=m batch-size=1 width=1280 height=720 ! nvinfer config-file-path= <primary-detector-config> ! nvvideoconvert ! 'video/x-raw(memory:NVMM), format=RGBA' ! dsexample full-frame=0 blur-objects=1 ! nvdsosd ! nv3dsink
    
  • 对于 Tesla

    $ gst-launch-1.0 filesrc location= <mp4-file> ! qtdemux ! h264parse ! nvv4l2decoder ! m.sink_0 nvstreammux name=m batch-size=1 width=1280 height=720 ! nvinfer config-file-path= <primary-detector-config> ! nvvideoconvert nvbuf-memory-type= nvbuf-mem-cuda-unified  ! 'video/x-raw(memory:NVMM), format=RGBA' ! dsexample full-frame=0 blur-objects=1 ! nvdsosd ! nveglglessink
    

在示例插件中实现自定义逻辑#

要在插件中实现自定义逻辑,请将以下函数调用列表替换为任何其他自定义库的相应函数。

DsExampleCtxInit
DsExampleCtxDeinit
DsExampleProcess
blur_objects

根据库的输入要求,get_converted_mat 也可能需要修改。

为示例插件添加 NVTX API#

与其他 DeepStreamSDK GStreamer 插件一样,NVTX API 也可以添加到自定义插件中。有关这些 API 的更多信息,请参见 https://docs.nvda.net.cn/gameworks/content/gameworkslibrary/nvtx/nvidia_tools_extension_library_nvtx.htm。按照以下步骤为自定义插件添加 NVTX API

  1. 在插件的源代码中包含 nvtx3/nvToolsExt.h 标头。

  2. 要测量范围,通常使用两个 API

    • nvtxRangePushA (context) - 此组件/插件的分析开始的点。

    • nvtxRangePop () - 此组件/插件的分析停止的点。

  3. 确保标记的放置位置使得插件的核心功能在上述两个 API 之间执行。这将准确地了解延迟。

  4. 为自定义插件运行 NSight,以获取有关在这两个标记之间运行的任务的信息。

在 OpenCV 中访问 NvBufSurface 内存#

NvBufSurface 中的 CUDA 和 CPU 内存可以通过 OpenCV 的 cv::cuda::GpuMatcv::Mat 接口访问。在这种情况下,NvBufSurface 可以与 OpenCV 中实现的任何计算机视觉算法一起使用。以下代码片段显示了如何在 OpenCV 中访问和使用 NvBufSurface 的 CUDA 内存。

cv::cuda::GpuMat gpuMat;
const int aDstOrder[] = {2,0,1,3};
unsigned int index = 0;   // Index of the buffer in the batch.
unsigned int width, height; // set width and height of buffer
NvBufSurface *input_buf; // Pointer to input NvBufSurface

gpuMat = cv::cuda::GpuMat(height, width, CV_8UC4,
(void *)input_buf->surfaceList[index].dataPtr);

 gpuMat = cv::cuda::GpuMat(height, width, CV_8UC4,
     (void *) input_buf->surfaceList[index].dataPtr,
      input_buf->surfaceList[index].pitch);
cv::cuda::swapChannels(gpuMat, aDstOrder);

在 Jetson 平台上,如果 NvBufSurface 的内存类型为 NVBUF_MEM_SURFACE_ARRAY,则应先通过 CUDA-EGL 互操作将其转换为 CUDA,然后再在 OpenCV 中访问它。请参考 sources/gst-plugins/gst-dsexample/gstdsexample.cpp 以访问 OpenCV 矩阵 (cv::Mat) 中的 NvBufSurface 内存。以下是所需的步骤

  1. 使用 NvBufSurfaceMapEglImage() 从 NvBufSurface 创建 EGL 图像

  2. 使用 cuGraphicsEGLRegisterImage() 在 cuda 中注册 EGL 图像

  3. 使用 cuGraphicsResourceGetMappedEglFrame() 映射 EGL 帧以获取 cuda 指针

有关更多详细信息,请参阅文件 /opt/nvidia/deepstream/deepstream/sources/gst-plugins/gst-nvinfer/gstnvinfer_allocator.cpp 中的 gst_nvinfer_allocator_alloc。