写点什么

如何利用 VisionSeed+ 树莓派,实现智能小车实时图传系统?

2020 年 9 月 26 日

如何利用VisionSeed+树莓派,实现智能小车实时图传系统?

导语 | 所谓图传,就是把相机模组捕捉到的画面,实时传输到另一个可接收该数据的设备上,并且在该设备上进行实时播放。本文将介绍如何基于 VisionSeed 和 Raspberry Pi(4B)搭建一套完整的图传系统,希望与大家一同交流。文章作者:毛江云,腾讯优图实验室研发工程师。


http://mpvideo.qpic.cn/0bf2jibziaadfaagfy2wr5pvgswdsrfahfaa.f10002.mp4?dis_k=27248bd1e2107c7bcfb47d671741019f&dis_t=1600673227&vid=wxv_1507883883827150849


VisionSeed 智能小车实际跑圈演示视频


一、概念介绍


1. Raspberry Pi


树莓派[1]其实不用笔者过多介绍,这应该是做的最成功的开源硬件芯片,深受技术和数码爱好者们的拥护。下图摘自淘宝某店家的中文说明图,总之第四代比第三代功能强了很多,而且好多接口都与时俱进了。



2. VisionSeed


VisionSeed[2] 是腾讯优图推出的一款具备 AI 功能的摄像头模组,产品如下图所示。它的体型很小,有点类似 Raspberry Pi Zero,不过麻雀虽小五脏俱全。


右边是整块 VisionSeed 的核心模块,包括 2 个摄像头(一个 UVC 摄像头,一个红外摄像头),剩下一整块都是 AI 计算单元;左边是控制板块,主要是对外的接口,如串口、TypeC 接口。两块直接通过 FP C 连接起来。



VisionSeed 模组可以搭载很多 CV 的 AI 能力,目前官方已经推出的有疲劳驾驶监测仪[3],笔者目前参加的智能小车就是正在孵化的另一个项目,期待越来越多的 AI 爱好者们参与进来,把 VisionSeed “玩出花”。


二、系统搭建


本文所介绍的是利用 VisionSeed 和 Raspberry Pi(4B) 搭建的一套 基于 FFMPEG 编码+SRS+WIFI 协议+RTMP 协议+FFPLAY 解码播放 的完整图传系统,该实时图传全过程示意图如下所示:



1. RTMP 推流服务器


推流服务器怎么选择 ?其实推流服务器有很多种选型,具体该选择哪种比较好?笔者结合自身经验给出 nginx-rtmp 服务srs 服务 的使用心得和实际对比:



补充说明一下 延时 这块:首先笔者给出的具体延迟时间并非真正服务器推流的延迟,而是端(VisionSeed 采集到视频)到端(播放设备播放视频)的延迟。


这中间涉及到的环节多且复杂:VisionSeed 的 UVC 视频采集,FFMPEG 编码、推流服务器推流、FFPLAY 解码、以及显示器显示。其中推流服务器推流还包括服务器内部 buffer 缓存、网络数据包拼接和组装、以及网络包的传输等。


2. FFMPEG 编码和推流


那么,该 怎么捕捉 VisionSeed 摄像头的视频呢


VisionSeed 上的摄像头是 UVC。 UVC 全称 Usb Video Class ,是一种标准的 USB 视频设备协议,也就是传说中的免驱摄像头。也就是说,这款摄像头可以通过 USB 即插即用,不需要安装驱动。


于是,我们用一根 TypeC 数据线把 VisionSeed 和树莓派连接在一起。如下图所示:



登录 Raspberry Pi,打开 Terminal,查看 UVC 的状态:$ lsusb。如果出现下图红框的部分,就说明 UVC 被系统识别了。



然后,查看 UVC 被挂在哪个节点上:$ ls /dev/video*:



也可以用 v4l2-ctl 进一步仔细查看 /dev/video* 的信息,如命令 v4l2-ctl --device=/dev/video0 --all 可以查看到摄像头的细节信息,命令 v4l2-ctl --list-devices 查看系统中所有的设备等。



”Linux 系统一切皆文件“,也就是说,系统是直接把 /dev/video0 当作文件来进行处理,也就是在 ffmpeg 的 -i 的参数。


那么, 怎么用 FFMPEG 做编码呢


虽然本地可以直接用 ffplay 播放 UVC 的原始流,但是要走 RTMP 协议做图传的话,必须要使用编码流。因此必须在搭载 VisionSeed 的 Raspberry Pi 上做编码,然后推流出去。


FFMPEG CMD 功能强大:


  • -i :指定文件输入路径;

  • -an :指不处理音频数据;

  • -vcodec:指定视频 codec;

  • -f:指定视频输出格式。


下述命令就是把从 /dev/video0 获取到的原始视频流编码成 flv,并保存成本地文件 test.flv。


ffmpeg -i /dev/video0 -an -vcodec h264 -f flv rtmp://localhost:${port}/${api}
复制代码


综上,FFMPEG 对 UVC 原始视频流做编码就完成了。


3. FFPLAY 收流


必须要确保客户端设备和推流端的 Raspberry Pi 处于同一个局域网下,互相可以联通。


笔者因为项目需要,客户端也选择了 Raspberry Pi +显示屏。但是实际上,选择手上的笔记本或者台式机就可以,只要确保安装了 FFMPEG(FFMPEG、FFPLAY 和 FFPROBE 是打包的)。


那么, 如何用 FFPLAY 实时显示 RTMP 视频呢


笔者原本以为播放端出不了什么问题,万万没想到 FFPLAY 的问题竟然无比的“坑”。首先,很多类似的网站教程提供的播放命令大多是:ffplay -i rtmp://${ip}:${port}/${api}。


这个命令存在相当大的延时,导致笔者最开始就走错了方向。ffplay 内部有 buffer,因此看到的播放画面其实是好几十秒之前的!


ffplay 播放时间长了会有累积延时,也就是越播放到后来,延时越大。并且画面时不时出现卡顿、有时候还会发现画面帧率不稳定,时快时慢。当推流端/服务端断开时,ffplay 画面就卡住了,超过 2 min 也并不会退出。


这些问题该怎样解决呢?下文将会来详细讨论。


三、优化延时


按照以上的流程搭建好之后,就可以在客户端上看到 VisionSeed 的视频画面了。但是,延时巨大,主观感受至少有 10s 的延迟,所以还需要进一步做优化。


1. FFMPEG 硬解码


细心的同学在使用 FFMPEG 做编码的时候,应该发现实际编码推流的帧率大约在 18 左右,运行到后来大概稳定在 10 左右,笔者这边的情况如下图所示:



但是,VisionSeed(关闭算法功能后)的原始视频帧率是 28 fps,分辨率是 1280x720。由此可见 Raspberry Pi 4B 的 CPU 编码速率跟不上,必须要优化。


虽然这个 CMD 没有开启多线程,但是根据笔者经验来看,即使多线程开满了,也很难满足性能要求。


要知道 Raspberry Pi 4B 是有专用编解码模块的,官方号称性能是 1080p@30fps 的编码能力,而端侧开发就是要“榨干”每一个芯片模组的过程。


查了资料后了解到,Raspberry Pi 的硬解码支持 OPENMAX 标准[4],这是一种类似 vaapi 等多媒体硬件加速的统一接口,因此可以直接用 h264_max 来调用底层硬解码,然后再推送到推流服务器上,命令如下:


ffmpeg -i /dev/video0 -an -vcodec h264_omx -f flv rtmp://localhost:${port}/${api}
复制代码


此外,对实时性要求再高一点的同学,不妨再多了解些 FFMPEG 的参数,参见 FFmpeg Formats Documentation[5]H.264 Video Encoding Guide[6],笔者下面摘录一些跟效率相关的参数,大家可以选择使用(如果有更多未列出来的,欢迎大家留言补充):


### 延时相关- fflags nobuffer # 减少由于buffer带来的延时,能够做到即时处理。- fflags flush_packets # 马上把packets刷出来。(实际好像没有对降低延时带来作用)- analyzeduration ${整型值|时间} # 流分析时间,数值越长,得到的流信息越多、准确,但是延时上升,默认值是5秒。(对编码流会起作用,原始流应该没啥作用)- max_delay ${整型值|时间} # 设置(解)封装的最大延时。(对封装格式的编码流有作用,原始流应该没啥作用)- framerate ${整型值|时间} # 输入视频的码率,默认值是25。(建议可以用-re,这个是用输入视频的码率)
复制代码


而笔者最后使用的命令如下:


ffmpeg -r 28 -fflags nobuffer -fflags flush_packets -i /dev/video0 -vf fps=fps=28 -an -vcodec h264_omx -preset slower -tune zerolatency -max_delay 10 -r 28 -video_size 1280x720 -g 50 -b:v 8192k -f flv "rtmp://{RTMPIP}:{RTMPPORT}/live/1"
复制代码


2. 用 srs 推流服务器,开启优化参数


从最终结果上来看,替换了 srs 服务器之后,时延确实比用 nignx-rtmp 提升了 400 ms。


但是这到底是因为 srs 确实比 nginx-rtmp 优秀呢,还是因为笔者打开 nginx-rtmp 方式不正确,还有待讨论。对这块有了解的大佬们,欢迎留言告知,不胜感激!


修改 srs.conf 如下:


listen              1935; # rtmp端口 !!可以修改为自己的端口号!!max_connections     1000;srs_log_tank        file;srs_log_file        ./objs/srs.log;http_api {    enabled         on;    listen          1985;}http_server {    enabled         on;    listen          80;    dir             ./objs/nginx/html;}stats {    network         0;    disk            sda sdb xvda xvdb;}vhost __defaultVhost__ {    #最小延迟打开,默认是打开的,该选项打开的时候,mr默认关闭。    min_latency     on;    #Merged-Read,针对RTMP协议,为了提高性能,SRS对于上行的read使用merged-read,即SRS在读写时一次读取N毫秒的数据    mr {        enabled     off;        #默认350ms,范围[300-2000]        #latency     350;    }    #Merged-Write,SRS永远使用Merged-Write,即一次发送N毫秒的包给客户端。这个算法可以将RTMP下行的效率提升5倍左右,范围[350-1800]    mw_latency      100;    #enabled         on;    #https://github.com/simple-rtmp-server/srs/wiki/v2_CN_LowLatency#gop-cache    gop_cache       off;    #配置直播队列的长度,服务器会将数据放在直播队列中,如果超过这个长度就清空到最后一个I帧    #https://github.com/simple-rtmp-server/srs/wiki/v2_CN_LowLatency#%E7%B4%AF%E7%A7%AF%E5%BB%B6%E8%BF%9F    queue_length    10;    #http_flv配置    http_remux {      enabled     on;      mount [vhost]/[app]/[stream].flv;      hstrs  on;    }}
复制代码


配置修改之后的确实时性得到了很大的提升。


3. 优化 FFPLAY


上文出现的问题,在这里也为大家一一解答。


问题一: ffplay 内部有 buffer,因此看到播放的画面是好几十秒之前的。


解决方法:关闭 buffer!


参考 ffplay Documentation[7],参数 -fflags nobuffer(FFMPEG 命令里面也有这个参数)是最关键的。笔者最后采用了:


ffplay -autoexit -fflags nobuffer -fflags flush_packets -flags low_delay -noframedrop -strict very -analyzeduration 600000 -i rtmp://192.168.1.1:2020/live/1
复制代码


问题二: ffplay 播放时间长了会有累积延时,也就是越播放到后来,延时越大。并且画面时不时出现卡顿、有时候还会发现画面帧率不稳定,时快时慢。


解决方法:自从用了 srs,累积延时的问题就没有了。至于画面不稳定的问题,可能和网络有关,笔者后面也会提到怎么在树莓派上搭建无线 AP 来提供专有无线局域网。


替换到无线 AP 之后,画面卡顿的情况会好很多。但是经过长时间的观察,还是会有帧率不稳定的情况。


问题三 :当推流端/服务端断开时,ffplay 画面就卡主了!超过 2 min 也并不会退出。


解决方法:这个其实就是 FFPLAY 的 bug !其实,ffplay 提供了几个参数,一个是 -autoexit,但是它对 RTMP 还有 RTSP 都不起作用,当流断开或者网断开的时候, ffplay 还是卡住的。


要想解决就必须修改源码,重新编译 ffplay。修改办法可以参考文档[8]


另一个是 -timeout 参数,但是一旦加上它,ffplay 就跑不起来,具体原因参考文章[9]


4. Raspberry Pi 上搭建无线 AP


怎么基于 Raspberry Pi 搭建无线 AP 是有官方教程的:How to use your Raspberry Pi as a wireless access point[10]。但是,官方教程是有坑的,下文将重点介绍哪些坑需要避开。


无线 AP,全称 Wireless Access Point,其实就是常说的 WIFI 热点,生活中的路由器也是一种无线 AP 设备。


在我们的环境中,可能会在没有无线网环境下,甚至在网络条件很糟糕的环境下。另外 Raspberry Pi 4B 本身自带了无线网卡,因此不妨用它搭建无线 AP,客户端直接接入它的网络就可以接受它的流数据。


而且,从网络链路上来说,无线 AP 还能在原来基础上减少路由器转发的环节。网络拓扑图如下图所示,优化的链路环节是把蓝色虚线替换了红色虚线和实线。



接下来,跟着官方教程一步步走:


(1)升级 apt-get 工具


建议国内的小伙伴们替换一下源,笔者早就已经替换到了清华源,教程网上很多,推荐树莓派 3b 更换国内源[11]


/etc/apt/sources.list 修改为:


deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main contrib non-free rpideb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ stretch main contrib non-free rpi
复制代码


/etc/apt/sources.list.d/raspi.list 修改为:


deb http://mirror.tuna.tsinghua.edu.cn/raspberrypi/ stretch main uideb-src http://mirror.tuna.tsinghua.edu.cn/raspberrypi/ stretch main ui
复制代码


(2)安装 hostapd 和 dnsmasq


hostapd 就是对外提供热点的主要服务,dnsmasq 则是负责 dns 和 dhcp 作用的。如果操作完毕后,没有搜索到自己设置的热点 WIFI,大概率是 hostapd 的问题(原因:hostapd 没有工作,因此没有对外提供 AP);如果搜到了热点网络,但是一直连不上的话,大概率是 dnsmasq 的问题(原因:dnsmasq 没有工作,没办法为客户端分配 ip)。


Raspberry Pi 为自己分为静态 IP。笔者这里的设置如下:


interface wlan0static ip_address=192.168.1.1/24 # 官方给的是192.168.0.10/24,这个自己灵活配置,这个是这台树莓派在它提供出去的AP网络里面的ip地址denyinterfaces eth0denyinterfaces wlan0
复制代码


(3)配置 DHCP


笔者这里的配置如下:


interface=wlan0  dhcp-range=192.168.1.6,192.168.1.12,255.255.255.0,24h # ip范围自定义就可以
复制代码


(4)修改 hostapd 配置


这一个步骤是最容易出问题的步骤,而且官方提供的配置在笔者的环境中并不能起作用。笔者给出自己的配置,并在注释中说明为什么这么配置:


interface=wlan0#bridge=br0    # 这个一定要去掉,因为笔者不需要做桥接(不需要外网)。country_code=CNhw_mode=a    # g-2.4GHZ;a-5GHZchannel=149    # 5G的信道,网上有的写36有的是0各种,推荐查看下面的信道示意图wmm_enabled=1macaddr_acl=0auth_algs=1ignore_broadcast_ssid=0wpa=2wpa_key_mgmt=WPA-PSKwpa_pairwise=TKIPrsn_pairwise=CCMPssid=${YOUR_NETWORK_NAME} # 对外暴露的wifi名称,请注意不要和已有网络重名wpa_passphrase=${YOUR_NETWORK_PSWD} # wifi的密码ieee80211n=1ieee80211d=1ieee80211ac=1
复制代码



其实做到这一步之后,基本就达到目标了。一个可用的无线 AP 就搭建好了。客户端只需要连接到刚刚设置的网络,就可以和这台 Raspberry Pi 通信。


终于,笔者把端到端的延时从十几秒优化到 400 ms 左右(一把辛酸泪)!最后,如果您有更多的优化方案,欢迎留言与我讨论~


参考资料


[1] Raspberry Pi:


https://www.raspberrypi.org/


[2] VisionSeed:


https://visionseed.youtu.qq.com/#/home


[3] 疲劳驾驶监测仪:


https://zhuanlan.zhihu.com/p/77190381


[4] OPENMAX 标准:


https://zh.wikipedia.org/wiki/OpenMAX


[5] FFmpeg Formats Documentation:


https://ffmpeg.org/ffmpeg-formats.html


[6] H.264 Video Encoding Guide:


https://trac.ffmpeg.org/wiki/Encode/H.264


[7] ffplay Documentation:


https://ffmpeg.org/ffplay-all.html


[8] -autoexit 参数修改参考:


http://ffmpeg.org/pipermail/ffmpeg-devel/2020-August/268749.html


[9] -timeout 参数修改参考:


https://www.jianshu.com/p/e75e3f1fb6b0


[10] How to use your Raspberry Pi as a wireless access point:


https://thepi.io/how-to-use-your-raspberry-pi-as-a-wireless-access-point/


[11] 树莓派 3b 更换国内源:


https://my.oschina.net/TimeCarving/blog/1622950


本文转载自公众号云加社区(ID:QcloudCommunity)。


原文链接


如何利用VisionSeed+树莓派,实现智能小车实时图传系统?


2020 年 9 月 26 日 14:001529

评论 2 条评论

发布
用户头像
好像很好玩的样子。
2020 年 09 月 27 日 06:48
回复
用户头像
感谢分享
2020 年 09 月 26 日 23:07
回复
没有更多了
发现更多内容

产品周刊 | 第 15 期(20200517)

Herbert

产品 设计 产品经理 产品设计

谈谈控制感(7):底线思维与控制感

史方远

职场 心理 成长

Spring Security 中的授权操作原来这么简单

江南一点雨

Java spring Spring Boot spring security

换脸新潮流:BIGO风靡全球的人脸风格迁移技术

DT极客

MyBatis支持的jdbcType 枚举类型

Kevin Liao

Redis缓存三大问题

Bruce Duan

redis 缓存穿透 缓存击穿 缓存雪崩

如何更自信的写作

七镜花园-董一凡

写作

给苹果提醒APP配个助手

BabyKing

提醒助手 TODO 奇妙清单 Reminders Helper

NIO看破也说破(四)—— Java的NIO

小眼睛聊技术

Java 学习 开源 架构 后端

中小型城市商业银行数字化转型实践(三)数据中台建设思路和路径

泡菜小仙

数据中台 数字化转型 数据架构

在Gitlab-ce的Docker中使用自定义端口

天飞

Docker gitlab

Kafka系列第7篇:你必须要知道集群内部工作原理的一些事!

z小赵

大数据 kafka 实时计算

JAVA主流锁

颇风

Java 多线程

东哥和刘亦菲的故事

张利东

R

设计模式前传——为什么要学设计模式

海星

Java 面试 设计模式

单核小鸡上的Minikube实践(一)

摩登土狗

Docker Linux DevOps k8s minikube

linux文件系统-inode学习整理

戈坞昂

Linux inode

ZooKeeper,到底如何选主?

奈学教育

重新强调完成的定义

Bob Jiang

Scrum 完成的定义 DoD definition of done

中小型城市商业银行数字化转型实践(二)集成关系ESB APIGateway ServiceMesh

泡菜小仙

架构设计 集成架构 ESB

游戏夜读 | Two Sum问题的八个解

game1night

DDD 实践手册(番外篇: 事件风暴-概念)

Joshua

领域驱动设计 DDD 事件风暴 事件驱动 Event Storming

npm下载electron缓慢的问题

玏佾

npm Electron

识别代码中的坏味道(三)

Page

敏捷开发 面向对象 重构 代码质量 代码坏味道

中小型城市商业银行数字化转型实践(一)整体技术架构转型(双态IT)

泡菜小仙

数字化转型 架构设计 技术架构

看得懂的区块链及智能合约概念

石君

区块链 智能合约

Vue+SpringBoot+SpreadJS 实现的在线文档

Geek_Willie

Spring Boot Vue SpreadJS

项目提升服务过程与总结稿

Geek_bc0aff

MacOS 下使用VSCode进行GoLang Test报错

北纬32°

golang macos vscode Unit Test debug

我的读书笔记-樊登读书法

lmymirror

学习 读书笔记 方法论 读书方式

部门最漂亮的妹子离职了

无箭的丘比特

团队管理 生涯规划 企业文化 职场

NLP领域的2020年大事记及2021展望

NLP领域的2020年大事记及2021展望

如何利用VisionSeed+树莓派,实现智能小车实时图传系统?-InfoQ