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先拖一拖吧~)

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