英伟达jetson开发板中编写gstreamer插件,实现自己的YOLOv5目标检测(适用于小白快速上手)

什么是gstreamer插件?

简单的说,deepstream框架是英伟达jetson系列边缘设备中的加速框架,想要在该框架中运行自己的算法,需要编写gstreamer插件,因为在该框架中,视频流的处理是由一个个gstreamer插件构成,包括视频编解码,视频格式转换,算法推理,视频推流等插件,并且需要在deepstream app中将需要用到的插件连接起来,构成流水线来运行。gstreamer插件就相当于deepstream框架中的一个个模块,模块间仅有数据传输,互不影响。

如何编写gstreamer插件

首先我们需要找到deepstream文件夹下source/gst-plugins/gstdsexample.cpp和gstdsexample.h文件(模板代码),仿照其代码格式进行修改,此处采用在插件中调用算法的方式(之前试过分别写前处理、推理、后处理插件,再连接起来的方式,发现框架找不到这三个插件)

  1. 首先我们先来看gstdsexample.h文件
//定义你自己的头文件名称
#ifndef __GST_MYMETHOD_H__
#define __GST_MYMETHOD_H__

#include <gst/base/gstbasetransform.h>
#include <gst/video/video.h>

/* Open CV headers */
#pragma GCC diagnostic push
#if __GNUC__ >= 8
#pragma GCC diagnostic ignored "-Wclass-memaccess"
#endif
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#pragma GCC diagnostic pop

#include <cuda.h>
#include <cuda_runtime.h>
#include "nvbufsurface.h"
#include "nvbufsurftransform.h"
#include "gst-nvquery.h"
#include "gstnvdsmeta.h"

// mymethod_lib中放你自己算法的源码,然后在此处引入头文件进行调用
#include "mymethod_lib/mymethod_lib.h"
//1. det
#include "mymethod_lib/det/simple_yolo.hpp"

*/
/* Package and library details required for plugin_init */
/*插件描述信息,其中PACKAGE字段指定了插件的名称,通过该名称实现插件的调用*/
#define PACKAGE "mymethod"
#define VERSION "1.0"
#define LICENSE "Proprietary"
#define DESCRIPTION "my sot track method example plugin for integration with DeepStream on DGPU"
#define BINARY_PACKAGE "NVIDIA DeepStream 3rdparty IP integration example plugin"
#define URL "http://nvidia.com/"


G_BEGIN_DECLS
/* Standard boilerplate stuff    固定结构,声明插件实例与插件类*/
typedef struct _GstMyMethod GstMyMethod;
typedef struct _GstMyMethodClass GstMyMethodClass;

/* Standard boilerplate stuff    固定结构*/
#define GST_TYPE_MYMETHOD (gst_mymethod_get_type())
#define GST_MYMETHOD(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_MYMETHOD,GstMyMethod))
#define GST_MYMETHOD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_MYMETHOD,GstMyMethodClass))
#define GST_MYMETHOD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GST_TYPE_MYMETHOD, GstMyMethodClass))
#define GST_IS_MYMETHOD(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_MYMETHOD))
#define GST_IS_MYMETHOD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_MYMETHOD))
#define GST_MYMETHOD_CAST(obj)  ((GstMyMethod *)(obj))

//类的实例对象
struct _GstMyMethod
{
  GstBaseTransform base_trans;

  // Unique ID of the element. The labels generated by the element will be
  // updated at index `unique_id` of attr_info array in NvDsObjectParams.
  /*元素的唯一ID。元素生成的标签将在NvDsObjectParams中attr_info数组的索引“unique_id”处更新。*/
  guint unique_id;

  // Frame number of the current input buffer
  guint64 frame_num;

  // CUDA Stream used for allocating the CUDA task
  cudaStream_t cuda_stream;

  // Host buffer to store RGB data for use by algorithm
  //用于存储RGB数据以供算法使用的主缓冲区
  void *host_rgb_buf;

  // the intermediate scratch buffer for conversions RGBA
  //用于转换的中间暂存缓冲区RGBA
  NvBufSurface *inter_buf;

  // OpenCV mat containing RGB data
  cv::Mat *cvmat;
  
  // Input video info (resolution, color format, framerate, etc)
  GstVideoInfo video_info;

  // Resolution at which frames/objects should be processed
  gint processing_width;
  gint processing_height;

  // Flag which defince igpu/dgpu
  guint is_integrated;

  // Amount of objects processed in single call to algorithm
  guint batch_size;

  // GPU ID on which we expect to execute the task
  guint gpu_id;

// 下面是自己算法需要用到的一些变量或者参数,按自己的需求来写
  //1. det params
  std::shared_ptr<SimpleYolo::Infer> engine_v5s;
  
  gchar* model_name;

};

//类(固定写法,只需要改成自己的名字即可)
// Boiler plate stuff
struct _GstMyMethodClass
{
  GstBaseTransformClass parent_class;
};

GType gst_mymethod_get_type (void);

G_END_DECLS
#endif /* __GST_MYMETHOD_H__ */
  1. 然后再看gstdsexample.cpp是如何写的,代码较多,就不直接放全部代码
  2. 第一部分是添加头文件和定义一些需要的函数固定写法,只需要将mymethod改成自己的名字
//添加需要使用的头文件
#include <string.h>
#include <string>
#include <sstream>
#include <iostream>
#include <ostream>
#include <fstream>
#include "gstmymethod.h"
#include <sys/time.h>
#include <opencv2/opencv.hpp>
#include <opencv2/cudaimgproc.hpp>

GST_DEBUG_CATEGORY_STATIC (gst_mymethod_debug);
#define GST_CAT_DEFAULT gst_mymethod_debug
#define USE_EGLIMAGE 1
/* enable to write transformed cvmat to files */
/* #define MYMETHOD_DEBUG */
static GQuark _dsmeta_quark = 0;

// classes(此处写用目标检测算法时的类别)
static const char* cocolabels[] = {
    "army"
};

//设置插件属性的函数,通过属性可以对插件内部的变量进行赋值,方便调整一些参数
static void gst_mymethod_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec);
static void gst_mymethod_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec);
// 获取输入图像的信息
static gboolean gst_mymethod_set_caps (GstBaseTransform * btrans,
    GstCaps * incaps, GstCaps * outcaps);
// 资源初始化,如算法初始化,插件初始化等
static gboolean gst_mymethod_start (GstBaseTransform * btrans);
// 资源销毁
static gboolean gst_mymethod_stop (GstBaseTransform * btrans);
// 算法调用
static GstFlowReturn gst_mymethod_transform_ip (GstBaseTransform *
    btrans, GstBuffer * inbuf);
// 1. det attach metadata 将检测结果传给下游插件
static void attach_metadata_det(GstMyMethod * dsexample, NvDsFrameMeta *frame_meta,
    gdouble scale_ratio, SimpleYolo::BoxArray boxes);
  1. 第二部分是定义插件属性的默认值, 这部分的枚举变量和插件属性的默认值按需修改,其他为固定写法,只需要将mymethod改成自己的名字
/* Enum to identify properties 插件属性的枚举变量*/
enum
{
  PROP_GPU_DEVICE_ID,
  PROP_MODEL_NAME,
};

#define CHECK_NVDS_MEMORY_AND_GPUID(object, surface)  \
  ({ int _errtype=0;\
   do {  \
    if ((surface->memType == NVBUF_MEM_DEFAULT || surface->memType == NVBUF_MEM_CUDA_DEVICE) && \
        (surface->gpuId != object->gpu_id))  { \
    GST_ELEMENT_ERROR (object, RESOURCE, FAILED, \
        ("Input surface gpu-id doesnt match with configured gpu-id for element," \
         " please allocate input using unified memory, or use same gpu-ids"),\
        ("surface-gpu-id=%d,%s-gpu-id=%d",surface->gpuId,GST_ELEMENT_NAME(object),\
         object->gpu_id)); \
    _errtype = 1;\
    } \
    } while(0); \
    _errtype; \
  })


/* Default values for properties 插件属性默认值*/
#define DEFAULT_GPU_ID 0
#define DEFAULT_MODEL_NAME ""
#define RGB_BYTES_PER_PIXEL 3
#define RGBA_BYTES_PER_PIXEL 4

#define CHECK_NPP_STATUS(npp_status,error_str) do { \
  if ((npp_status) != NPP_SUCCESS) { \
    g_print ("Error: %s in %s at line %d: NPP Error %d\n", \
        error_str, __FILE__, __LINE__, npp_status); \
    goto error; \
  } \
} while (0)

#define CHECK_CUDA_STATUS(cuda_status,error_str) do { \
  if ((cuda_status) != cudaSuccess) { \
    g_print ("Error: %s in %s at line %d (%s)\n", \
        error_str, __FILE__, __LINE__, cudaGetErrorName(cuda_status)); \
    goto error; \
  } \
} while (0)

/* By default NVIDIA Hardware allocated memory flows through the pipeline. We
 * will be processing on this type of memory only. */
//设置插件sink和src端的连接方式与属性,这里设置数据存放在GPU,所以内存指定为"memory:NVMM"
#define GST_CAPS_FEATURE_MEMORY_NVMM "memory:NVMM"
static GstStaticPadTemplate gst_mymethod_sink_template =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
        (GST_CAPS_FEATURE_MEMORY_NVMM,
            "{ NV12, RGBA, I420 }")));

static GstStaticPadTemplate gst_mymethod_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE_WITH_FEATURES
        (GST_CAPS_FEATURE_MEMORY_NVMM,
            "{ NV12, RGBA, I420 }")));
            
/* Define our element type. Standard GObject/GStreamer boilerplate stuff  固定结构*/
#define gst_mymethod_parent_class parent_class
G_DEFINE_TYPE (GstMyMethod, gst_mymethod, GST_TYPE_BASE_TRANSFORM);
  1. 第三部分是配置函数的实现,基本是固定写法,其中一些属性按需添加,需要将mymethod改成自己的名字
/* Install properties, set sink and src pad capabilities, override the required
 * functions of the base class, These are common to all instances of the
 * element.
 */
// 插件的配置函数,配置子类的实现、插件属性、插件描述信息等
static void
gst_mymethod_class_init (GstMyMethodClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;
  GstBaseTransformClass *gstbasetransform_class;

  /* Indicates we want to use DS buf api */
  g_setenv ("DS_NEW_BUFAPI", "1", TRUE);

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;
  gstbasetransform_class = (GstBaseTransformClass *) klass;

  /* Overide base class functions  重构类的函数实现*/
  gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_mymethod_set_property);
  gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_mymethod_get_property);

  gstbasetransform_class->set_caps = GST_DEBUG_FUNCPTR (gst_mymethod_set_caps);
  gstbasetransform_class->start = GST_DEBUG_FUNCPTR (gst_mymethod_start);
  gstbasetransform_class->stop = GST_DEBUG_FUNCPTR (gst_mymethod_stop);

  //默认直通模式下,所以子类重构的是transform_ip函数
  gstbasetransform_class->transform_ip =
      GST_DEBUG_FUNCPTR (gst_mymethod_transform_ip);

  /* Install properties  注册插件属性*/
  g_object_class_install_property (gobject_class, PROP_GPU_DEVICE_ID,
      g_param_spec_uint ("gpu-id",
          "Set GPU Device ID",
          "Set GPU Device ID", 0,
          G_MAXUINT, 0,
          GParamFlags
          (G_PARAM_READWRITE |
              G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY)));

  g_object_class_install_property (gobject_class, PROP_MODEL_NAME,
    g_param_spec_string ("model-name", "v5 model name/Path",
        "Path/name to the model file for this instance of nvinfer",
        DEFAULT_MODEL_NAME,
        (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
            GST_PARAM_MUTABLE_PLAYING)));
  
  /* Set sink and src pad capabilities 设置sink和src的pad capabilities属性*/
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&gst_mymethod_src_template));
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&gst_mymethod_sink_template));

  /* Set metadata describing the element 设置插件描述信息*/
  gst_element_class_set_details_simple (gstelement_class,
      "MyMethod plugin",
      "MyMethod Plugin",
      "some functions (det, mut, sot)",
      "NVIDIA Corporation. Post on Deepstream for Tesla forum for any queries "
      "@ https://devtalk.nvidia.com/default/board/209/");
}
static void
gst_mymethod_init (GstMyMethod * mymethod)
{
  GstBaseTransform *btrans = GST_BASE_TRANSFORM (mymethod);

  /* We will not be generating a new buffer. Just adding / updating metadata. */
  //设置是否基于同一内存空间处理buffer,在直通模式下,inbuf和outbuf是同一内存空间的
  gst_base_transform_set_in_place (GST_BASE_TRANSFORM (btrans), TRUE);
  /* We do not want to change the input caps. Set to passthrough. transform_ip
   * is still called. */
  //设置是否是直通模式,直通模式下调用的是transform_ip函数,非直通模式下调用transform
  gst_base_transform_set_passthrough (GST_BASE_TRANSFORM (btrans), TRUE);

  /* Initialize all property variables to default values 设置插件属性默认值*/
  mymethod->gpu_id = DEFAULT_GPU_ID;
  mymethod->model_name = DEFAULT_MODEL_NAME;
 
  /* This quark is required to identify NvDsMeta when iterating through
   * the buffer metadatas */
  if (!_dsmeta_quark)
    _dsmeta_quark = g_quark_from_static_string (NVDS_META_STRING);
}

/* Function called when a property of the element is set. Standard boilerplate.
   设置插件属性信息(标准模板)*/
static void
gst_mymethod_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstMyMethod *mymethod = GST_MYMETHOD (object);
  switch (prop_id) {
    case PROP_GPU_DEVICE_ID:
      mymethod->gpu_id = g_value_get_uint (value);
      break;
    case PROP_MODEL_NAME:
      //g_free (mymethod->model_name);
      mymethod->model_name = g_value_dup_string (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

/* Function called when a property of the element is requested. Standard
 * boilerplate.
   获取插件属性信息(标准模板)*/
static void
gst_mymethod_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstMyMethod *mymethod = GST_MYMETHOD (object);

  switch (prop_id) {
    case PROP_GPU_DEVICE_ID:
      g_value_set_uint (value, mymethod->gpu_id);
      break;
    case PROP_MODEL_NAME:
      g_value_set_string (value, mymethod->model_name);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

  1. 第四部分是资源初始化和销毁函数以及获取图像信息函数的实现 ,只需添加自己算法的初始化代码即可,其他为固定写法,需要将mymethod改成自己的名字
/**
 * Initialize all resources and start the output thread
  初始化资源,如算法初始化*/
static gboolean
gst_mymethod_start (GstBaseTransform * btrans)
{
  GstMyMethod *mymethod = GST_MYMETHOD (btrans);
  NvBufSurfaceCreateParams create_params;
  MyMethodInitParams init_params =
      { mymethod->processing_width, mymethod->processing_height};

  GstQuery *queryparams = NULL;
  guint batch_size = 1;
  int val = -1;

  // 1.det
  int deviceid = 0;
  auto mode_name = SimpleYolo::mode_string(SimpleYolo::Mode::FP16);
  SimpleYolo::set_device(deviceid);

  std::string model_file_v5s = mymethod->model_name; //"yolov5s_dynamic.FP16.engine";
  mymethod->engine_v5s = SimpleYolo::create_infer(
          model_file_v5s,
          SimpleYolo::Type::V5,
          deviceid,
          0.4f,
          0.5f
      );
  if (mymethod->engine_v5s == nullptr) {
      std::cout << "init det model failed !";
      return 0;
  }

  /* Algorithm specific initializations and resource allocation. 算法特定的初始化和资源分配*/
 /*
  mymethod->mymethodlib_ctx = MyMethodCtxInit (&init_params);

  GST_DEBUG_OBJECT (mymethod, "ctx lib %p \n", mymethod->mymethodlib_ctx);
*/
  CHECK_CUDA_STATUS (cudaSetDevice (mymethod->gpu_id),
      "Unable to set cuda device");

  cudaDeviceGetAttribute (&val, cudaDevAttrIntegrated, mymethod->gpu_id);
  mymethod->is_integrated = val;
  mymethod->batch_size = 1;
  
  queryparams = gst_nvquery_batch_size_new ();
  if (gst_pad_peer_query (GST_BASE_TRANSFORM_SINK_PAD (btrans), queryparams)
      || gst_pad_peer_query (GST_BASE_TRANSFORM_SRC_PAD (btrans), queryparams)) {
    if (gst_nvquery_batch_size_parse (queryparams, &batch_size)) {
      mymethod->batch_size = batch_size;
    }
  }
  GST_DEBUG_OBJECT (mymethod, "Setting batch-size %d \n",
      mymethod->batch_size);
  gst_query_unref (queryparams);


  CHECK_CUDA_STATUS (cudaStreamCreate (&mymethod->cuda_stream),
      "Could not create cuda stream");

  if (mymethod->inter_buf)
    NvBufSurfaceDestroy (mymethod->inter_buf);
  mymethod->inter_buf = NULL;

  /* An intermediate buffer for NV12/RGBA to BGR conversion  will be
   * required. Can be skipped if custom algorithm can work directly on NV12/RGBA. 
     图像数据格式转换 NV12/RGBA to BGR  若自定义算法能在NV12/RGBA上处理,那可不用转换*/
  create_params.gpuId  = mymethod->gpu_id;
  create_params.width  = mymethod->processing_width;
  create_params.height = mymethod->processing_height;
  create_params.size = 0;
  create_params.colorFormat = NVBUF_COLOR_FORMAT_RGBA;
  create_params.layout = NVBUF_LAYOUT_PITCH;

  if(mymethod->is_integrated) {
    create_params.memType = NVBUF_MEM_DEFAULT;
  }
  else {
    create_params.memType = NVBUF_MEM_CUDA_PINNED;
  }

  if (NvBufSurfaceCreate (&mymethod->inter_buf, 1,
          &create_params) != 0) {
    GST_ERROR ("Error: Could not allocate internal buffer for mymethod");
    goto error;
  }

  /* Create host memory for storing converted/scaled interleaved RGB data */
  CHECK_CUDA_STATUS (cudaMallocHost (&mymethod->host_rgb_buf,
          mymethod->processing_width * mymethod->processing_height *
          RGB_BYTES_PER_PIXEL), "Could not allocate cuda host buffer");

  GST_DEBUG_OBJECT (mymethod, "allocated cuda buffer %p \n",
      mymethod->host_rgb_buf);

//开启opencv就需要设置该参数来保存opencv图像数据
  /* CV Mat containing interleaved RGB data. This call does not allocate memory.
   * It uses host_rgb_buf as data. */
  mymethod->cvmat =
      new cv::Mat (mymethod->processing_height, mymethod->processing_width,
      CV_8UC3, mymethod->host_rgb_buf,
      mymethod->processing_width * RGB_BYTES_PER_PIXEL);

  if (!mymethod->cvmat)
    goto error;

  GST_DEBUG_OBJECT (mymethod, "created CV Mat\n");

  return TRUE;
error:
  if (mymethod->host_rgb_buf) {
    cudaFreeHost (mymethod->host_rgb_buf);
    mymethod->host_rgb_buf = NULL;
  }

  if (mymethod->cuda_stream) {
    cudaStreamDestroy (mymethod->cuda_stream);
    mymethod->cuda_stream = NULL;
  }
  /*
  if (mymethod->mymethodlib_ctx)
    MyMethodCtxDeinit (mymethod->mymethodlib_ctx);
  return FALSE;
  */
}

/**
 * Stop the output thread and free up all the resources
 */
static gboolean
gst_mymethod_stop (GstBaseTransform * btrans)
{
  GstMyMethod *mymethod = GST_MYMETHOD (btrans);

  if (mymethod->inter_buf)
    NvBufSurfaceDestroy(mymethod->inter_buf);
  mymethod->inter_buf = NULL;

  if (mymethod->cuda_stream)
    cudaStreamDestroy (mymethod->cuda_stream);
  mymethod->cuda_stream = NULL;

#ifdef WITH_OPENCV
  delete mymethod->cvmat;
  mymethod->cvmat = NULL;
#endif

  mymethod->my_method = 0;

  if (mymethod->host_rgb_buf) {
    cudaFreeHost (mymethod->host_rgb_buf);
    mymethod->host_rgb_buf = NULL;
  }

  GST_DEBUG_OBJECT (mymethod, "deleted CV Mat \n");

  /* Deinit the algorithm library */
  //MyMethodCtxDeinit (mymethod->mymethodlib_ctx);
  //mymethod->mymethodlib_ctx = NULL;

  GST_DEBUG_OBJECT (mymethod, "ctx lib released \n");

  return TRUE;
}

/**
 * Called when source / sink pad capabilities have been negotiated.
   获取协商好的caps参数,并进行相应的处理。如获取输入图像的宽高等信息*/
static gboolean
gst_mymethod_set_caps (GstBaseTransform * btrans, GstCaps * incaps,
    GstCaps * outcaps)
{
  GstMyMethod *mymethod = GST_MYMETHOD (btrans);
  /* Save the input video information, since this will be required later. */
  gst_video_info_from_caps (&mymethod->video_info, incaps);

  //判断接收数据是否符合要求(这里是需要符合opencv的接收数据)
  /* requires RGBA format for blurring the objects in opencv */
  
    if (mymethod->video_info.finfo->format != GST_VIDEO_FORMAT_RGBA) {
    GST_ELEMENT_ERROR (mymethod, STREAM, FAILED,
        ("input format should be RGBA when using blur-objects property"), (NULL));
    goto error;
    }

  return TRUE;

error:
  return FALSE;
}

  1. 第五部分是 算法调用函数的实现,并将检测后的结果叠加在GstBuffer里面传递给下游插件;
/**
 * Called when element recieves an input buffer from upstream element.
   直通模式下,获取上游数据并进行算法处理后,将检测后的结果叠加在GstBuffer里面传递给下游。*/
   
static GstFlowReturn
gst_mymethod_transform_ip (GstBaseTransform * btrans, GstBuffer * inbuf)
{
  GstMyMethod *mymethod = GST_MYMETHOD (btrans);
  GstMapInfo in_map_info;
  GstFlowReturn flow_ret = GST_FLOW_ERROR;
  gdouble scale_ratio = 1.0;
  MyMethodOutput *output;
  //MyMut_info mut_info;
  NvBufSurface *surface = NULL;
  NvDsBatchMeta *batch_meta = NULL;
  NvDsFrameMeta *frame_meta = NULL;
  NvDsMetaList * l_frame = NULL;
  guint i = 0;

  mymethod->frame_num++;
  CHECK_CUDA_STATUS (cudaSetDevice (mymethod->gpu_id),
      "Unable to set cuda device");
  
  memset (&in_map_info, 0, sizeof (in_map_info));
  if (!gst_buffer_map (inbuf, &in_map_info, GST_MAP_READ)) {
    g_print ("Error: Failed to map gst buffer\n");
    goto error;
  }

  nvds_set_input_system_timestamp (inbuf, GST_ELEMENT_NAME (mymethod));
  surface = (NvBufSurface *) in_map_info.data;
  GST_DEBUG_OBJECT (mymethod,
      "Processing Frame %" G_GUINT64_FORMAT " Surface %p\n",
      mymethod->frame_num, surface);

  if (CHECK_NVDS_MEMORY_AND_GPUID (mymethod, surface))
    goto error;

  //从inbuf里面获取nvidia的streamMeta数据结构
  batch_meta = gst_buffer_get_nvds_batch_meta (inbuf);
  if (batch_meta == nullptr) {
    GST_ELEMENT_ERROR (mymethod, STREAM, FAILED,
        ("NvDsBatchMeta not found for input buffer."), (NULL));
    return GST_FLOW_ERROR;
  }
  
 /* Using object crops as input to the algorithm. The objects are detected by
     * the primary detector 使用裁剪的对象作为算法的输入。对象由主探测器探测*/
    if(!mymethod->is_integrated) 
    {
      if (!(surface->memType == NVBUF_MEM_CUDA_UNIFIED || surface->memType == NVBUF_MEM_CUDA_PINNED))
      {
        GST_ELEMENT_ERROR (mymethod, STREAM, FAILED,
            ("%s:need NVBUF_MEM_CUDA_UNIFIED or NVBUF_MEM_CUDA_PINNED memory for opencv",__func__), (NULL));
        return GST_FLOW_ERROR;
      }
    }
	
	//std::thread change_id_thread(change_id);
  	//change_id_thread.detach();
    for (l_frame = batch_meta->frame_meta_list; l_frame != NULL;
      l_frame = l_frame->next)
    {
      //1.获取每一帧图片
      frame_meta = (NvDsFrameMeta *) (l_frame->data);
      //auto start_=std::chrono::steady_clock::now();
      cv::Mat in_mat;

      //2. 图片转为opencv类型
      /* 2.1 Map the buffer so that it can be accessed by CPU  映射缓冲区,以便CPU可以访问它 */
      if (surface->surfaceList[frame_meta->batch_id].mappedAddr.addr[0] == NULL){
        if (NvBufSurfaceMap (surface, frame_meta->batch_id, 0, NVBUF_MAP_READ_WRITE) != 0){
          GST_ELEMENT_ERROR (mymethod, STREAM, FAILED,
              ("%s:buffer map to be accessed by CPU failed", __func__), (NULL));
          return GST_FLOW_ERROR;
        }
      }

      /*2.2 Cache the mapped data for CPU access   缓存映射数据以供CPU访问*/
      if(mymethod->inter_buf->memType == NVBUF_MEM_SURFACE_ARRAY)
        NvBufSurfaceSyncForCpu (surface, frame_meta->batch_id, 0);

      /*2.3 给in_mat赋值,修改in_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]);
      
      cv::Mat input_mat;
      cv::cvtColor (in_mat, input_mat, cv::COLOR_RGBA2BGR);
      //printf("in_mat:(%d, %d, %d)  =================\n", in_mat.cols, in_mat.rows, in_mat.channels());
   // 调用目标检测算法
     auto start_=std::chrono::steady_clock::now();
     std::vector<SimpleYolo::Box> vec_obj;
     SimpleYolo::BoxArray boxes;
     boxes = mymethod->engine_v5s->commit(input_mat).get();
     // 将检测结果添加到帧数据并传递给下游插件
     attach_metadata_det(mymethod, frame_meta, scale_ratio, boxes);
  
     auto end_=std::chrono::steady_clock::now();
     std::chrono::duration<double, std::milli> elapsed = end_ - start_;
     std::cout << "time_detect:" << elapsed.count() << std::endl;

     /* Cache the mapped data for device access 缓存映射数据以供设备访问*/
     if(mymethod->inter_buf->memType == NVBUF_MEM_SURFACE_ARRAY) 
        NvBufSurfaceSyncForDevice (surface, frame_meta->batch_id, 0);

//如果需要保存图片,则可以在这里进行
#ifdef WITH_OPENCV
#ifdef MYMETHOD_DEBUG
        /* Use openCV to remove padding and convert RGBA to BGR. Can be skipped if
        * algorithm can handle padded RGBA data. */
#if (CV_MAJOR_VERSION >= 4)
        cv::cvtColor (in_mat, *mymethod->cvmat, cv::COLOR_RGBA2BGR);
#else
        cv::cvtColor (in_mat, *mymethod->cvmat, CV_RGBA2BGR);
#endif
        /* used to dump the converted mat to files for debug */
        static guint cnt = 0;
        cv::imwrite("out_" + std::to_string (cnt) + ".jpeg", *mymethod->cvmat);
        cnt++;
#endif
#endif

    }
  
  flow_ret = GST_FLOW_OK;

error:

  nvds_set_output_system_timestamp (inbuf, GST_ELEMENT_NAME (mymethod));
  gst_buffer_unmap (inbuf, &in_map_info);
  return flow_ret;
}
  1. 第六部分是 attach_metadata_det函数的具体实现 ,这个函数中可以设置目标框的线条、字体等信息;
/**
 * 1. 将检测结果画到输出图上
*/
static void attach_metadata_det(GstMyMethod * dsexample, NvDsFrameMeta *frame_meta,
    gdouble scale_ratio, SimpleYolo::BoxArray boxes)
{
  gint output_num = boxes.size();
  if(output_num < 1)
    return;

  NvDsBatchMeta *batch_meta = frame_meta->base_meta.batch_meta;
  NvDsObjectMeta *object_meta = NULL;
  NvDsObjectMetaList *object_meta_list = frame_meta->obj_meta_list;
  static gchar font_name[] = "Serif";
  GST_DEBUG_OBJECT (dsexample, "Attaching metadata %d\n", output_num);
  //g_print("Attaching metadata %d\n", output_num);
  
  //清除infer存入的数据
  //nvds_clear_obj_meta_list(frame_meta, object_meta_list);

  for (auto& obj : boxes) {
    object_meta = nvds_acquire_obj_meta_from_pool(batch_meta);
    NvOSD_RectParams & rect_params = object_meta->rect_params;
    NvOSD_TextParams & text_params = object_meta->text_params;

    /* Assign bounding box coordinates */
    rect_params.left = obj.left;
    rect_params.top = obj.top;
    rect_params.width = obj.right - obj.left;
    rect_params.height = obj.bottom - obj.top;

    /* Semi-transparent yellow background */
    rect_params.has_bg_color = 0;
    rect_params.bg_color = (NvOSD_ColorParams) {1, 1, 0, 0.4};
    /* Red border of width 6 */
    rect_params.border_width = 3;
    rect_params.border_color = (NvOSD_ColorParams) {1, 0, 0, 1};

    /* Scale the bounding boxes proportionally based on how the object/frame was
    * scaled during input */
    rect_params.left /= scale_ratio;
    rect_params.top /= scale_ratio;
    rect_params.width /= scale_ratio;
    rect_params.height /= scale_ratio;

    object_meta->object_id = obj.class_label;
    gchar strid[10];
    snprintf (strid, 10, "%s", cocolabels[obj.class_label]);
    g_strlcpy (object_meta->obj_label, strid, MAX_LABEL_SIZE);
    /* display_text required heap allocated memory */
    text_params.display_text = g_strdup (strid);
    /* Display text above the left top corner of the object */
    text_params.x_offset = rect_params.left;
    text_params.y_offset = rect_params.top - 10;
    /* Set black background for the text */
    text_params.set_bg_clr = 1;
    text_params.text_bg_clr = (NvOSD_ColorParams) {0, 0, 0, 1};
    /* Font face, size and color */
    text_params.font_params.font_name = font_name;
    text_params.font_params.font_size = 11;
    text_params.font_params.font_color = (NvOSD_ColorParams) {1, 1, 1, 1};

    //将修改后的osd参数放到添加到帧数据里
    nvds_add_obj_meta_to_frame(frame_meta, object_meta, NULL);
    frame_meta->bInferDone = TRUE;

  }
}
  1. 最后则是 插件信息设置 ,将mymethod改为自己的名字即可。
/**
 * Boiler plate for registering a plugin and an element.
 */
static gboolean
mymethod_plugin_init (GstPlugin * plugin)
{
  GST_DEBUG_CATEGORY_INIT (gst_mymethod_debug, "mymethod", 0,
      "mymethod plugin");

  return gst_element_register (plugin, "mymethod", GST_RANK_PRIMARY,
      GST_TYPE_MYMETHOD);
}

GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
    GST_VERSION_MINOR,
    nvdsgst_mymethod,
    DESCRIPTION, mymethod_plugin_init, DS_VERSION, LICENSE, BINARY_PACKAGE, URL)

编写cmake或者makefile进行编译

上面是整个插件的代码编写,makefile或者cmake中需要包含opencv、deepstream、cuda等需要用到的路径。具体写法这里就不再详细讲解,网上有相关的教程。
注意!!!!!!
要将编译生成的so文件复制到deepstream文件夹下的lib/gst-plugins/文件夹中去,否则deepstream框架检测不到插件。
复制过去后可以通过以下命令查看是否检测到该插件

 gst-inspect-1.0 插件名称

在这里插入图片描述

编写deepstream app代码,将其他插件与自定义插件相连接,构成完整的pipeline。

到这里仅仅是完成了自定义插件的编写,要使用它还需要编写deepstream app代码,然后通过deepstream app代码来运行这个pipeline。

插件目录结构如下:

在这里插入图片描述
mymethod_lib中放自己检测算法的源码

在这里插入图片描述

具体怎么实现pipeline的构建,请听下回分解(工作之余抽空再写~)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值