現在大家已經知道如何迭代NvDsBatchMeta,但這只停留在「讀取」,實際應用免不了要有「寫入」的能力,所以今天就教大家如何附加Meta Data,看看結果有什麼不同
我們複製Day 18的程式進行修改,目標是讓每個人多一個ironman標籤
[...]
#define DEMO_VIDEO_FILE "/opt/nvidia/deepstream/deepstream/samples/streams/sample_1080p_h264.mp4"
#define OUTPUT_FILE "/ironman/day_20_demo.mp4"
#define PGIE_CONFIG_FILE "/opt/nvidia/deepstream/deepstream/sources/apps/sample_apps/deepstream-test1/dstest1_pgie_config.txt"
#define PGIE_UNIQUE_ID 1
#define PERSON_CLASS_ID 2
#define IRONMAN_UNIQUE_ID 9
[...]
static NvDsClassifierMeta* create_classifier_meta(NvDsBatchMeta* batch_meta, const gchar* label) {
/* We must acquire a new meta data from a NvDsBatchMeta */
NvDsClassifierMeta* class_meta = nvds_acquire_classifier_meta_from_pool(batch_meta);
if (!class_meta) {
g_print("WARNING: Can't acquire meta data from pool\n");
return NULL;
}
NvDsLabelInfo* label_info = nvds_acquire_label_info_meta_from_pool(batch_meta);
if (!label_info) {
g_print("WARNING: Can't acquire meta data from pool\n");
class_meta->base_meta.release_func(class_meta, NULL);
return NULL;
}
/* Set up meta data */
label_info->num_classes = 1;
g_strlcpy(label_info->result_label, label, MAX_LABEL_SIZE);
label_info->pResult_label = NULL;
label_info->result_class_id = 0;
label_info->label_id = 0;
label_info->result_prob = 0.99f;
class_meta->unique_component_id = IRONMAN_UNIQUE_ID;
class_meta->classifier_type = "ironman";
nvds_add_label_info_meta_to_classifier(class_meta, label_info);
return class_meta;
}
static void object_meta_refresh_label(NvDsObjectMeta* obj_meta) {
for (NvDsClassifierMetaList* l_class = obj_meta->classifier_meta_list; l_class; l_class = l_class->next) {
NvDsClassifierMeta* class_meta = l_class->data;
if (class_meta->unique_component_id != IRONMAN_UNIQUE_ID) {
continue;
}
for (NvDsLabelInfoList* l_label = class_meta->label_info_list; l_label; l_label = l_label->next) {
NvDsLabelInfo* label_info = l_label->data;
gchar* old_label = obj_meta->text_params.display_text;
const gchar* result_label = label_info->pResult_label ?
label_info->pResult_label : label_info->result_label;
obj_meta->text_params.display_text = g_strdup_printf("%s %s", old_label, result_label);
g_free(old_label);
}
}
}
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;
for (NvDsObjectMetaList* l_obj = frame_meta->obj_meta_list; l_obj; l_obj = l_obj->next) {
NvDsObjectMeta* obj_meta = l_obj->data;
if (obj_meta->unique_component_id == PGIE_UNIQUE_ID
&& obj_meta->class_id == PERSON_CLASS_ID) {
NvDsClassifierMeta* class_meta = create_classifier_meta(batch_meta, "ironman");
/* Must lock 'batch_meta' when modifying existing metadata */
nvds_acquire_meta_lock(batch_meta);
nvds_add_classifier_meta_to_object(obj_meta, class_meta);
object_meta_refresh_label(obj_meta);
nvds_release_meta_lock(batch_meta);
}
}
}
return GST_PAD_PROBE_OK;
}
多連接一個lib,-lnvds_meta
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 -lnvds_meta -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

static NvDsClassifierMeta* create_classifier_meta(NvDsBatchMeta* batch_meta, const gchar* label) {
/* We must acquire a new meta data from a NvDsBatchMeta */
NvDsClassifierMeta* class_meta = nvds_acquire_classifier_meta_from_pool(batch_meta);
if (!class_meta) {
g_print("WARNING: Can't acquire meta data from pool\n");
return NULL;
}
NvDsLabelInfo* label_info = nvds_acquire_label_info_meta_from_pool(batch_meta);
if (!label_info) {
g_print("WARNING: Can't acquire meta data from pool\n");
class_meta->base_meta.release_func(class_meta, NULL);
return NULL;
}
/* Set up meta data */
label_info->num_classes = 1;
g_strlcpy(label_info->result_label, label, MAX_LABEL_SIZE);
label_info->pResult_label = NULL;
label_info->result_class_id = 0;
label_info->label_id = 0;
label_info->result_prob = 0.99f;
class_meta->unique_component_id = IRONMAN_UNIQUE_ID;
class_meta->classifier_type = "ironman";
nvds_add_label_info_meta_to_classifier(class_meta, label_info);
return class_meta;
}
在DeepStream中,產生Metadata的標準方法是從NvDsBatchMeta的pool內取得,那SDK有提供取得Metadata的函式nvds_acquire_<metatype>_meta_from_pool,如果要提前釋放的話必須自己呼叫base_meta.release_func
產生Metadata後就是根據自己的需求把數值填入欄位,填的愈詳細,日後要擴充其他元件搭配一起使用就更容易。至於NvDsLabelInfo::pResultLabel這個欄位的用途是如果你的標籤超過MAX_LABEL_SIZE時可以使用但並不推薦,因為釋放的時候是呼叫g_free,所以必須強制使用g_malloc0或相關的分配函式,沒有辦法掛上自己的物件池或分配器
建立Metadata本身是否是執行續安全的,官方在這段的描述比較模糊,姑且就相信Pool本身是執行續安全(不是的話挺可怕的),所以這部分可以不必加鎖
/* in handle_osd_sink_pad_probe */
for (NvDsObjectMetaList* l_obj = frame_meta->obj_meta_list; l_obj; l_obj = l_obj->next) {
NvDsObjectMeta* obj_meta = l_obj->data;
if (obj_meta->unique_component_id == PGIE_UNIQUE_ID
&& obj_meta->class_id == PERSON_CLASS_ID) {
NvDsClassifierMeta* class_meta = create_classifier_meta(batch_meta, "ironman");
/* Must lock 'batch_meta' when modifying existing metadata */
nvds_acquire_meta_lock(batch_meta);
nvds_add_classifier_meta_to_object(obj_meta, class_meta);
object_meta_refresh_label(obj_meta);
nvds_release_meta_lock(batch_meta);
}
}
當我們建立好Metadata,接著就是要把他掛上已存在的Metadata,這時候就必須要鎖住batch_meta,這在官方的範例當中也有寫,使用nvds_acquire_meta_lock上鎖,nvds_release_meta_lock解鎖。使用nvds_add_<metatype>_meta_to_<metatype>將Metadata加入,然後不必理會像是num_classes這種欄位,函式會幫你處理好
再來是非常容易漏掉的一個步驟,就是「必須」要更新display_text才能讓OSD畫出你想要的文字,平時這一步在nvinfer就幫你做好了,但現在是我們自己加上Metadata,所以這一步不能漏掉
static void object_meta_refresh_label(NvDsObjectMeta* obj_meta) {
for (NvDsClassifierMetaList* l_class = obj_meta->classifier_meta_list; l_class; l_class = l_class->next) {
NvDsClassifierMeta* class_meta = l_class->data;
if (class_meta->unique_component_id != IRONMAN_UNIQUE_ID) {
continue;
}
for (NvDsLabelInfoList* l_label = class_meta->label_info_list; l_label; l_label = l_label->next) {
NvDsLabelInfo* label_info = l_label->data;
gchar* old_label = obj_meta->text_params.display_text;
const gchar* result_label = label_info->pResult_label ?
label_info->pResult_label : label_info->result_label;
obj_meta->text_params.display_text = g_strdup_printf("%s %s", old_label, result_label);
g_free(old_label);
}
}
}
我這邊選擇用比較通用的形式來更新display_text,官方也是這麼做的,就是把所有result_label用空格分離並全部加在既有的文字後面,更新前記得先把舊的文字指標保留起來,更新後要釋放。
這邊用到了g_strdup_printf,它會把分配一個新的buffer來寫入格式化的字串並保證是null-terminated。這裡就得提醒一下,既然都使用GLib了,那就放手去用它的程式庫吧,這程式庫的原則從來就不是效能,頻繁分配記憶體什麼的就別管了
今天的內容算不上複雜,但就是要很仔細,得要親自操作幾次才會記住哪些欄位記得要填,哪些欄位記得要改。很多欄位其實不是為了顯示,而是為了搭配官方的設計,將邊緣硬體推論後的Metadata發佈到IoT