DeepStream-test1-python-demo样例

DeepStream-test1-python-demo样例

基于知乎博客解释完成:
https://zhuanlan.zhihu.com/p/359079725
img

大致整体流程:

test1样例的整体流程: 首先数据源元件(filesrc)负责从磁盘上读取视频数据,解析器元件(h264parse)负责对数据进行解析,编码器元件(nvv4l2decoder)负责对数据进行解码,流多路复用器元件(nvstreammux)负责批处理帧以实现最佳推理性能,推理元件(nvinfer)负责实现加速推理,转换器元件(nvvideoconvert)负责将数据格式转换为输出显示支持的格式,可视化元件(nvdsosd)负责将边框与文本等信息绘制到图像中,渲染元件和接收器元件(nvegltransform and nveglglessink)负责输出到屏幕上.

代码详解

main函数

在主函数中调用gst_init()来完成相应的初始化工作,以便将用户从命令行输入的参数传递给GStreamer函数库

Gst.init(None)

创建主循环,在执行 g_main_loop_run后正式开始循环

loop = GLib.MainLoop()

在GStreamer框架中管道是用来容纳和管理元件的,下面将创建一条名为pipeline的管道:

pipeline = Gst.Pipeline()

创建管理中需要使用的所有元件,并检查所有元件是否创建成功

print("Creating Source \n ")
# 创建一个gstreamer element, 类型为filesrc,名称为file-source。
source = Gst.ElementFactory.make("filesrc", "file-source")
if not source:
    sys.stderr.write(" Unable to create Source \n")

# Since the data format in the input file is elementary h264 stream,
# we need a h264parser
print("Creating H264Parser \n")
# 创建一个gstreamer element, 类型为h264parse,名称为h264-parser。
# 因为输入文件中的数据格式是基本的h264流,所以我们需要一个h264解析器
h264parser = Gst.ElementFactory.make("h264parse", "h264-parser")
if not h264parser:
    sys.stderr.write(" Unable to create h264 parser \n")

# Use nvdec_h264 for hardware accelerated decode on GPU
print("Creating Decoder \n")
# 创建一个gstreamer element, 类型为nvv4l2decoder,名称为nvv4l2-decoder。
# 调用GPU硬件加速来解码h264文件
decoder = Gst.ElementFactory.make("nvv4l2decoder", "nvv4l2-decoder")
if not decoder:
    sys.stderr.write(" Unable to create Nvv4l2 Decoder \n")

# Create nvstreammux instance to form batches from one or more sources.
# 创建一个gstreamer element, 类型为nvstreammux,名称为stream-muxer。
# 从一个或多个源中来组成batches
streammux = Gst.ElementFactory.make("nvstreammux", "Stream-muxer")
if not streammux:
    sys.stderr.write(" Unable to create NvStreamMux \n")

# Use nvinfer to run inferencing on decoder's output,
# behaviour of inferencing is set through config file
# 创建一个gstreamer element, 类型为nvinfer,名称为primary-inference。
# 使用nvinfer在解码器的输出上运行推理,推理过程的参数是通过配置文件设置的
pgie = Gst.ElementFactory.make("nvinfer", "primary-inference")
if not pgie:
    sys.stderr.write(" Unable to create pgie \n")

# Use convertor to convert from NV12 to RGBA as required by nvosd
# 创建一个gstreamer element, 类型为nvvideoconvert,名称为converter。
# 使用转换器插件,从NV12 转换到 nvosd 所需要的RGBA
nvvidconv = Gst.ElementFactory.make("nvvideoconvert", "convertor")
if not nvvidconv:
    sys.stderr.write(" Unable to create nvvidconv \n")

# Create OSD to draw on the converted RGBA buffer
# 创建一个gstreamer element, 类型为nvdsosd,名称为onscreendisplay。
# 创建OSD在转换后的RGBA缓冲区上绘图
nvosd = Gst.ElementFactory.make("nvdsosd", "onscreendisplay")

if not nvosd:
    sys.stderr.write(" Unable to create nvosd \n")

# Finally render the osd output
# 判断平台类型,是否需要转化  nvegltransform?
# 判断设备平台是否为TEGRA,是的话创建transform元件,实现渲染osd输出。
# 这个属性是在makefile文件中设置的。
if is_aarch64():
    print("Creating nv3dsink \n")
    sink = Gst.ElementFactory.make("nv3dsink", "nv3d-sink")
    if not sink:
        sys.stderr.write(" Unable to create nv3dsink \n")
else:
    print("Creating EGLSink \n")
    sink = Gst.ElementFactory.make("nveglglessink", "nvvideo-renderer")
    if not sink:
        sys.stderr.write(" Unable to create egl sink \n")

print("Playing file %s " % args[1])
# 数据源元件负责从磁盘文件中读取视频数据,它具有名为location的属性,
# 用来指明文件在磁盘上的位置。使用标准的GObject属性机制可以为元件设置相应的属性
# 这里是为source元件的location属性赋值,
source.set_property('location', args[1])
if os.environ.get('USE_NEW_NVSTREAMMUX') != 'yes':  # Only set these properties if not using new gst-nvstreammux
    streammux.set_property('width', 1920)
    streammux.set_property('height', 1080)
    streammux.set_property('batched-push-timeout', 4000000)

streammux.set_property('batch-size', 1)
# 设置nvinfer元件中的属性config-file-path, 通过设置配置文件来设置nvinfer元件的所有必要属性
pgie.set_property('config-file-path', "dstest1_pgie_config.txt")


此时。管道、元件都已经创建并赋值。现在需要把创建好的元件按照顺序,需要全部添加到管道中。

print("Adding elements to Pipeline \n")
# 这里sink已经被判断过,是否需要使用transform
pipeline.add(source)
pipeline.add(h264parser)
pipeline.add(decoder)
pipeline.add(streammux)
pipeline.add(pgie)
pipeline.add(nvvidconv)
pipeline.add(nvosd)
pipeline.add(sink)

现在,我们需要通过pad(衬垫)来将元件连接起来。Pad是一个element的输入/输出接口,分为src pad(生产衬垫)和sink pad(消费衬垫)两种。在element通过pad连接成功后,数据会从上一个element的src pad传到下一个element的sink pad然后进行处理。

# we link the elements together
# file-source -> h264-parser -> nvh264-decoder ->
# nvinfer -> nvvidconv -> nvosd -> video-renderer
print("Linking elements in the Pipeline \n")
# 先将输入部分连接
source.link(h264parser)
h264parser.link(decoder)

# decoder和streammux通过pad(衬垫)来将元件连接起来
# 在element通过pad连接成功后,数据会从上一个element的src pad传到下一个element的sink pad然后进行处理
# 生产pad
srcpad = decoder.get_static_pad("src")
if not srcpad:
    sys.stderr.write(" Unable to get source pad of decoder \n")

# 消费pad
sinkpad = streammux.get_request_pad("sink_0")
if not sinkpad:
    sys.stderr.write(" Unable to get the sink pad of streammux \n")

srcpad.link(sinkpad)

# 连接剩余元件
streammux.link(pgie)
pgie.link(nvvidconv)
nvvidconv.link(nvosd)
nvosd.link(sink)

获取管道的消息总线,添加消息监控器。其中bus_call 是消息处理函数

bus = pipeline.get_bus()
bus.add_signal_watch()
bus.connect("message", bus_call, loop)

让我们添加探测来获得生成的元数据的信息。我们添加探测到 osd 元素的接收单元,因为到那时,缓冲区已经获得了所有的元数据。在下文我们会介绍osd_sink_pad_buffer_probe函数,这里先了解此函数的作用就是获取到所有元数据信息,在此基础上画框和打印文字。

# 添加探测来获得生成的元数据的信息,
# 添加探测到 osd 元素的接收单元,因为缓冲区已经获得了所有的元数据
osdsinkpad = nvosd.get_static_pad("sink")
if not osdsinkpad:
    sys.stderr.write(" Unable to get sink pad of nvosd \n")
# osd_sink_pad_buffer_probe函数的作用就是获取到所有元数据信息,在此基础上画框和打印文字
# 具体介绍参考上面
osdsinkpad.add_probe(Gst.PadProbeType.BUFFER, osd_sink_pad_buffer_probe, 0)

所有准备工作都做好之后,就可以通过将管道的状态切换到PLAYING状态,来启动整个管道的数据处理流程:

# start play back and listen to events
print("Starting pipeline \n")
# 将管道的状态切换到PLAYING状态,来启动整个管道的数据处理流程
pipeline.set_state(Gst.State.PLAYING)
try:
    # 进入主训练, 等待管道遇到错误或者EOS而终止
    loop.run()
except:
    pass
# cleanup
pipeline.set_state(Gst.State.NULL)

osd_sink_pad_buffer_probe函数

这个函数的作用就是提取从 osd接收器接收到的元数据 ,并更新绘图矩形、对象信息等的参数。阅读这部分,需要了解DeepStream中的数据结构,可以阅读官方文档,看**这里**。 但我在阅读官方文档过程中发现文中绘制的数据结构有点过时,和源码中的不完全对应。我自己重新绘制了一个,见下图。

img

def osd_sink_pad_buffer_probe(pad, info, u_data):
    frame_number = 0
    num_rects = 0

    gst_buffer = info.get_buffer()
    if not gst_buffer:
        print("Unable to get GstBuffer ")
        return

    # Retrieve batch metadata from the gst_buffer
    # Note that pyds.gst_buffer_get_nvds_batch_meta() expects the
    # C address of gst_buffer as input, which is obtained with hash(gst_buffer)
    # 函数 gst_buffer_get_nvds_batch_meta()作用是 从 Gst Buffer 中提取 NvDsBatchMeta
    batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer))
    # 对batch_meta 中的frame_meta_list进行遍历; frame_meta_list列表的长度是num_frames_in_batch
    l_frame = batch_meta.frame_meta_list
    while l_frame is not None:
        try:
            # Note that l_frame.data needs a cast to pyds.NvDsFrameMeta
            # The casting is done by pyds.NvDsFrameMeta.cast()
            # The casting also keeps ownership of the underlying memory
            # in the C code, so the Python garbage collector will leave
            # it alone.
            frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
        except StopIteration:
            break

        # Intiallizing object counter with 0.
        obj_counter = {
    
    
            PGIE_CLASS_ID_VEHICLE: 0,
            PGIE_CLASS_ID_PERSON: 0,
            PGIE_CLASS_ID_BICYCLE: 0,
            PGIE_CLASS_ID_ROADSIGN: 0
        }
        frame_number = frame_meta.frame_num
        num_rects = frame_meta.num_obj_meta
        l_obj = frame_meta.obj_meta_list
        # 单帧的obj_meta_list进行遍历
        while l_obj is not None:
            try:
                # Casting l_obj.data to pyds.NvDsObjectMeta
                obj_meta = pyds.NvDsObjectMeta.cast(l_obj.data)
            except StopIteration:
                break
            # 判断目标检测框的类别如果是0(0是汽车的类别标签),那么车辆数+1,目标检测框数+1.
            # 如果是2(0是行人的类别标签),那么行人数+1,目标检测框数+1.
            obj_counter[obj_meta.class_id] += 1
            obj_meta.rect_params.border_color.set(0.0, 0.0, 1.0, 0.8)  # 0.8 is alpha (opacity)
            try:
                l_obj = l_obj.next
            except StopIteration:
                break

        # Acquiring a display meta object. The memory ownership remains in
        # the C code so downstream plugins can still access it. Otherwise
        # the garbage collector will claim it when this probe function exits.
        display_meta = pyds.nvds_acquire_display_meta_from_pool(batch_meta)
        # 设置display_meta的text_params属性
        display_meta.num_labels = 1
        py_nvosd_text_params = display_meta.text_params[0]
        # Setting display text to be shown on screen
        # Note that the pyds module allocates a buffer for the string, and the
        # memory will not be claimed by the garbage collector.
        # Reading the display_text field here will return the C address of the
        # allocated string. Use pyds.get_string() to get the string content.
        py_nvosd_text_params.display_text = "Frame Number={} Number of Objects={} Vehicle_count={} Person_count={}".format(
            frame_number, num_rects, obj_counter[PGIE_CLASS_ID_VEHICLE], obj_counter[PGIE_CLASS_ID_PERSON])

        # Now set the offsets where the string should appear
        # 设置文本在画面中的x,y坐标(分别是相对于画面原点的偏移量)
        py_nvosd_text_params.x_offset = 10
        py_nvosd_text_params.y_offset = 12

        # Font , font-color and font-size
        # 设置文本的字体类型、字体颜色、字体大小
        py_nvosd_text_params.font_params.font_name = "Serif"
        py_nvosd_text_params.font_params.font_size = 10
        # set(red, green, blue, alpha); set to White
        py_nvosd_text_params.font_params.font_color.set(1.0, 1.0, 1.0, 1.0)

        # Text background color
        # 设置文本的背景颜色
        py_nvosd_text_params.set_bg_clr = 1
        # set(red, green, blue, alpha); set to Black
        py_nvosd_text_params.text_bg_clr.set(0.0, 0.0, 0.0, 1.0)
        # Using pyds.get_string() to get display_text as string
        print(pyds.get_string(py_nvosd_text_params.display_text))
        # 将display_meta 信息添加到frame_meta信息中
        pyds.nvds_add_display_meta_to_frame(frame_meta, display_meta)
        try:
            l_frame = l_frame.next
        except StopIteration:
            break

    return Gst.PadProbeReturn.OK

猜你喜欢

转载自blog.csdn.net/charles_zhang_/article/details/129585416