上一篇文章已经带着大家安装 DeepStream 的 Python 开发环境,并且执行最简单的 deepstream-test1.py,让大家体验一下这个范例的效果。本文则进一步以这个
Python 代码讲解 DeepStream 插件工作流
,并且扩充 USB 摄像头作为输入,以及将输出透过 RTSP 转发到其他电脑上观看。
如果还未安装 Python 环境或下载 Python 范例的,请至前一篇文章中找安装与下载的步骤,这里不再重复。
前面文章中已经简单提过 DeepStream 所用到的插件内容,但那只是整个框架中非常基础的一小部分,本文要用代码开始解说范例的时候,还是得将 Gstreamer 一些重要构成元素之间的关系说明清楚,这样才能让大家在代码过程得以一目了然。
现在先把这个 test1 范例的执行流程先讲解清楚,这样在阅读后面的代码就会更加容易掌握上下之间的交互关系。这里的流程对 C/C++ 版本与 Python 版本是完全一样的,只不过代码不过用 Python 来说明:
1. 首先 filesrc 数据源元件负责从磁盘上读取视频数据
2. h264parse 解析器元件负责对数据进行解析
3. nvv4l2decoder 编码器元件负责对数据进行解码
4. nvstreammux 流多路复用器元件负责批处理帧以实现最佳推理性能
6. nvvideoconvert 转换器元件负责将数据格式转换为输出显示支持的格式
7. nvdsosd 可视化元件负责将边框与文本等信息绘制到图像中
8. nvegltransform 渲染元件和 nveglglessink 接收器元件负责输出到屏幕上
建立 DeepStream 应用程式的步骤与 Gstreamer 几乎一样,都是有固定的步骤,只要熟悉之后就会发现其实并没有什么难度,接下去就开始我们的执行步骤。
创建
DeepStream
应用的7大步骤初始化
Gstreamer
与创建管道
(pipeline)
初始化 Gstreamer 与创建管道(pipeline)
pipeline = Gst.Pipeline()
用 Gst.ElementFactory.make() 创建所需要的元素,每个元素内指定插件类别(粗体部分)并给定名称(自行设定):
source = Gst.ElementFactory.make("filesrc", "file-source")
h264parser = Gst.ElementFactory.make("h264parse", "h264-parser")
# 调用NVIDIA的nvdec_h264硬件解码器
decoder = Gst.ElementFactory.make("nvv4l2decoder", "nvv4l2-decoder")
# 创建nvstreammux实例,将单个或多个源数据,复用成一个“批(batch)”
streammux = Gst.ElementFactory.make("nvstreammux", "Stream-muxer")
# 使用NVINFERE对解码器的输出执行推理,推理行为是通过配置文件设置
pgie = Gst.ElementFactory.make("nvinfer", "primary-inference")
# 根据nvosd的要求,使用转换器将NV12转换为RGBA
nvvidconv = Gst.ElementFactory.make("nvvideoconvert", "convertor")
nvosd = Gst.ElementFactory.make("nvdsosd", "onscreendisplay")
# 最后将osd的绘制,进行渲染后在屏幕上显示结果
transform=Gst.ElementFactory.make("nvegltransform", "egltransform")
sink = Gst.ElementFactory.make("nveglglessink", "nvvideo-renderer")
source.set_property('location', args[1])
streammux.set_property('width', 1920)
streammux.set_property('height', 1080)
streammux.set_property('batch-size', 1)
streammux.set_property('batched-push-timeout', 4000000)
pgie.set_property('config-file-path', "dstest1_pgie_config.txt")
将元件添加到导管之中:
用pipeline.add()
本范例的管道流为file-source -> h264-parser -> nvh264-decoder -> streammux -> nvinfer -> nvvidconv -> nvosd -> video-renderer
source.link(h264parser) # file-source -> h264-parser
h264parser.link(decoder) # h264-parser -> nvh264-decoder
# 下面粗线的三行,是streammux的特殊处理方式
sinkpad = streammux.get_request_pad("sink_0")
srcpad = decoder.get_static_pad("src")
streammux.link(pgie) # streammux -> nvinfer
pgie.link(nvvidconv) # nvinfer -> nvvidconv
nvvidconv.link(nvosd) # nvvidconv -> nvosd
nvosd.link(transform) # nvosd -> transform
transform.link(sink) # transform -> video-renderer
前面5个步骤都是比较静态的固定步骤,只要将想开发的应用所需要的插件元件进行“创建”、“给值”、“连接”就可以。
接下去的部分是整个应用中非常关键的灵魂,
就是我们得为整个应用去建构“信息(message)传递系统”,这样才能让这个应用与插件元件之间形成互动,进而正确执行我们想要得到的结果。其相互关系图如下,这里并不花时间去讲解调用细节,想了解的请自行参考 Gstreamer 框架的详细使用。
loop = GObject.MainLoop()
bus = pipeline.get_bus()
bus.add_signal_watch()
bus.connect ("message", bus_call, loop)
# 用osdsinkpad来确认nvosd插件是否获得输入
osdsinkpad = nvosd.get_static_pad("sink")
# 添加探针(probe)以获得生成的元数据的通知,我们将probe添加到osd元素的接收器板中,因为到那时,缓冲区将具有已经得到了所有的元数据。
osdsinkpad.add_probe(Gst.PadProbeType.BUFFER, \
osd_sink_pad_buffer_probe
, 0)
注意粗体“
osd_sink_pad_buffer_probe
”部分,这是代码中另一个重点,需要自行撰写代码去执行的部分,就是代码中第41~126行的内容,这里面的处理以“帧”为单位(在“
while l_frame is not None
:”里面),将该帧所检测到的物件种类进行加总,并且将物件根据种类的颜色画出框框。
事实上在这80+行代码中,真正与数据处理相关的部分,只有20行左右的内容,注释的部分占用不小的篇幅,这是作者为大家提供非常重要的说明,只要耐心地去阅读,就能轻松地掌握里面的要领。
这部分就是个“启动器”,如同汽车钥匙“执行发动”功能一样。
# 配置导管状态为PLAYING就可以
pipeline.set_state(
Gst.State.PLAYING
)
try: