0. 在哪里分析GstBuffer数据
You can also get frame data in probe callback. Please refer to deepstream-test1 -> osd_sink_pad_buffer_probe()。
这个是deepstream论坛中相关问题的回答,具体可以阅读下面上个相关问题的内容
1. Access frame pointer in deepstream-app
2. Frame data extraction and pipeline halts/hangs after 7 minutes of run
3. Semantic Segmentation in DS 4
同样,在CSDN中也找到相关博客的介绍
1. DeepStream结合OpenCV4实现视频的分析和截图(一)
2. DeepStream结合OpenCV4实现视频的分析和截图(二)
3. Ubuntu18.04.3 安装 Nvidia DeepStream 并在 P4 显卡上解码取帧存图
此外,还参考了OpenCV对于RGBA图像操作的相关内容
1. 如何访问GstBuffer数据
通过上述分析,我们知道可以在probe回调函数中来保存图像信息。此时我们应该根据自己的需求,选择相应element的pad来设置probe回调函。根据OSD之前和之后获取数据,可以分为两大类:
- OSD element之前保存数据:可以选择nvinfer的src pad(buffer type为NV12)或者nvvidconv的src pad(buffer type为RGBA)来访问数据
- OSD element之后保存数据:也即选择osd 的src pad(buffer type为RGBA)
上述两种类型之前的区别,应该在于检测信息是否已经渲染到frame_meta数据中。
下面以osd_sink_pad_buffer_probe回调函数举例,如何访问GstBuffer数据?
/* osd_sink_pad_buffer_probe will extract metadata received on OSD sink pad
* and update params for drawing rectangle, object information etc. */
static GstPadProbeReturn
osd_sink_pad_buffer_probe (GstPad * pad, GstPadProbeInfo * info,
gpointer u_data)
{
GstBuffer *buf = (GstBuffer *) info->data;
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;
gst_buffer_unmap (buf, &in_map_info);
}
通过上述的代码,将GstBuffer的内容映射到NvBufSurface *surface上,此时我们可以通过NvBufSurface的对象访问对应GstBuffer的数据。
1.1 NvBufSurface结构
Holds information about batched buffers.可以类比NvDsBatchMeta
下面为NvBufSurface的成员变量分析,其中最重要的是确认当前buffer的memory type。
NvBufSurface对象中的数据memType返回的类型是NvBufSurfaceMemType,通过下面的打印语句能获取到当前NvBufSurface对象的属性信息。
g_print("surface->gpuId=%d\n", surface->gpuId);
g_print("surface->batchSize=%d\n", surface->batchSize);
g_print("surface->numFilled=%d\n", surface->numFilled);
g_print("surface->isContiguous=%d\n", surface->isContiguous);
g_print("surface->memType=%d\n", surface->memType);
返回结果为
surface->gpuId=0
surface->batchSize=1
surface->numFilled=1
surface->isContiguous=0
surface->memType=0
buffer的memType默认情况下是0,即NVBUF_MEM_DEFAULT,这样会根据当前的硬件平台选择相应的memType。NVBUF_MEM_CUDA_DEVICE for dGPU, NVBUF_MEM_SURFACE_ARRAY for Jetson.
2. 基于NvBufSurface保存FrameMeta数据
此时,我们了解了NvBufSurface对象的详细信息,那么如何获取到buffer的数据呢?我们注意到surfaceList属性,提供了一个指针指向buffer的数据地址并且是array的格式。那么,接下来我们要做的是将这个数据按照对应色彩空间和batch_id保存下来。
2.1 NvBufSurfaceMap
NvBufSurfaceMap的定义如下,作用是将GPU上的数据映射到CPU上。这个函数使用有两个需要注意的点是:
- 对buffer的memType有要求,在dGPU平台上,有效的memType只有NVBUF_MEM_CUDA_UNIFIED,Jetson平台上,支持NVBUF_MEM_SURFACE_ARRAY and NVBUF_MEM_HANDLE类型
- 在正式访问映射后的CPU数据之前需要调用 NvBufSurfaceSyncForCpu()函数
NvBufSurfaceSyncForCpu的定义如下,只有在NVBUF_MEM_SURFACE_ARRAY and NVBUF_MEM_HANDLE类型下有效,因此这个函数调用只需要在Jetson平台上,而在dGPU平台上调不调用区别不大(没有任何效果)NvBufSurfaceSyncForCpu的定义如下,只有在
下面的代码为NvBufSurfaceMap的框架,映射数据和解除映射相对应一起出现。
batch_meta = gst_buffer_get_nvds_batch_meta(buf);
if (NvBufSurfaceMap(surface, -1, -1, NVBUF_MAP_READ_WRITE) == 0)
{
NvBufSurfaceSyncForCpu(surface, -1, -1); // 在dGPU平台上没有任何作用
... // 保存数据
NvBufSurfaceUnMap(surface, -1, -1);
}
将数据映射到CPU上之后,通过下面两行语句能分别获取到GPU和CPU上的数据。
surface->surfaceList[batch_id].dataPtr // GPU
surface->surfaceList[batch_id].mappedAddr.addr[0]//CPU
完整的代码可以参考下面这个,我采用的方法是用CPU上的数据初始化一个Mat对象,并将其深拷贝,然后将RGBA的颜色空间转换为BGR,最终保存数据。
NvDsMetaList * l_frame = NULL;
for (l_frame = batch_meta->frame_meta_list; l_frame != NULL; l_frame = l_frame->next)
{
NvDsFrameMeta *frame_meta = (NvDsFrameMeta *) (l_frame->data);
guint batch_id = frame_meta->batch_id;
g_print("surface->surfaceList[batch_id].pitch=%d\n", surface->surfaceList[batch_id].pitch);
g_print("surface->surfaceList[batch_id].colorFormat=%d\n", surface->surfaceList[batch_id].colorFormat); // colorFormat = NVBUF_COLOR_FORMAT_RGBA,
g_print("surface->surfaceList[batch_id].dataSize=%d\n", surface->surfaceList[batch_id].dataSize);
// according to batch_id
uint32_t current_frame_width = surface->surfaceList[batch_id].width;
uint32_t current_frame_height = surface->surfaceList[batch_id].height;
// save image
cv::Mat current_frame_data = cv::Mat((gint)current_frame_height,
(gint)current_frame_width,
CV_8UC4,
surface->surfaceList[batch_id].mappedAddr.addr[0],
surface->surfaceList[batch_id].pitch
);
cv::Mat image_data((gint)current_frame_height,
(gint)current_frame_width,
CV_8UC4);
current_frame_data.copyTo(image_data);
// RGBA convert BGR
cv::Mat ouput_image = cv::Mat ((gint)current_frame_height,
(gint)current_frame_width,
CV_8UC3);
cv::cvtColor(image_data, ouput_image, cv::COLOR_RGBA2BGR); // opencv4
cv::imwrite("saved_image.jpg", ouput_image);
}
2.2 Makefile文件的修改
1. 添加opencv的头文件
CFLAGS+= -I/opt/nvidia/deepstream/deepstream-5.1/sources/includes \
-I /usr/local/cuda-$(CUDA_VER)/include \
-fPIC -std=c++17 -g \
-I/usr/local/opencv-4.5.1/include/opencv4
2. 添加相对应的库文件
- -lnvbufsurface -lnvbufsurftransform
- -L/usr/local/opencv-4.5.1/lib -lopencv_core -lopencv_imgproc -lopencv_imgcodecs
LIBS+= -L$(LIB_INSTALL_DIR) -lnvdsgst_meta -lnvds_meta -lnvdsgst_helper -lm -lnvbufsurface -lnvbufsurftransform \
-L/usr/local/cuda-$(CUDA_VER)/lib64/ -lcudart \
-lcuda -Wl,-rpath,$(LIB_INSTALL_DIR) \
-L/usr/local/opencv-4.5.1/lib -lopencv_core -lopencv_imgproc -lopencv_imgcodecs
通过上述的过程,我们就能保存带检测结果的图像帧数据。