GStreamer設計了一些手段讓應用程式可以跟Pipeline的資料互動,其中一種就是探針(Probe),呼叫者可以將GstProbe戳在元件的Pad上,如此一來可以直接操作流經Pad的數據。今天就跟大家介紹怎麼使用GstProbe去看到DeepStream附加的MetaData
為了節省篇幅,請大家直接複製Day 17的main.c和Makefile進行修改
修改main.c,[...]代表省略,方便判斷新增的程式碼要加在哪裡
#include <gst/gst.h>
#include <gstnvdsmeta.h>
#define DEMO_VIDEO_FILE "/opt/nvidia/deepstream/deepstream/samples/streams/sample_1080p_h264.mp4"
#define OUTPUT_FILE "/ironman/day_18_demo.mp4"
#define PGIE_CONFIG_FILE "/opt/nvidia/deepstream/deepstream/sources/apps/sample_apps/deepstream-test1/dstest1_pgie_config.txt"
[...]
static gboolean pad_is_nvmm_video(GstPad* pad) {
[...]
}
static GstPadProbeReturn handle_osd_sink_pad_probe(GstPad*, GstPadProbeInfo* info, gpointer) {
/* 'buf' is owned by 'info', we don't need to release it */
GstBuffer* buf = gst_pad_probe_info_get_buffer(info);
/* gst_pad_probe_info_get_buffer may return NULL */
if (!buf) {
g_print("WARNING: Can't get GstBuffer from GstPadProbeInfo\n");
return GST_PAD_PROBE_OK;
}
NvDsBatchMeta* batch_meta = gst_buffer_get_nvds_batch_meta(buf);
if (!batch_meta) {
g_print("WARNING: No NvDsBatchMeta was attched on the GstBuffer\n");
return GST_PAD_PROBE_OK;
}
for (NvDsFrameMetaList* l_frame = batch_meta->frame_meta_list; l_frame; l_frame = l_frame->next) {
NvDsFrameMeta* frame_meta = l_frame->data;
g_print("frame_num: %" G_GINT32_FORMAT ", num_objects: %" G_GUINT32_FORMAT "\n",
frame_meta->frame_num, frame_meta->num_obj_meta);
/* Prevent too many messages be printed. */
gboolean should_print_object = (frame_meta->frame_num % 90) == 0;
for (NvDsObjectMetaList* l_obj = frame_meta->obj_meta_list; l_obj; l_obj = l_obj->next) {
NvDsObjectMeta* obj_meta = l_obj->data;
if (should_print_object) {
/* This is the final bbox plotted on the screen */
const NvOSD_RectParams* rect = &obj_meta->rect_params;
g_print("object: {label=%s,x=%" G_GUINT32_FORMAT ",y=%" G_GUINT32_FORMAT
",w=%" G_GUINT32_FORMAT ",h=%" G_GUINT32_FORMAT "}\n",
obj_meta->text_params.display_text,
(guint)rect->left, (guint)rect->top,
(guint)rect->width, (guint)rect->height);
/* Prevent too many messages be printed. */
should_print_object = FALSE;
}
/* Remain for user to access the object meta */
}
}
return GST_PAD_PROBE_OK;
}
[...]
static GstElement* create_demo_pipeline(const gchar* demo_video_file, const gchar* output_file) {
[...]
/* Set signal callback for srcbin */
(void)g_signal_connect(srcbin, "pad-added", G_CALLBACK(on_srcbin_pad_added), streammux);
GstPad* osd_sink_pad = gst_element_get_static_pad(osd, "sink");
/* Use GST_PAD_PROBE_TYPE_BUFFER for looking GstBuffer in the probe callback */
(void)gst_pad_add_probe(osd_sink_pad, GST_PAD_PROBE_TYPE_BUFFER, handle_osd_sink_pad_probe, NULL, NULL);
return pipeline;
}
修改Makefile,字數不多就直接改整份,重點是新增連接DeepStream的程式庫
CC := gcc
TARGET := demo_app
SRCS := main.c
OBJS := $(SRCS:.c=.o)
DS_DIR := /opt/nvidia/deepstream/deepstream
PKGS := gstreamer-1.0
CFLAGS += -Wall -Wextra $(shell pkg-config --cflags $(PKGS))
LIBS += $(shell pkg-config --libs $(PKGS))
CFLAGS += -I$(DS_DIR)/sources/includes
LIBS += -L$(DS_DIR)/lib -lnvdsgst_meta \
-Wl,-rpath,$(DS_DIR)/lib
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) $(OBJS) -o $@ $(LIBS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
.PHONY: all clean
編譯及執行
make && ./demo_app
會看到像這樣的輸出
[...]
frame_num: 1350, num_objects: 23
object: {label=person,x=397,y=481,w=31,h=80}
frame_num: 1351, num_objects: 23
frame_num: 1352, num_objects: 23
frame_num: 1353, num_objects: 23
frame_num: 1354, num_objects: 23
frame_num: 1355, num_objects: 23
[...]
這次的範例需要從GstBuffer取得DeepStream附加的Metadata,也就是NvDsBatchMeta,DeepStream把所有Metadata都附加在這個結構底下。NvDsBatchMeta是定義在nvdsmeta.h,但是必須引用gstnvdsmeta.h才有gst_buffer_get_nvds_batch_meta的宣告,而gst_buffer_get_nvds_batch_meta的符號是在libnvdsgst_meta.so內。
基於上述的變動,我們要在Makefile加上DeepStream的include路徑讓compiler使用
CFLAGS += -I$(DS_DIR)/sources/includes
linker的部分則是加上以下這段,加上-rpath這個參數可以在runtime的時候去該路徑搜尋lib,不必在LD_LIBRARY_PATH加上路徑
LIBS += -L$(DS_DIR)/lib -lnvdsgst_meta \
-Wl,-rpath,$(DS_DIR)/lib
GstPad* osd_sink_pad = gst_element_get_static_pad(osd, "sink");
/* Use GST_PAD_PROBE_TYPE_BUFFER for looking GstBuffer in the probe callback */
(void)gst_pad_add_probe(osd_sink_pad, GST_PAD_PROBE_TYPE_BUFFER, handle_osd_sink_pad_probe,NULL, NULL);
gulong
gst_pad_add_probe (GstPad * pad,
GstPadProbeType mask,
GstPadProbeCallback callback,
gpointer user_data,
GDestroyNotify destroy_data)
GstProbe是掛在在GstPad上,這邊我們選用nvdsosd的sink,因為這個位置已經有nvinfer附加的Metadata。那麼Probe的附加方式有很多種,我們只要看GstBuffer的話就是選擇GST_PAD_PROBE_TYPE_BUFFER,這個GstProbe會「串聯」在Sink Pad前面。
static GstPadProbeReturn handle_osd_sink_pad_probe(GstPad*, GstPadProbeInfo* info, gpointer) {
[...]
}
我們得先用以下方式取得NvDsBatchMeta,記得這邊要檢查是否為NULL,因為如果你Probe位置放錯,或是前面元件接錯,都有可能導致GstBuffer上沒有附加NvDsBatchMeta
NvDsBatchMeta* batch_meta = gst_buffer_get_nvds_batch_meta(buf);
if (!batch_meta) {
g_print("WARNING: No NvDsBatchMeta was attched on the GstBuffer\n");
return GST_PAD_PROBE_OK;
}
接著是各種NvDs<Type>MetaList,全部都是GList的別名
typedef GList NvDsFrameMetaList;
typedef GList NvDsUserMetaList;
typedef GList NvDsObjectMetaList;
typedef GList NvDisplayMetaList;
typedef GList NvDsClassifierMetaList;
typedef GList NvDsLabelInfoList;
GList是Linked List資料結構,迭代的方式只要遵循以下寫法就可以,其他不同Metadata也是完全一樣的做法
for (NvDsFrameMetaList* l_frame = batch_meta->frame_meta_list; l_frame; l_frame = l_frame->next) {
NvDsFrameMeta* frame_meta = l_frame->data;
/* Some stuff with NvDsFrameMeta */
}
最後講一下為什麼Bounding Box是從obj_meta->rect_params取出
typedef struct _NvDsObjectMeta {
NvDsBaseMeta base_meta;
struct _NvDsObjectMeta *parent;
gint unique_component_id;
gint class_id;
guint64 object_id;
NvDsComp_BboxInfo detector_bbox_info;
NvDsComp_BboxInfo tracker_bbox_info;
gfloat confidence;
gfloat tracker_confidence;
NvOSD_RectParams rect_params;
NvOSD_MaskParams mask_params;
NvOSD_TextParams text_params;
gchar obj_label[MAX_LABEL_SIZE];
NvDsClassifierMetaList *classifier_meta_list;
NvDsUserMetaList *obj_user_meta_list;
gint64 misc_obj_info[MAX_USER_FIELDS];
gint64 reserved[MAX_RESERVED_FIELDS];
}NvDsObjectMeta;
因為DeepStream除了PGIE會找出Bounding Box之外,還有Tracker可以預測Bounding Box。detector_bbox_info是儲存PGIE的輸出,tracker_bbox_info是儲存Tracker的輸出,rect_params是最終nvdsosd畫出來的Bounding Box。由於Tracker在預測完之後會把結果覆寫到rect_params,所以才要以它為基準
如果有興趣的人可以去查這份原始碼/opt/nvidia/deepstream/deepstream/sources/gst-plugins/gst-nvinfer/gstnvin fer_meta_utils.cpp,裡面的attach_metadata_detector函式有一個片段有附上說明
/* Assign bounding box coordinates. These can be overwritten if tracker component is present in the pipeline */
rect_params.left = obj.left;
rect_params.top = obj.top;
rect_params.width = obj.width;
rect_params.height = obj.height;
經過今天的努力,想必漸漸地可以感受到先前看到的那些範例畫面是怎麼出現的,接下來會循序漸進,繼續從程式碼的角度繼續解析DeepStream的內容。