DeepStream结合OpenCV4实现视频的分析和截图(一)

目录

 

安装环境

Deepstream-test4配置文件改写

调用OpenCV截图

存疑

其他收获


前言

本文实现基于test4,基于deepstream-app的更改和代码更新见DeepStream结合OpenCV4实现视频的分析和截图(二)

安装环境

Ubuntu:18.04.1

DeepStream:SDK 4.0.1

CUDA:10.1

OpenCV:4.1

首先,需要在linux上安装opencv。

sudo apt-get install libopencv-dev

参考:https://www.cnblogs.com/chenzhen0530/p/12109868.html

需要注意的是,要把ffmpeg重新编译安装,否则在make时,会报错,往前翻寻找报错信息,是一个和video相关的报错。

Deepstream-test4配置文件改写

我使用的例子是deepstream-test4。原因是涉及了kafka,相对test5看起来也简单些。在test4上我做了修改。

首先,test4如果在vscode里调试,会遇到写launch.json文件时arg参数的问题。下面是我在vscode github上的提问。

https://github.com/microsoft/vscode/issues/93166#issuecomment-604327721

即deepstream要求--conn-str="add;9092;topic",这个必须一起输入进去,在终端输入是没问题的,但是在vscode里调试,设置launch.json的args时,会省略掉分号后面的所有内容。如果哪位大佬解决了这个问题,可以联系我。

为了解决上面这个问题,我写了个配置文件。顺便也要重新定义一下输入,包括截图时间、人数、kafka连接地址、输入源等。

下面是我改写的配置文件代码。可以作为模板使用。

//**************** my function to parse config file *********//
//my config define
#define CONFIG_GROUP_KAFKA "kafka"
#define CONFIG_GROUP_KAFKA_MSG_BROKER_CONN_STR "conn-str"
#define CONFIG_GROUP_KAFKA_MSG_BROKER_TOPIC "topic"
#define CONFIG_GROUP_ALARM "alarm"
#define CONFIG_GROUP_ALARM_INTERVAL "interval"
#define CONFIG_GROUP_ALARM_NUMBER "number"
GST_DEBUG_CATEGORY (APP_CFG_PARSER_CAT);

#define CHECK_ERROR(error) \
    if (error) { \
        GST_CAT_ERROR (APP_CFG_PARSER_CAT, "%s", error->message); \
        goto done; \
    }

typedef struct 
{
  /* data */
  gchar *kafka_conn_str;
  gchar *kafka_topic;
}myKafka;

typedef struct 
{
  guint interval;      //config time interval for screenshot
  guint number;  //number of person. It will cause alarm.
}myalarm;


typedef struct
{
  /* data */
  myKafka msg_broker;  //config kafka address and topic
  myalarm alarm;  // alarm info.
}myConfig;

static gboolean parse_kafka(myConfig *config, GKeyFile *key_file)
{
  gboolean ret = FALSE;
  gchar **keys = NULL;
  gchar **key = NULL;
  GError *error = NULL;

  keys = g_key_file_get_keys (key_file, CONFIG_GROUP_KAFKA, NULL, &error);
  CHECK_ERROR (error);
  for(key=keys;*key;key++)
  {
    if(!g_strcmp0 (*key, CONFIG_GROUP_KAFKA_MSG_BROKER_CONN_STR)) //address
    {
      config->msg_broker.kafka_conn_str = 
    g_key_file_get_string (key_file, CONFIG_GROUP_KAFKA,
      CONFIG_GROUP_KAFKA_MSG_BROKER_CONN_STR, &error);
    CHECK_ERROR (error);
    }
    else if (!g_strcmp0 (*key, CONFIG_GROUP_KAFKA_MSG_BROKER_TOPIC)) // topic
    {
      config->msg_broker.kafka_topic = 
      g_key_file_get_string (key_file, CONFIG_GROUP_KAFKA,
        CONFIG_GROUP_KAFKA_MSG_BROKER_TOPIC, &error);
      CHECK_ERROR (error);
    }  
    else {
      NVGSTDS_WARN_MSG_V ("Unknown key '%s' for group [%s]", *key,
                          CONFIG_GROUP_KAFKA);
    }
  }

  ret = TRUE;
done:
  if (error) {
    g_error_free (error);
  }
  if (keys) {
    g_strfreev (keys);
  }
  if (!ret) {
    NVGSTDS_ERR_MSG_V ("%s failed", __func__);
  }
  return ret;
}

static gboolean parse_arlam(myConfig *config, GKeyFile *key_file)
{
  gboolean ret = FALSE;
  gchar **keys = NULL;
  gchar **key = NULL;
  GError *error = NULL;

  config->alarm.interval = 10;  // default interval to screenshot

  keys = g_key_file_get_keys (key_file, CONFIG_GROUP_ALARM, NULL, &error);
  CHECK_ERROR (error);
  for(key=keys;*key;key++)
  {
    if(!g_strcmp0 (*key, CONFIG_GROUP_ALARM_INTERVAL)) //interval
    {
      config->alarm.interval = 
    g_key_file_get_integer (key_file, CONFIG_GROUP_ALARM,           
      CONFIG_GROUP_ALARM_INTERVAL, &error);        // it must be integer rather g_key_file_get_string
    CHECK_ERROR (error);
    }
    else if (!g_strcmp0 (*key, CONFIG_GROUP_ALARM_NUMBER)) // number of person
    {
      config->alarm.number = 
      g_key_file_get_integer (key_file, CONFIG_GROUP_ALARM,
        CONFIG_GROUP_ALARM_NUMBER, &error);
      CHECK_ERROR (error);
    }  
    else {
      NVGSTDS_WARN_MSG_V ("Unknown key '%s' for group [%s]", *key,
                          CONFIG_GROUP_ALARM);
    }
  }

  ret = TRUE;
done:
  if (error) {
    g_error_free (error);
  }
  if (keys) {
    g_strfreev (keys);
  }
  if (!ret) {
    NVGSTDS_ERR_MSG_V ("%s failed", __func__);
  }
  return ret;
}

gboolean
parse_config_file (myConfig *config, gchar *cfg_file_path)
{
  GKeyFile *my_cfg_file = g_key_file_new ();
  GError *error = NULL;
  gboolean ret = FALSE;
  gchar **groups = NULL;
  gchar **group;

  if (!APP_CFG_PARSER_CAT) {
    GST_DEBUG_CATEGORY_INIT (APP_CFG_PARSER_CAT, "CFG_PARSER", 0, NULL);
  }

  if (!g_key_file_load_from_file (my_cfg_file, cfg_file_path, G_KEY_FILE_NONE,
          &error)) {
    GST_CAT_ERROR (APP_CFG_PARSER_CAT, "Failed to load uri file: %s",
        error->message);
    goto done;
  }
  groups = g_key_file_get_groups (my_cfg_file, NULL);

  for (group = groups; *group; group++) {
    gboolean parse_err = FALSE;
    GST_CAT_DEBUG (APP_CFG_PARSER_CAT, "Parsing group: %s", *group);
    // parsing kafka broker
    if (!g_strcmp0 (*group, CONFIG_GROUP_KAFKA)) {
      parse_err = !parse_kafka (config, my_cfg_file);
    } 
    // parsing alarm 
    if (!g_strcmp0 (*group, CONFIG_GROUP_ALARM)){
      parse_err = !parse_arlam (config, my_cfg_file);
    }
    if (parse_err) {
      GST_CAT_ERROR (APP_CFG_PARSER_CAT, "Failed to parse '%s' group", *group);
      goto done;
    }

  }

  ret = TRUE;
done:
  if (my_cfg_file) {
    g_key_file_free (my_cfg_file);
  }

  if (groups) {
    g_strfreev (groups);
  }

  if (error) {
    g_error_free (error);
  }
  if (!ret) {
    NVGSTDS_ERR_MSG_V ("%s failed", __func__);
  }
  return ret;
}
//**************************************************************//

调用OpenCV截图

终于到主题了。感谢这位博主提供的思路。https://blog.csdn.net/qq_32220889/article/details/102995841

博客中博主说了一句

试了很多方法,最后解决了,基本思想就是:删掉 nvosd,在 nvvidconv 的 src 上或在 sink 的 sink 上通过 gst_pad_add_probe 函数添加 probe,然后再 probe 回调中处理帧。

他踩过的坑我,以及论坛中别人踩过的坑,我很幸运地全踩个遍。最终发现,不用删掉nvosd组件,即原来构建的pipeline无需做修改。添加一个cudaMemcpy。原因很简单,就是直接从NvBufSurface过来的数据是不能直接访问的。

下面这段开始,是我踩坑记录,想了解的可以看一下,想奔结果的,直接跳过!

===========================================我是踩坑分割线========================================

或者NvBufSurfaceMap + NvBufSurfaceSyncForCpu。但是为什么会报"mapping of memory type not supported"呢?意思是说内存映射类型不支持,就去官网找这个函数https://docs.nvidia.com/metropolis/deepstream/4.0/dev-guide/DeepStream_Development_Guide/baggage/group__ee__nvbufsurface.html,加上debug,发现我的类型是NVBUF_MEM_DEFAULT,而官方写的很清楚,我没在jetson上跑,所以类型只能是NVBUF_MEM_CUDA_UNIFIED。如下图

于是我强制转换了类型。原来的错是不报了,但是在NvBufSurfaceSyncForCpu时又报错了,查看文档,还是类型问题。看来耍小聪明是不行滴。从下图中可以看出,无论是NvBufSurfaceSyncForCpu还是NvBufSurfaceSyncForDevice要求的类型都是针对Jetson的。纳尼?那我怎么转也没用。转了也不认!

========================================踩坑爬出来啦==============================================

参考这个论坛官方回复

https://forums.developer.nvidia.com/t/access-frame-pointer-in-deepstream-app/79838/24?u=jiejing_ma

参考完直接上我的代码

int write_frame(GstBuffer *buf)
{
  NvDsMetaList * l_frame = NULL;
  NvDsMetaList * l_user_meta = NULL;
  // Get original raw data
  GstMapInfo in_map_info;
  char* src_data = NULL;
  if (!gst_buffer_map (buf, &in_map_info, GST_MAP_READ)) {
        g_print ("Error: Failed to map gst buffer\n");
        gst_buffer_unmap (buf, &in_map_info);
        return GST_PAD_PROBE_OK;
    }
  NvBufSurface *surface = (NvBufSurface *)in_map_info.data;
  NvDsBatchMeta *batch_meta = gst_buffer_get_nvds_batch_meta (buf);
  l_frame = batch_meta->frame_meta_list; 
  if (l_frame) {
    NvDsFrameMeta *frame_meta = (NvDsFrameMeta *) (l_frame->data);
    /* Validate user meta */
    src_data = (char*) malloc(surface->surfaceList[frame_meta->batch_id].dataSize);
    if(src_data == NULL) {
        g_print("Error: failed to malloc src_data \n");
    }
#ifdef PLATFORM_TEGRA
    NvBufSurfaceMap (surface, -1, -1, NVBUF_MAP_READ);
    NvBufSurfacePlaneParams *pParams = &surface->surfaceList[frame_meta->batch_id].planeParams;
    unsigned int offset = 0;
    for(unsigned int num_planes=0; num_planes < pParams->num_planes; num_planes++){
        if(num_planes>0)
            offset += pParams->height[num_planes-1]*(pParams->bytesPerPix[num_planes-1]*pParams->width[num_planes-1]);
        for (unsigned int h = 0; h < pParams->height[num_planes]; h++) {
          memcpy((void *)(src_data+offset+h*pParams->bytesPerPix[num_planes]*pParams->width[num_planes]),
                (void *)((char *)surface->surfaceList[frame_meta->batch_id].mappedAddr.addr[num_planes]+h*pParams->pitch[num_planes]),
                pParams->bytesPerPix[num_planes]*pParams->width[num_planes]
                );
        }
    }
    NvBufSurfaceSyncForDevice (surface, -1, -1);
    NvBufSurfaceUnMap (surface, -1, -1);
#else
    cudaMemcpy((void*)src_data,
                (void*)surface->surfaceList[frame_meta->batch_id].dataPtr,
                surface->surfaceList[frame_meta->batch_id].dataSize,
                cudaMemcpyDeviceToHost);
#endif 


  gint frame_width = (gint)surface->surfaceList[frame_meta->batch_id].width;
  gint frame_height = (gint)surface->surfaceList[frame_meta->batch_id].height;
  gint frame_step = surface->surfaceList[frame_meta->batch_id].pitch;
  cv::Mat frame = cv::Mat(frame_height, frame_width, CV_8UC4, src_data, frame_step);
  // g_print("%d\n",frame.channels());
  // g_print("%d\n",frame.rows);
  // g_print("%d\n",frame.cols);

  cv::Mat out_mat = cv::Mat (cv::Size(frame_width, frame_height), CV_8UC3);
  cv::cvtColor(frame, out_mat, CV_RGBA2BGR);
  cv::imwrite("test.jpg", out_mat);
    if(src_data != NULL) {
        free(src_data);
        src_data = NULL;
    }
  }
  gst_buffer_unmap (buf, &in_map_info);
}

这只是一个可以完成截图的小demo。后面还要修改。根据Infer出来的数据,取帧做opencv处理。根据哪些数据和信息以及做什么处理这里就不写了。上结果图

存疑

surface结构中batchsize有4个,对应surface->surfaceList.mappedAddr.addr有4个地址,但是我并没有设置batchsize。实验过程中,只有第一个地址有数据,并且正常播放的osd视频前三帧为黑色,但是却能截图出正常图片来。不清楚这两者之间是否存在关系。以及如何设置surface的batchsize(比如我要跑多路视频,分别根据infer信息截图)

​​​​​​​

其他收获

好吧,其实这是我第一次正式接触Linux、makefile、makelist、gcc/g++和vscode(以前学网络实验时简单用过),边学边用,比熟练的人速度肯定慢了好多,总之过程是很艰辛的。这让我意识到,平时遇到不懂的,一定不能拖,当下立马解决,而且要追根问底,多查多看。还有平时真的要多看编译、链接、装载方面的书籍,不然以后肯定会有很大麻烦。

我就是个小白,哈哈,存疑问题有哪位大佬知道,可以评论区指点迷津哈~还有其他问题的也可以评论区交流~

立个flag,反正也不一定实现。下一篇更新接多路视频opencv的处理~

(问我UE里怎么用opencv插件的,我最近没玩UE,所以一直没更博,emmm先拖一拖吧~)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章