iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0

現在大家已經知道如何迭代NvDsBatchMeta,但這只停留在「讀取」,實際應用免不了要有「寫入」的能力,所以今天就教大家如何附加Meta Data,看看結果有什麼不同

範例程式

我們複製Day 18的程式進行修改,目標是讓每個人多一個ironman標籤

main.c

[...]
#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;
}

Makefile

多連接一個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

成果

result

程式碼解說

如何產生新的metadata

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本身是執行續安全(不是的話挺可怕的),所以這部分可以不必加鎖

新增Metadata

/* 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,所以這一步不能漏掉

更新display_text

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


上一篇
[Day 19] 細說各種Metadata
下一篇
[Day 21] 如何建立User Metadata
系列文
深入認識DeepStream,不只是停在執行範例 共 22 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言