Edge AI
DeepStream
deployment
Nvidia
Jetson
YOLO-POSE
pose estimation
TensorRT
pose
Performance on Jetson(AGX Xavier / AGX Orin) for TensorRT Engine
model | device | size | batch | fps | ms |
---|---|---|---|---|---|
yolov8s-pose.engine | AGX Xavier | 640 | 1 | 40.6 | 24.7 |
yolov8s-pose.engine | AGX Xavier | 640 | 12 | 12.1 | 86.4 |
yolov8s-pose.engine | AGX Orin | 640 | 1 | 258.8 | 4.2 |
yolov8s-pose.engine | AGX Orin | 640 | 12 | 34.8 | 33.2 |
yolov7w-pose.pt | AGX Xavier | 640 | 1 | 14.4 | 59.8 |
yolov7w-pose.pt | AGX Xavier | 960 | 1 | 11.8 | 69.4 |
yolov7w-pose.engine* | AGX Xavier | 960 | 1 | 19.0 | 52.1 |
yolov7w-pose.engine* | AGX Orin | 960 | 1 | 61.1 | 16.8 |
trtexec
command.相比於傳統的 top-down 和 bottom-up 方法,YOLO-pose 方法具有以下優勢:
計算效率更高:YOLO-pose 方法可以在單次推理中定位所有人物的位置和姿勢,而不需要多次前向傳遞。相比之下,top-down 方法需要進行多次前向傳遞,而 bottom-up 方法需要進行複雜的後處理。
精度更高:YOLO-pose 方法使用了一種新的方法來處理多人姿勢估計問題,並且可以在不需要測試時間增強的情況下實現最佳性能。相比之下,top-down 和 bottom-up 方法需要使用複雜的後處理和測試時間增強技術來提高性能。
複雜度更低:YOLO-pose 方法的複雜度與圖像中的人數無關,因此可以更輕鬆地處理多人姿勢估計問題。相比之下,top-down 方法的複雜度隨著圖像中的人數增加而增加,而 bottom-up 方法需要進行複雜的後處理。
強項:多人擁擠場景
完整程式碼見 : deepstream_YOLOv8-Pose_rtsp.py
要從deepstream buffer中讀取模型預測結果,需要在Deepstream Pipeline中負責模型推理的Gst-nvinfer
模塊輸出的Buffer中將meta data撈出
整體data pipeline如下圖。如果還想連影像資料一起撈的話,要從FRAME BUFFER著手
source : deepstream-imagedata-multistream
NvDsBatchMeta
data structureNvDsBatchMeta
資料結構解析取出客製模型的輸出資料要取得的目標(模型預測的張量)位於 NvDsBatchMeta
> NvDsFrameMeta
> NvDsUserMeta
之下,並指定為NvDsInferTensorMeta
的資料結構,該結構用於存儲推論張量的相關屬性和數據。
NvDsBatchMeta
Deepstream中關於Meta data資料結構定義如下圖
source : MetaData in the DeepStream SDK
NvDsUserMeta
NvDsUserMeta
的資料結構定義source : NVIDIA DeepStream SDK API Reference - NvDsUserMeta Struct Reference
NvDsUserMeta
範例Nvidia bodypose2D 自定義meta資料結構
static
gpointer nvds_copy_2dpose_meta (gpointer data, gpointer user_data)
{
NvDsUserMeta *user_meta = (NvDsUserMeta *) data;
NvDs2DposeMetaData *p_2dpose_meta_data =
(NvDs2DposeMetaData *)user_meta->user_meta_data;
NvDs2DposeMetaData *pnew_2dpose_meta_data =
(NvDs2DposeMetaData *)g_memdup ( p_2dpose_meta_data,
sizeof(NvDs2DposeMetaData));
return (gpointer) pnew_2dpose_meta_data;
}
使用obj_user_meta_list取出meta資料
for (l_user = obj_meta->obj_user_meta_list; l_user != NULL;
l_user = l_user->next)
詳細的範例見DeepStream SDK samplessources/apps/sample_apps/deepstream_infer_tensor_meta-test.cpp
以下是讀取及解析模型預測結果的raw tensor data方法
Gst-nvinfer
插件啟用屬性output-tensor-meta
,或在配置文件中啟用相同名稱的屬性
output-tensor-meta=1
NvDsFrameMeta
內的frame_user_meta_list
NvDsObjectMeta
內的obj_user_meta_list
Gst-nvinfer
的Meta數據可以在下游的GStreamer pad probe
中訪問
/* Iterate each frame metadata in batch */
for (NvDsMetaList *l_frame = batch_meta->frame_meta_list; l_frame != NULL;
l_frame = l_frame->next)
{
NvDsFrameMeta *frame_meta = (NvDsFrameMeta *)l_frame->data;
.
.
.
/* Iterate user metadata in frames to search PGIE's tensor metadata */
for (NvDsMetaList *l_user = frame_meta->frame_user_meta_list;
l_user != NULL; l_user = l_user->next)
{
NvDsUserMeta *user_meta = (NvDsUserMeta *)l_user->data;
if (user_meta->base_meta.meta_type != NVDSINFER_TENSOR_OUTPUT_META)
continue;
NvDsInferTensorMeta *meta =
(NvDsInferTensorMeta *)user_meta->user_meta_data;
.
.
.
for (unsigned int i = 0; i < meta->num_output_layers; i++)
{
NvDsInferLayerInfo *info = &meta->output_layers_info[i];
info->buffer = meta->out_buf_ptrs_host[i];
NvDsInferDimsCHW LayerDims;
std::vector<NvDsInferLayerInfo>
NvDsInferTensorMeta
def pose_src_pad_buffer_probe(pad, info, u_data):
gst_buffer = info.get_buffer()
batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer))
l_frame = batch_meta.frame_meta_list
frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
l_usr = frame_meta.frame_user_meta_list
user_meta = pyds.NvDsUserMeta.cast(l_usr.data)
tensor_meta = pyds.NvDsInferTensorMeta.cast(user_meta.user_meta_data)
[Gst-nvinfer
]後的[Gst-nvdspostprocess
]階段,取出NvDsInferTensorMeta
內的output_layers_info
進行解析NvDsBatchMeta
data schema to numpy
arrayNvDsBatchMeta
資料結構解轉換為numpy
陣列NvDsInferTensorMeta
> NvDsInferLayerinfo
> layer.buffer
> numpy array
NvDsInferTensorMeta
source : NVIDIA DeepStream SDK API Reference - NvDsInferTensorMeta Struct Reference
在取得模型推理結果的張量後(Deepstream中用來儲存推理結果的NvDsInferTensorMeta
資料結構),這時候的原始資料型態還是記憶體中分配的一塊buffer,必須透過python ctype
函式庫的幫助,取得buffer記憶體位置的指標後,將其轉換為numpy資料格式,方便後續在python中處理
NvDsInferTensorMeta
> numpy array : python code examplelayer_output_info.inferDims.d
API來自動抓取模型輸出的形狀,用於指定NumPy陣列的維度。這樣,後面就可以使用NumPy
提供的強大功能來操作這個陣列/張量,即模型預測結果)data_type_map = {pyds.NvDsInferDataType.FLOAT: ctypes.c_float,
pyds.NvDsInferDataType.INT8: ctypes.c_int8,
pyds.NvDsInferDataType.INT32: ctypes.c_int32}
def pose_src_pad_buffer_probe(pad, info, u_data):
.
.
.
tensor_meta = pyds.NvDsInferTensorMeta.cast(user_meta.user_meta_data)
# layers_info = []
# for i in range(tensor_meta.num_output_layers):
# layer = pyds.get_nvds_LayerInfo(tensor_meta, i)
# # print(i, layer.layerName)
# layers_info.append(layer)
# if your model only have one output layer, just give "0" as key
layer_output_info = pyds.get_nvds_LayerInfo(tensor_meta, 0) # as num_output_layers == 1
# remove zeros from both ends of the array. 'b' : 'both'
dims = np.trim_zeros(layer_output_info.inferDims.d, 'b')
if frame_number == 0 :
print(f'\tModel output dimension from LayerInfo: {dims}')
# load float* buffer to python
cdata_type = data_type_map[layer_output_info.dataType]
ptr = ctypes.cast(pyds.get_ptr(layer_output_info.buffer),
ctypes.POINTER(cdata_type))
# Determine the size of the array
# Automatic acquisition of buffer memory sizes based on output in the model
out = np.ctypeslib.as_array(ptr, shape=dims)
檢視YOLOv8-pose的輸出,張量形狀為(batch, anchors, max_outputs)
netron yolov8s-pose.onnx
由於yolov7-pose
與yolov8-pose
模型的輸出形狀與anchors不同
我這邊使用的後處理函式需要採用(batch, max_outputs, anchors)的格式輸入,而且包含類別標籤的機率,因此手動客製調整yolov8-pose
模型的輸出張量
anchors of yolov7-pose
anchors of yolov8-pose
def pose_src_pad_buffer_probe(pad, info, u_data):
.
network_info = tensor_meta.network_info
.
# [Optional] Postprocess for yolov8-pose prediction tensor
# [YOLOv8](https://github.com/ultralytics/ultralytics)
# (batch, 56, 8400) >(batch, 8400, 56) for yolov8
out = out.transpose((0, 2, 1))
# make pseudo class prob
cls_prob = np.ones((out.shape[0], out.shape[1], 1), dtype=np.uint8)
out[..., :4] = map_to_zero_one(out[..., :4]) # scalar prob to [0, 1]
# insert pseudo class prob into predictions
out = np.concatenate((out[..., :5], cls_prob, out[..., 5:]), axis=-1)
out[..., [0, 2]] = out[..., [0, 2]] * network_info.width # scale to screen width
out[..., [1, 3]] = out[..., [1, 3]] * network_info.height # scale to screen height
dstest1_pgie_YOLOv8-Pose_config.txt
以下列出幾個使用客製模型時需要特別設置的參數
model-engine-file
指定你的TensorRT Engine路徑,我習慣放在/opt/nvidia/deepstream/deepstream/samples/models/
並指定絕對路徑
model-engine-file=<your engine path>
network-type
指定模型種類,由於deepstream還沒有提供公版的姿態模型,所有要指定其他:100
[Gst-nvdspreprocess & Gst-nvdspostprocess plugin]
0=Detector, 1=Classifier, 2=Segmentation, 100=Other
network-type=100
output-tensor-meta
要設為1
(true)才能在NvDsUserMeta
取得模型輸出結果
output-tensor-meta=1
tensor-meta-pool-size
預設單一batch是6,即使不設定也會自動分配,有測試過設比較大的值但加速效果不明顯
if batch-size!=1 。