396浏览
查看: 396|回复: 0

[官方资料] Jetson Nano 2GB系列文章(35)Python版test1实战说明

[复制链接]
上一篇文章已经带着大家安装 DeepStream 的 Python 开发环境,并且执行最简单的 deepstream-test1.py,让大家体验一下这个范例的效果。本文则进一步以这个 Python 代码讲解 DeepStream 插件工作流,并且扩充 USB 摄像头作为输入,以及将输出透过 RTSP 转发到其他电脑上观看。

如果还未安装 Python 环境或下载 Python 范例的,请至前一篇文章中找安装与下载的步骤,这里不再重复。

前面文章中已经简单提过 DeepStream 所用到的插件内容,但那只是整个框架中非常基础的一小部分,本文要用代码开始解说范例的时候,还是得将 Gstreamer 一些重要构成元素之间的关系说明清楚,这样才能让大家在代码过程得以一目了然。

现在先把这个 test1 范例的执行流程先讲解清楚,这样在阅读后面的代码就会更加容易掌握上下之间的交互关系。这里的流程对 C/C++ 版本与 Python 版本是完全一样的,只不过代码不过用 Python 来说明:

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

建立 DeepStream 应用程式的步骤与 Gstreamer 几乎一样,都是有固定的步骤,只要熟悉之后就会发现其实并没有什么难度,接下去就开始我们的执行步骤。

  • 创建 DeepStream 应用的7大步骤初始化 Gstreamer 与创建管道(pipeline)

1. 初始化 Gstreamer 与创建管道(pipeline)

  1. # 从“def main(args):”开始
  2. GObject.threads_init()  
  3. # 标准GStreamer初始化
  4. Gst.init(None)              
  5. # 创建Gst物件与初始化
  6. pipeline = Gst.Pipeline()  
  7. # 创建与其他元素相连接的管道元素
复制代码

2. 创建所有需要的元件(element):用Gst.ElementFactory.make() 创建所需要的元素,每个元素内指定插件类别(粗体部分)并给定名称(自行设定):

  1. # 阶段1-处理输入源的插件:
  2. # 建立“源”元素负责从文件读入数据
  3. source = Gst.ElementFactory.make("filesrc", "file-source")
  4. # 解析文件是否为要求的h264格式
  5. h264parser = Gst.ElementFactory.make("h264parse", "h264-parser")
  6. # 调用NVIDIA的nvdec_h264硬件解码器
  7. decoder = Gst.ElementFactory.make("nvv4l2decoder", "nvv4l2-decoder")
  8. # 创建nvstreammux实例,将单个或多个源数据,复用成一个“批(batch)”
  9. streammux = Gst.ElementFactory.make("nvstreammux", "Stream-muxer")
  10. # 阶段2-执行推理的插件:
  11. # 使用NVINFERE对解码器的输出执行推理,推理行为是通过配置文件设置
  12. pgie = Gst.ElementFactory.make("nvinfer", "primary-inference")
  13. # 阶段3-处理输出的插件:
  14. # 根据nvosd的要求,使用转换器将NV12转换为RGBA
  15. nvvidconv = Gst.ElementFactory.make("nvvideoconvert", "convertor")
  16. # 创建OSD以在转换的RGBA缓冲区上绘制
  17. nvosd = Gst.ElementFactory.make("nvdsosd", "onscreendisplay")
  18. # 最后将osd的绘制,进行渲染后在屏幕上显示结果
  19. transform=Gst.ElementFactory.make("nvegltransform", "egltransform")
  20. sink = Gst.ElementFactory.make("nveglglessink", "nvvideo-renderer")
复制代码

3. 配置元件的参数

  1. # 以args[1]给定的文件名为输入源视频文件
  2. source.set_property('location', args[1])
  3. # 设定流复用器的尺寸、数量
  4. streammux.set_property('width', 1920)
  5. streammux.set_property('height', 1080)
  6. streammux.set_property('batch-size', 1)
  7. streammux.set_property('batched-push-timeout', 4000000)
  8. # 设定pgie的配置文件
  9. pgie.set_property('config-file-path', "dstest1_pgie_config.txt")
复制代码

4. 将元件添加到导管之中:用pipeline.add()

  1. pipeline.add(source)
  2. pipeline.add(h264parser)
  3. pipeline.add(decoder)
  4. pipeline.add(streammux)
  5. pipeline.add(pgie)
  6. pipeline.add(nvvidconv)
  7. pipeline.add(nvosd)
  8. pipeline.add(sink)
  9. if is_aarch64():
  10.      pipeline.add(transform)
复制代码

5. 将元件按照要求连接起来:本范例的管道流为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")
srcpad.link(sinkpad)   
      
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 框架的详细使用。

qw2.jpg

6. 创建一个事件循环(evnet loop):将信息(mesages)传入并监控bus的信号

  1. loop = GObject.MainLoop()
  2. bus = pipeline.get_bus()
  3. bus.add_signal_watch()
  4. bus.connect ("message", bus_call, loop)
  5. # 用osdsinkpad来确认nvosd插件是否获得输入
  6. osdsinkpad = nvosd.get_static_pad("sink")
  7. # 添加探针(probe)以获得生成的元数据的通知,我们将probe添加到osd元素的接收器板中,因为到那时,缓冲区将具有已经得到了所有的元数据。
  8. osdsinkpad.add_probe(Gst.PadProbeType.BUFFER,   \
  9. osd_sink_pad_buffer_probe,   0)
复制代码

注意“osd_sink_pad_buffer_probe”部分,这是代码中另一个重点,需要自行撰写代码去执行的部分,就是代码中第41~126行的内容,这里面的处理以“帧”为单位(在“while l_frame is not None:”里面),将该帧所检测到的物件种类进行加总,并且将物件根据种类的颜色画出框框。

事实上在这80+行代码中,真正与数据处理相关的部分,只有20行左右的内容,注释的部分占用不小的篇幅,这是作者为大家提供非常重要的说明,只要耐心地去阅读,就能轻松地掌握里面的要领。

7. 播放并收听事件:这部分就是个“启动器”,如同汽车钥匙“执行发动”功能一样。

# 配置导管状态为PLAYING就可以
pipeline.set_state(Gst.State.PLAYING)
try:
loop.run()    # 执行前面创建的事件循环
except:
    pass
# 执行结束之后,需要清除导管,将状态为NULL就可以
pipeline.set_state(Gst.State.NULL)

以上就是建立DeepStream应用的标准步骤,可以将“def main(args):”部分的代码当作是个模板去加以利用。

至于“osd_sink_pad_buffer_probe”函数的作用,就是从osd接收器提取接收的元数据,并更新绘图矩形、对象信息等的参数,里面的代码也都是标准内容,可以在别的应用在重复套用。更多参数优化的细节部分,须花时间详细阅读DeepStream开发手册。

接下来就实际执行一下Python版本的deepstream-test1代码,看看效果如何!

  • 执行deepstream_test_1.py

前面文章中已经将NVIDIA/AI-IOT/deepstream-python-apps项目下载到Jetson Nano 2GB上的<deepstream< span="">根目录>/sources下面,现在就到这个目录下去执行

  1. cd<deepstream< span="">根目录>/sources/deepstream_python_apps/apps
  2. cd deepstream-test1
复制代码

下面有执行文件deepstream_test_1.py、配置文件dstest1_pgie_config.txt与说明文件README,这个配置文件就是步骤3最后“pgie.set_property”里面指定的文件,在执行文件里看不到任何与推理模型相关的内容,原来都放在设定文件里面去指定了。

关于设定文件的参数设定部分,是相对容易了解的,这里不多花时间说明,接下去直接执行以下指令看看执行结果:

  1. python3 deepstream_test_1.py   ../../../../samples/streams/sample_720p.h264
复制代码

就能跑出我们熟悉的结果,

qw3.jpg

如果觉得左上方显示的字体太小,请自行改动代码第110行的字体号数。下图是字体放大到20号时候的显示结果,现在就可以看到很清楚了。

qw4.jpg

到这里,相信您应该对DeepStream代码有更深层次的了解,在了解整个框架与工作流程之后,可以发现要开发一个基础应用,并不是一件太困难的事情,不过建议您多反复阅读代码内的每一行说明,并且自行适度修改些参数看看效果会有什么变化,一旦熟悉这些逻辑与交互关系之后,就会觉得DeepStream其实很简单。


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2022 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail