关注前沿技术,分享热点话题,QCon全球软件开发大会三站同启,重磅回归!立即查看 了解详情

如何利用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 rpi
deb-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 ui
deb-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 wlan0
static ip_address=192.168.1.1/24 # 官方给的是 192.168.0.10/24,这个自己灵活配置,这个是这台树莓派在它提供出去的 AP 网络里面的 ip 地址
denyinterfaces eth0
denyinterfaces 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=CN
hw_mode=a # g-2.4GHZ;a-5GHZ
channel=149 # 5G 的信道,网上有的写 36 有的是 0 各种,推荐查看下面的信道示意图
wmm_enabled=1
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
ssid=${YOUR_NETWORK_NAME} # 对外暴露的 wifi 名称,请注意不要和已有网络重名
wpa_passphrase=${YOUR_NETWORK_PSWD} # wifi 的密码
ieee80211n=1
ieee80211d=1
ieee80211ac=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:00 1406

评论 2 条评论

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

实现一致性 hash 算法

不在调上

区块链产业正开启“赛马”模式

CECBC区块链专委会

产业落地 政策扶持 赛马模式 技术革命

Cypress与TestCafe WebUI端到端测试框架简介

软测小生

自动化测试 Cypress TestCafe Web UI 测试框架

架构师训练营第五周总结

一剑

ARTS打卡-05

Geek_yansheng25

分布式缓存架构与负载均衡架构

王友

负载均衡 极客大学架构师训练营 消息队列 分布式缓存 第五周

架构师训练营作业-Week5

wyzwlj

极客大学架构师训练营

架构师训练营第五章作业

饶军

操作系统概览

引花眠

计算机基础

第五周-作业2-学习总结

seng man

练习 05-1

闷骚程序员

架构师训练营 - 学习总结 第 5 周

水边

极客大学架构师训练营

iOS sonar实践

余志斐

ios sonar

Week3:作业一

车小勺的男神

读《看见》

YoungZY

消息队列与异步架构||负载均衡架构

独孤魂

【第五周】学习总结——缓存、消息队列、负载均衡

三尾鱼

极客大学架构师训练营

架构师训练营第五周作业

锦澄

一致性Hash算法

Week3:作业二

车小勺的男神

ARTS打卡 第6周

引花眠

ARTS 打卡计划

第五周总结

不在调上

为什么C++可以返回Vector局部变量

韩小非

c++ 内存泄露 函数调用 堆内存管理

架构师训练营 - 第 4 周命题作业

红了哟

区块链各行业应用案例

CECBC区块链专委会

产业落地 政策扶持 去中心化信任 防篡改不可逆 低廉高效

PHP实现一致性Hash算法

Arthur.Li

php 极客大学架构师训练营 一致性hash

第五周总结

胡江涛

极客大学架构师训练营

架构师训练营--第五周作业

花花大脸猫

极客大学架构师训练营

分布式缓存框架

王鹏飞

计算机操作系统基础(十二)---线程同步之自旋锁

书旅

php laravel 线程 操作系统 进程

week5.课后作业

个人练习生niki

架构师训练营第 0 期第 5 周作业

Arthur

极客大学架构师训练营

微软秋季技术课堂

微软秋季技术课堂

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