赞
踩
想做网络小车这一想法起因于大二的时候,有一次在抖音刷视频,看到了一个ID为一片枫叶的大佬发的一个视频,是一个从零开始制作的网络小车的全过程,从机械、硬件到软件,全部是他自己一个人完成的,当时觉得很酷,感觉有点像是第二个“稚晖君”,膜拜。且在疫情时代,网络小车可以帮助人们出门买菜取快递等,降低一些接触风险,甚至往大了说可以在工业煤炭等行业,乃至抗震抢险救灾等不适合人直接探索的地区,去代替人去完成一些工作。当然最主要的是,在制作网络小车的过程中,可以学习到许多关于嵌入式、网络通信、服务器搭建、APP编写以及人工智能等相关知识。
本人目前是大四通信工程专业学生,之前有过相关硬件及软件方面的学习,在此记录一下制作网络小车的全过程,也许是一件有意义的事情。
(注:我的制作思路与方案与前文提到的一片枫叶视频中的思路与方案不同,原因是我后来也学习了解了一些网络小车的相关知识,发现一片枫叶大佬是有很多工作经验的,所以他使用了webrtc采用C++去编写流媒体的相关程序,以及后面的与其他公司合作编写APP,这一过程并不适合大多数在校学生去完成,所以只能采取别的方法。)
(前半段为远程驾驶、视频传输、环境监测、电量监测展示,后半段为音频双向传输展示。测试环境:本人位置位于河南郑州,车位于河南洛阳,服务器为 乌兰察布的阿里云服务器)
以下正文:
概括来讲,该4G网络小车最终需要实现:通过4G网络,传输音、视频信息以及远程遥控信号,实现在只要有运营商网络覆盖的范围内,即可实现移动式的、不受距离约束的双向信息交互。
采用端-云-端的设计理念,整个系统分为移动终端、云端服务器和APP监控端三部分。其中移动终端分为控制部分和音视频传输部分,控制部分将RC越野车改装作为移动平台,音视频传输部分使用csi摄像头和WM8960声卡,分别通过H264编码和PCM编码来传输信号,选用树莓派作为硬件开发平台,其上搭载Linux操作系统作为软件开发平台;云端租用阿里云服务器,完成音视频信号的存储转发;APP端实现音视频监控及4G远程遥控。
系统整体采用端-云-端架构,按层来划分,可分为感知层、汇聚层、传输层和应用层四个层,感知层和汇聚层属于移动终端,传输层属于云端,应用层属于监控端,端云之间通过消息队列遥测传输(message queuing telemetry transport,MQTT)协议和实时传输协议(Real-time Transport Protocol,RTP)完成数据传输。
在整个系统中,以树莓派搭载的Linux操作系统以及云端的阿里云主机和独立服务器为运行环境。感知层可分为数据采集系统和小车控制系统两个部分,在数据采集系统中,csi摄像头用来获取外部图像,传输视频信号;WM8960声卡获取外部声音,传输音频信号;AD芯片将电池电压转换为数字信号进行传输。小车控制系统包含舵机和电机,通过接受4G遥控信号来操控小车。汇聚层即为搭载树莓派4B的远程探测小车,将感知层获取到的信息进行处理,并将处理后的信息发送至位于传输层的阿里云服务器,同时也可接受服务器传来的信息,进行双向数据交互。用户的手机端APP位于应用层,可接受小车传输来的音视频信息并显示,同时可将操控小车的指令打包成为4G遥控信号发送至传输层阿里云服务器。即传输层与应用层、传输层与汇聚层之间均为双向数据传输。整个系统的框架如图所示。
移动终端(即小车)的控制部分和音视频传输部分,均需要处理器进行控制,经过对几款常见的嵌入式开发处理器,如:Arduino、STM32、ESP8266、树莓派、香蕉派、K210等进行比对后,最终选用树莓派4B作为移动终端的核心处理器。
树莓派是一款基于Linux操作系统,只有信用卡大小的微型电脑。它以SD/MicroSD卡为内存硬盘,主板上有4个USB接口,和一个以太网接口,可连接键盘、鼠标和网线,同时拥有视频模拟信号的电视输出接口和HDMI高清视频输出接口,以上部件全部整合在一张仅比信用卡稍大的主板上,具有所有PC的基本功能,非常适合用来作为本项目的主控。
其实树莓派对新手来说并不是很友好,真正进行开发之前还需要很多步骤去熟悉树莓派,如烧录操作系统、ssh远程控制、vnc远程控制、命令行的基本操作、Linux系统的熟悉、网络连接等等,我在开始之前先学习了大概一个月的树莓派,才对其完整流程真正熟悉。
一些较为基础的Linux指令:
- pwd 即print working directory打印当前的工作目录
- ls 查看当前根目录下有什么文件
- cd xxx 进入某个文件中
- cd .. 返回上一级目录
- ls / 若当前不在根目录中,但想查看根目录中的内容
- cd /usr/bin 通过绝对路径进入某个文件中
- clear 清除当前屏幕
网络对树莓派来说是必须具备的,是VNC远程连接的关键,其次,我们的4G小车旨在只要有运营商网络覆盖的地方均可进行遥控,就需要树莓派有自己的网络,而不是去连接别人的wifi。
树莓派拥有自己的4G网络可以通过4G模块或者随身的WIFI,由于4G模块过于昂贵,最终我选择了随身wifi,将其直接插在树莓派的USB接口上即可。
什么是带宽?
在计算机网络中,带宽通常指网络的传输速率。它是指从一个点到另一个点所能通过的最大数据量。带宽通常用位/秒(bps)表示,它可以影响网络中的数据传输速度和延迟。更高的带宽意味着更快的传输速度和更少的延迟,而较低的带宽则可能导致数据传输速度慢和延迟高。
在单位时间内从网络中的某一点到另一点所能通过的“最高数据率”。对于带宽的概念,比较形象的一个比喻是高速公路。单位时间内能够在线路上传送的数据量。
100M带宽,指的是100Mbps,即“每秒100M的比特数”,就是指该网络里能发送数据的最大速度是100Mbit。
我们访问互联网的过程中存在这两种行为:一是上传数据,二是下载数据。上行带宽(速度)指的是上传的速度,比如你发邮件、上传照片等;而下行带宽(速度)指的是下载数据是的数度,比如你看电影,下载东西。
移动通信中,上行带宽(速率)是指移动终端给基站发送信息时的数据传输速率,比如手机、笔记本等无线终端给基站传输数据速率;下行带宽(速率)是指基站向移动终端发送信息时的传输速率,比如手机或笔记本等无线终端从基站或者网络下载数据的速率。上行宽带(速度)和下行宽带(速度)是不对称的,一般是下行速度大于上行的速度。我们平时所使用的宽带说多少M,都是指的下行宽带,因为我们上网主要是从互联网上下载数据,而上传的数据量要少很多。
在使用宽带的过程中,发现电脑中下载的速度根本就达不到自己办理的宽带的标准,例如办理的10M的宽带,结果下载速度只有1M/s左右的速度,这是为什么呢?
这是因为运行商M的宽带下行速度和Windows电脑上面下行速度的单位不一样,Windows电脑的单位是KBps(每秒传输的Byte数),而运营商的宽带单位是Kbps(每秒传输的bit数),1Byte=8bit。例如你从宽带运营上那里办理了10M的宽带,我们知道1M=1024K,因此10Mbps=10240Kbps=1280KBps(10240Kbps/8),所以你在电脑上下载数据的最大下载速度只有1280KBps,也就是只有1M/s左右的样子。
这是TD-LTE无线数据终端,LTE根据双工方式不同,分为LTE-TDD和LTE-FDD两种制式,其中LTE-TDD又称为TD- LTE。
需要使树莓派上电以后自动连接该wifi,需要进行配置:
在SD卡中新建一个wpa_supplicant.conf的文件,写入一下内容:
country=CN ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1 network={ ssid="4G_Car" psk="wangsijie975." priority=7 #数字越大,优先级越高 } network={ ssid="wsj" psk="wangsijie975." priority=6 } network={ ssid="CMCC-GfKg" psk="001126wsg" priority=5 }
经过在网上查阅相关资料后,发现树莓派对供电的要求比较苛刻,若供电不稳,电源纹波有可能会击穿烧毁树莓派,现在市面上的树莓派的价格已经上千元了,普通人根本玩不起啊,我这个树莓派还是老师给的,可不敢懈怠,但问题是树莓派安装在小车上有不能用它原装的电源适配器,我硬件水平不够,不敢自己画一个供电板子,最后思前想后,斥巨资(150¥)买了个微雪电子的电源模块:UPS HAT (B) - Waveshare Wiki
将声卡、摄像头、网卡、电源模块都安装好后,如图所示,讲真此时我已经花了不少钱了,哭。
在开始编程之前,需要现在树莓派上安装一些库和依赖,以及进行相关的配置。
更新源的命令:
- sudo apt-get update
- sudo apt-get upgrade
安装依赖:
- sudo pip3 install --upgrade pip
- sudo pip3 install wave
- sudo pip3 install picamera
- sudo pip3 install paho-mqtt
- sudo pip3 install Adafruit_ADS1x15
- sudo pip3 install Adafruit_MCP4725
验证是否安装成功:
- python3
- import wave
- import picamera
- import RPi.GPIO as GPIO
- import paho.mqtt.client as mqtt
- import Adafruit_ADS1x15
- import Adafruit_MCP4725
- import alsaaudio #这个是声卡的库,前面提到过
若没有报错,即安装成功,如图:
此外,还需要设置串口为TTL:
- 在命令行输入sudo nano /boot/config.txt
- 加入:dtoverlay=pi3-miniuart-bt (设置串口为TTL,因为舵机是通过ttl电平传输数据的)
- 保存后退出
之后,通过输入ls -l /dev (查看设备目录),如图设置成功:
我在淘宝上买了莽牛rc越野车模:https://m.tb.cn/h.UldMHLV?tk=wMDYd5pUonz,准备将其改装作为移动终端。
飞特数字舵机:https://m.tb.cn/h.UP76ilq?tk=akQfd5p5Gsk,可通过串口协议控制。
为了进行音频信号的双向传输,需要一块声卡来处理音频信号,我选用了微雪电子的WM8960声卡,该产品是基于树莓派而设计的音频模块,采用WM8960低功耗立体声编解码器,通过I2C接口控制,I2S接口传输音频。板载标准3.5mm耳机接口,可通过外接耳机播放音乐,同时也可通过双通道喇叭接口外接喇叭播放。板子左右两边有一个高质量MEMS硅麦克风,可以立体声录音。
在使用该声卡之前,首先需要对树莓派进行相关配置,才可以正常使用,链接为:WM8960 Audio HAT - Waveshare Wiki
在进行配置时,我踩了许多坑,最终才将其配置好。我一开始给树莓派安装的是64位的操作系统,安装驱动的时候一切正常,但是当在树莓派上安装声卡对应的库时,一直报错,如图:
问了微雪电子的客服,说是让用32位的操作系统试试,无奈我只好又去安装32位的操作系统(我是在上面那个链接里下载的微雪电子官方提供的镜像,以为用官方提供的镜像总可以了吧),把之前的繁琐操作重复一遍,头大,但是结果还是在安装声卡对应的库时报错,又问了微雪电子客服,他说那个镜像是2019年的,太旧了,可能版本不支持,让我再用最新的32位操作系统试试,这里真的欲哭无泪,我只好又双叒叕将之前繁琐的操作进行一遍,但,功夫不负有心人,终于成功了!(泪目( Ĭ ^ Ĭ ),差点以为我的网络小车之旅就要在这里结束了。)
csi摄像头链接:https://m.tb.cn/h.UlynvAq?tk=R0zcd5pRh5z
树莓派上的csi接口专门用来连接csi摄像头,CSI接口规范是由MIPI(Mobile Industry Processor Interface)联盟组织于2005年发布的关于相机串行接口,它作为一种全新的相机设备和处理器之间的接口框架,给便携式、手机摄像头等相关产业提供了一种灵活且高速的设备接口。
此前,传统摄像头接口一般都包括了数据总线、时钟总线、同步信号线控制线等,物理接口框图如下所示:
这种摄像头物理接口所占用的数据线较多,逻辑设计上也比较复杂,需要严格同步包括水平同步信号,垂直同步信号以及时钟信号,这对摄像头这端以及接收器这端都提出了较高的要求,同时,在高速传输的过程中,直接使用数字信号作为数据容易被其他外部信号干扰,不如差分信号的稳定性,这样也大大限制了其传输的速率以及相机最大能够实时传输的图像质量。
而基于CSI摄像头数据传输过程使用了数据差分信号对视频中像素值进行传输,同时CSI传输接口能够非常灵活的进行精简或者扩展,对于接口较少的应用场景,CSI接口可以只使用一组差分数据信号线以及一组差分时钟线就能够完成摄像头的数据串行传输过程,这样便减少了负载,同时也能够满足一定的传输速率,而对于大阵列的CCD相机,CSI接口也能够扩展其差分数据线,从而满足多组数据线并行传输的高速要求。
测试摄像头功能:
在测试摄像头功能之前,首先需要在树莓派中打开摄像头,在命令行中输入:
sudo raspi-config
选择Interface Options选项,再打开摄像头即可。
以下通过两种方式进行测试:
通过指令直接拍照测试
首先可以先查看一下摄像头状态:
vcgencmd get_camera
如图检测到了摄像头
然后输入拍照命令:
raspistill -o test.jpg
若得到一张图片,则摄像头功能正常。
2.通过程序控制摄像头拍照测试
新建一个test文件夹,在里面创建一个main.py的python文件:
-
- import picamera
- import time
- from VHandler import VHandler
-
- print("hello")
-
- while True:
- with picamera.PiCamera() as camera:
- camera.resolution=(1280,720)
- camera.framerate = 15
- camera.start_recording(VHandler(),format='mjpeg',quality=100,bitrate=3300000)
-
- time.sleep(1)
- print("-----------------")
再建立一个VHandler.py的python文件:
-
- from PIL import Image
- from io import BytesIO
-
- class VHandler():
- def __init__(self):
- pass
- def write(self,s):
- print(s)
- image=Image.open(BytesIO(s))
- image.save('1.jpg')
然后在命令行中,运行该程序:
得到一张图片,测试成功。
查看树莓派上有哪些I2C的地址:
sudo i2cdetect -y 1
UU是声卡的地址,42是电源的地址
当后期所有工作完成后,若想要树莓派上电以后不再进入桌面系统,而是直接进入命令行,会节省很多开销,方法是:在树莓派桌面的configuration选项卡下的Boot选项卡下,选择To CLI即可。
树莓派40pin引脚对照表:
注:BCM编码在python编程时使用,wiringPi编码在C语言编程时使用。
移动终端和云端,以及云端和监控端之间都是双向通信的,这需要用到网络编程的相关知识,有必要去提前学习一下。
网络编程的目的:
1.使用网络能够把多方链接在一起,然后可以进行数据传递。
2.所谓的网络编程就是,让在不同的电脑上的软件能够进行数据传递,即进程之间的通信,
一个IPv4地址分成两块:一块表示网络号,一块表示主机号。
例如192.168.43.131
一般来说,192.168.43表示网络号,131表示在这个网络中的主机号(这是C类IPv4地址,三组数据表示网络号,1组数据表示主机号)
IPv4按照功能划分,可分为私有地址和公有地址。
私有地址:通俗地理解,不能上互联网的IP地址叫做私有IP地址。在这么多网络IP中,国际规定有一部分IP地址用于局域网使用,也就是属于私网IP,不在公网中使用,它们的范围是:
10.0.0.0~10.255.255.255 (A类私有地址部分)
172.16.0.0~172.31.255.255 (B类私有地址部分)
192.168.0.0~192.168.255.255 (C类私有地址部分)
IP地址127.0.0.1~127.255.255.255用于回路测试,ping 127.0.0.1用来测试当前电脑是否具备网络功能,如果能ping通,则表示具备网络功能,否则不具备。
IP地址用来标记一台电脑,为了能够标记电脑上运行的不同进程(如QQ、微信、微博等),需要使用端口来标记。
进程:是运行中的程序。
端口是通过端口号来标记的,端口号只有整数,范围是从0~65535。
知名端口
知名端口是众所周知的端口,用来给特殊的进程使用,普通的进程不能使用这些端口,范围从0到1023。
21端口分配给FTP服务
22端口分配给SSH服务 (SSH远程登录Linux系统)
80端口分配给HTTP服务
动态端口
动态端口是普通进程直接可以使用的,范围是1024-65535。
程序如果想通过网络进行收发数据,需要使用socket进行编程来实现。
Socket(简称套接字)是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程通信,我们网络上各种各样的服务大多是基于socket来完成通信的。
完成网络通信所必须具备的条件:
1.源IP、目的IP
2.源端口、目的端口
3.协议(TCP或UDP)
创建socket,在 Python 中 使用socket 模块的socket 函数就可以完成:
-
- import socket
- socket.socket(AddressFamily, Type)
说明:
函数 socket.socket 创建一个套接字,该函数带有两个参数:
Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET
Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)
前面步骤已经交换了硬件串口与mini串口的映射关系,但是,现在还不能使用树莓派串口模块与电脑进行通信,因为,树莓派gpio口引出串口默认是用来做控制台使用的,即是为了用串口控制树莓派,而不是通信。所以我们要禁用此默认设置。
首先执行命令如下:
sudo systemctl stop serial-getty@ttyAMA0.service
sudo systemctl disable serial-getty@ttyAMA0.service
然后执行命令行修改文件:
sudo nano /boot/cmdline.txt
并删除语句console=serial0,115200(没有的话就不需要此步骤)
移动终端分为控制部分和音视频传输部分,两部分均以树莓派4b作为主控,编程语言为python。
由于移动终端包含许多任务:音频接收、音频传输、视频传输、车体控制等,所以给不同的任务分配不同的进程,由于多进程之间数据是不共享的,通过进程管理器(Manager)和队列(Queue)完成进程间通信,以提高代码运行效率。
程序总体流程为:
1.树莓派上电后,循环检测网络是否连接成功,直至网络成功连接才跳出循环(由于树莓派上电以后会立即执行程序,但此时网卡并没有准备好,而良好的网络连接是后面所有操作的基础,所以程序的第一步是确保网络成功连接,才会跳出测试网络连接这一功能)
2.对WM8960声卡进行音量设置。
3.创建进程管理器和队列的对象,并设置服务器IP地址、音频录制端口、音频播放端口、图传端口等。
4.创建录音对象、设置进程守护、开启录音进程。
5.创建播音对象、设置进程守护、开启播音进程。
6.创建图传对象、设置进程守护、开启图传进程。
7.通过串口通讯协议,接收4G信号,控制舵机与电机。
程序流程图如图所示:
该功能主要是通过urllib3库访问网页,由此来检测网络是否成功连接,urllib3是一个功能强大,友好的,用于http客户端的Python库。
首先构造一个PoolManager的连接池实例对象,用来处理与线程池的连接以及线程安全的所有细节,然后通过Get请求,访问百度网页,同时获取此次访问结果的返回值并存储下来,将结果记录在Log日志中,若返回值等于200,表示网络连接成功,跳出检测网络的程序,执行后续代码,否则等待1s后,重复以上操作,程序流程图如下。
代码如下:
-
- while True: #测试网络是否连接成功
- try:
- r=httptest.request('GET','http://www.baidu.com')
- internetStatus=r.status #若返回值为200,表示网络连接成功
- myLog.logger.info(internetStatus)
- if(internetStatus==200):
- myLog.logger.info("internet connect success")
- break
- time.sleep(1)
- except Exception as e:
- myLog.logger.info(e)
- time.sleep(1)
-
- ipAddress="??.??.??.119" #服务器IP地址
- rcPort=8095 #树莓派上录制的语音信息,通过该端口号传到服务器
- pcPort=8096 #语音播放端口
- imgPort=8098 #图传端口
- vQueue = Queue() #队列
创建进程管理器,用于进程间通信:
-
- ma = multiprocessing.Manager() #创建进程管理器
- manager = ma.dict({"voiceStatus": False, #voiceStatus(是否在语音通信)
- "musicStatus":False, #musicStatus(是否在播放音乐)
- "pipeline":0, #pipeline 如果有指令进入pipeline开始递增,如果指令停止1秒 则通过pipeline探测
- "lowPower":False
- })
录音进程的任务是:将小车上录制到的声音发送给服务器。
首先创建录音对象,有三个参数分别为服务器地址、音频录制端口和进程管理器:
-
- rc=RecordClient(ipAddress,rcPort,manager)
传输音频信号需要用到PCM编码,所以录音进程中需要创建一个PCM编码器用来传输音频
,通过alsaaudio库中的PCM模块来完成:
-
- self.inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK,
- channels=1, rate=16000, format=alsaaudio.PCM_FORMAT_S16_LE,
- periodsize=2000, device=self.device)
相关参数和介绍:alsaaudio — alsaaudio documentation 0.9.2 documentation (larsimmisch.github.io)
关于PCM编码
脉冲编码调制 (PCM)就是把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在信道中传输。脉冲编码调制就是对模拟信号先抽样,再对样值幅度量化,编码的过程。
抽样,就是对模拟信号进行周期性扫描,把时间上连续的信号变成时间上离散的信号,抽样必须遵循 奈奎斯特 抽样定理。该模拟信号经过抽样后还应当包含原信号中所有信息,也就是说能无失真的恢复原模拟信号。它的抽样速率的下限是由抽样定理确定的。抽样速率采用8KHZ。
量化,就是把经过抽样得到的瞬时值将其幅度离散,即用一组规定的电平,把瞬时抽样值用最接近的电平值来表示,通常是用二进制表示。( 量化误差:量化后的信号和抽样信号的差值。量化误差在接收端表现为噪声,称为量化噪声。量化级数越多误差越小,相应的二进制码位数越多,要求传输速率越高,频带越宽。为使量化噪声尽可能小而所需码位数又不太多,通常采用非均匀量化的方法进行量化。非均匀量化根据幅度的不同区间来确定量化间隔,幅度小的区间量化间隔取得小,幅度大的区间量化间隔取得大。 )
一个模拟信号经过抽样、量化后,得到已量化的 脉冲 幅度调制 信号,它仅为有限个数值。
编码,就是用一组二进制码组来表示每一个有固定电平的量化值。然而,实际上量化是在编码过程中同时完成的,故编码过程也称为模/数变换,可记作A/D。
音信号先经防混叠 低通滤波器 ,进行脉冲抽样,变成8KHz重复频率的抽样信号(即离散的脉冲调幅PAM信号),然后将幅度连续的PAM信号用“四舍五入”办法量化为有限个幅度取值的信号,再经编码后转换成二进制码。对于电话, CCITT 规定抽样率为8KHz,每抽样值编8位码,即共有2^8=256个量化值,因而每话路PCM编码后的标准数 码率 是64kb/s。为解决均匀量化时小信号量化误差大, 音质 差的问题,在实际中采用不均匀选取量化间隔的非线性量化方法,即量化特性在小信号时分层密,量化间隔小,而在大信号时分层疏,量化间隔大。
在实际中使用的是两种对数形式的压缩特性:A律和μ律,A律编码主要用于30/32路一次群系统,μ律编码主要用于24路一次群系统。A律PCM用于欧洲和中国,μ律PCM用于北美和日本。( 我国采用的是A率13折线编码。 )
PCM以采样技术为定理。采样定理:如果在规定的时间内,以有效信号最高频率的二倍或二倍以上的 速率 对该信号进行采样,则这些采样信息值中包含了全部原始信号信息。
整个录音进程的流程如下:
播音进程有两个主要任务:
1.播放音乐
2.播放人声(把服务器发来的语音信息接收,并用声卡将其播放出来)
首先创建播音对象,有三个参数分别为服务器地址、语音播放端口和进程管理器:
-
- pc=PlayClient(ipAddress,pcPort,manager)
与录音进程同理,由于要传输音频信号,所以也需要创建PCM编码器:
-
- self.al = alsaaudio.PCM(channels=1, rate=16000, format=alsaaudio.PCM_FORMAT_S16_LE, periodsize=2000,device='default')
播音进程的流程:
此外,在这个类当中,还提供了一些可供外部调用的接口函数,可供外部程序调用播放音乐。
YUV是三种分量合在一起,表示一个像素。RGB是三种分量合在一起,表示一个像素。
一、图像压缩编码的必要性
在网络中传输图像数据时,每秒钟传输的数据量为:
数据量=图像分辨率×像素字节数×字节比特数×帧数
在本设计中,图像分辨率为1280*720,即720P的图像分辨率,对于未经压缩的图像来说,其数据格式为RGB,像素字节数为3Bytes,字节比特数为8,帧数为30fps,将这些数据代入(4-1)计算,可以得出每秒钟传输的数据量约为663Mbps,无论从上行带宽还是下行带宽来看,均无法满足该图像传输所需的网络要求,因此,需要引入图像压缩编码技术,来减小每秒钟所传输的数据量。由此可见,图像数据的压缩编码对于网络中的视频传输至关重要。
二、H264编码
h264编码:入门理解H264编码-CSDN博客
常见的视频压缩编码格式有:H264、MPEG-2、MPEG-4和VC-1等,其均是通过各自不同的压缩编码技术,将原视频格式的文件转换成另一种视频格式文件,以提高视频质量或者减小文件数据容量便于传输。以上视频编码格式的性能如下表所示。
在本设计中,探测车远程驾驶主要依靠于稳定且高清的视频回传,对比上表可知,H264编码压缩效率高且画面质量清晰,虽然对硬件要求较高,但树莓派对视频采集在硬件上的支持较高,可以提供良好的硬件需求,因此选择H264编码作为本设计的图像压缩编码格式。
编码格式 | 压缩比 | 对硬件要求 | 画面质量 | 适用场景 |
H264 | 25%~40% | 高 | 清晰 | 高清视频会议 |
MPEG-2 | 100% | 低 | 一般 | 广播级的数字电视编码和传输 |
MPEG-4 | 50%~60% | 低 | 较清晰 | 长时间录像 |
VC-1 | 30%~40% | 高 | 较清晰 | 硬件要求没有H264高的场景 |
H264编码的基本思想是在一段视频中,环境(亮度、对比度、色温色调等)往往不会发生突变,即每一帧图像之间的数据是高度相关的,因此在一段时间内的视频图像并不需要对每一幅图像都进行完整的帧编码,而是选取某段时间的第一帧图像进行完整编码,其余图像则注重与第一幅图像的差别进行编码。
在H264编码中,有三种重要的帧类型:I帧、P帧、B帧[13],I帧是帧内编码帧,该帧为关键帧,是对一幅图像的完整编码,占用的数据量较大,是P帧与B帧的参考帧;P帧是前向预测编码帧,以其前面的一个I帧或P帧作为参考帧,采用运动补偿的方法极大地降低了相邻图像之间的冗余;B帧是双向预测内插编码帧,该帧以它前面的I帧或P帧和它后面的P帧作为参考帧,进行双向预测编码,压缩比最高,预测较为准确。
在图传进程中,将视频清晰度分为3个等级:QualityA、QualityB、QualityC,以用来适应不同的网络质量情况,在手机APP中分别对应高清、清晰、流畅三个选项可供选择。
传输视频信息需要进行h264编码,使用picamera库中的start_recording对象完成此功能:
-
- camera.start_recording(VHandler(),format='h264', quality=self.vQuality,
- bitrate=0, intra_period=5,inline_headers=False,
- intra_refresh='cyclicrows')
quality=self.vQualit 表示根据清晰度进行设置图像质量
bitrate=0 表示不受波特率控制
intra_period=5 表示每隔5个帧就插入一个I帧 I帧越少,所占的空间就越少
intra_refresh='cyclicrows' 表示设置关键帧格式为循环性
VHandler()是一个类,h264编码后的数据,将传入该类的write方法中,并发送出去。在TCP通信中,会产生半包和粘包的现象,采用在每一帧数据前添加帧头的办法来解决,关于半包和粘包:https://www.cnblogs.com/vipstone/p/14239160.html
-
- class VHandler():
- def __init__(self):
- # self.size = 0
- self.count=0
- self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.client.connect(("?.?.?.119",8098))
-
-
- def write(self, s): #s就是每一帧图像
- res = bytearray(self.HexToByte("FFD9FFD8FEFEFAFBFC")) #给每个数据加头,解决TCP传输半包和粘包的问题
- res.extend(s) #将这个数据接到每一帧图像数据上
- self.count = self.count + 1
- self.client.sendall(res) #将数据发送出去
- # self.size += len(s)
- #下面的3个方法没用到
- def flush(self):
- print('%d bytes would have been written' % self.size)
-
- def ByteToHex(self, bins):
- return ''.join(["%02X" % x for x in bins]).strip()
- def HexToByte(self, hexStr):
- return bytes.fromhex(hexStr)
图传进程的流程:
在总控制进程中,大体上包含3个任务:
1.舵机的控制
2.电机的控制
3.mqtt服务
下面分别介绍这三种控制方法。
一、舵机的控制
舵机在电力电子中十分常见,本设计使用舵机来控制探测车的转向。使用树莓派输出PWM波来控制舵机的转角,驱动舵机常用的PWM波频率为50Hz,即一个周期为20ms的方波。,理论上舵机运转的位置如下图和表所示。
舵机的转角范围是0°到180°,对应的脉冲宽度为1ms到2ms,即占空比范围是5%到10%,当占空比小于5%时,舵机转角为0°,当占空比大于10%时,舵机转角为180°。
舵机位置 | 脉冲宽度 | 占空比 |
起始位置(0度) | 1ms | 5% |
中间位置(90度) | 1.5ms | 7.5% |
末尾位置(180度) | 2ms | 10% |
然而在实际使用舵机时,实际的运转位置和理论值往往会有一些偏差,且需要考虑舵机安装的位置等因素,因此需要对舵机进行校准。
通过在树莓派程序中设置不同的占空比,最终测得舵机的实际运转位置与占空比的关系如下表所示。
舵机位置 | 占空比 |
起始位置(0度) | 2% |
中间位置(90度) | 6% |
末尾位置(180度) | 10% |
因此控制舵机的PWM波占空比应该在2%到10%之间,考虑到舵机安装在探测车上后的角度损耗,在经过实际测试后,最终确定实际使用的PWM波占空比范围为3%到9%之间。
在大多数情况下,我们以角度为参数来控制舵机,因此需要在舵机角度和占空比之间进行一个转换,上面提到0°对应占空比为2%,180°对应占空比为10%,且此变换是线性的,可以列出下式。
上式中Duty表示占空比,α表示角度。
经过整理后,可以得出角度与舵机占空比的对应关系式:
最终在树莓派中通过接受遥控指令,结合上式控制舵机转向角度即可。
关于硬件连接:舵机的电源线和地线连接稳压模块,信号线连接树莓派的TX引脚
二、电机的控制
在本设计中,电机作为探测车的动力输出,与舵机转向配合共同完成探测车的驾驶功能。电机的使用与舵机类似,在电机驱动电路中介绍过,共使用两路信号控制电机,一路信号输出PWM波控制电机转速,另一路信号通过高低电平改变电机转向。
在APP中有一个滑动条(seekbar),通过滑动seekbar来控制电机转速,seekbar的取值范围为0-100,恰好对应电机占空比,在树莓派中设置驱动电机的PWM波频率为10KHz,占空比与seekbar的关系为:
Duty=seekbar
电机的正反转根据接收到的摇杆信号控制即可。
三、MQTT服务
MQTT协议是当今世界上最受欢迎的物联网协议。它已广泛应用于车联网、智能家居、即时聊天应用和工业互联网等领域。目前通过MQTT协议连接的设备已经过亿,这些都得益于MQTT 协议为设备提供了稳定、可靠、易用的通信基础。
在本设计中,除了音视频信息,其他所有控制信号均通过MQTT(消息队列遥测传输协议)进行传输。MQTT协议是跨平台的,不同编程语言之间均可通过MQTT协议进行交互[16],它最大的优点在于用较少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。因此用MQTT协议来转发探测车和手机APP之间通信所需要的命令会非常可靠,比通过TCP或UDP协议来传输要稳定、快速。
在MQTT进程中,首先创建MQTT客户端对象,连接服务器的1883端口(1883端口为MQTT指定端口),当连接成功时,订阅”st0001”话题,订阅话题成功后,即可向该话题中发布消息或接收该话题中的消息,在本设计中,树莓派、服务器、手机APP三者之间的MQTT通信示意图如下图所示。
为了减小传输数据时占用的带宽,并简化服务器与客户端的代码开发难度,使用JSON格式对数据进行封装后再传输。JSON是一种通用的轻量级数据交换文本格式,在物联网传输过程中十分常见。
JSON中的数据是以键值对的方式来存放的,例如5种空气质量数据为:
CO2=221 CH2O=3 TVOC=12 PM2_5=11 PM10=13
在这5组数据中,CO2、CH2O、TVOC、PM2_5、PM10是“键”,221、3、12、11、13是“值”,即“键”为数据名,“值”为数据值,在JSON格式中,以{“键1”:值1,“键2”:值2,……,“键n”:值n}的方式呈现。上面5组数据经过JSON格式打包后,即为:
{“CO2”:221,”CH2O”:3,”TVOC”:12,”PM2_5”:11,”PM10”:13}
同理,在接收数据时,只需通过键名对数据进行解析即可。
为了使树莓派开机以后,自动运行编写好的程序,需要进行相关配置。
- cd /usr/lib/systemd/system #进入此文件夹
- nano car.service #创建一个文件
在car.service文件中写入:
- [Unit]
- Description=wsj
- [Service]
- Type=oneshot
- ExecStart=python3 /home/love/Feb/jeep264/jeep264/CarStart.py &
- [Install]
- WantedBy=multi-user.target
然后开启这个开机自启程序服务:
sudo systemctl enable car.service
至此已经完成开机自启。
结束的话使用该命令:
sudo systemctl disable car.service
可以使用下面的命令查看树莓派中在运行的程序:
sudo systemctl status
可以使用下面的命令来停止运行该程序:
sudo systemctl stop car.service
经过尝试上述方案并不可行!经过反复尝试其他方法后,找到一个可行的方法:
编辑/etc/rc.local文件:
sudo nano /etc/rc.local
在 fi 和 exit 0 之间加入要执行的命令:
python3 /home/love/Feb/jeep264/jeep264/CarStart.py &,然后重启,程序开机执行。注意!如果程序里有死循环,一定要在最后加上 & 表示在后台运行。如果程序未能正常运行,可以使用sudo systemctl status rc-local查看程序报出的错误,这是很重要的。
经过上述操作后,开机重启,还是不行!!!
在不指定用户的情况下,默认使用root用户执行,这时是不能使用一些安装在love用户下的python库的。所以在启动程序时,需要指定用户启动。
su - love -c 'python3 /home/love/Feb/jeep264/jeep264/CarStart.py &'
当移动终端(小车)连接到网络以后,并不是公网的IP,外网若要访问小车,有两种方式:一种是使用内网穿透的技术来访问,另一种是通过服务器来中转。
我们这个系统采用第二种方法,即通过服务器的方式,整个系统的网络结构如下图所示。
在服务器中有两个服务,一个是tomcat的服务,另一个是rabbitmq的服务。在tomcat服务中,我们在IDEA中编写java程序,开通3个端口:语音录制端口、语音播放端口、视频传输端口,实来现信息的转发。而关于小车的控制部分,如控制舵机转向、小车的前进和后退下发的一些指令,都是通过mqtt rabbitmq来进行转发的。
mqtt协议是跨平台的,不同语种之间均可通过mqtt协议进行交互。用mqtt协议来转发小车和APP之间通信所需要的命令,会非常可靠,比用TCP来传输稳定的多。该部分只需要在rabbitmq中配置即可,不需要编写额外的代码。
参考文章:windows环境下安装RabbitMQ(超详细)_windows安装rabbitmq-CSDN博客
这篇文章更好:【精选】【Windows安装RabbitMQ详细教程】_rabbitmq windows_慕之寒的博客-CSDN博客
特别需要注意的是,在安装Erlang时,一定要以管理员的身份去安装,否则验证Erlang是否安装成功时,总会显示安装未成功。
访问RabbitMQ的网址: http://localhost:15672
已创建网络类型为专有网络的安全组,并且在安全组的入方向添加规则并放行80、5672及15672端口,如果使用SSH远程连接Linux实例,还需要放行22端口。 具体操作,请参见云服务器ECS如何添加安全组规则_云服务器 ECS-阿里云帮助中心。
其余操作与在本机操作一致,需要在服务器上开放80、5672、15672端口。
同步调用:A服务调用B服务,需要等待B服务执行完毕的返回值,A服务才可以继续往下执行。
异步调用:A服务调用B服务,无需等待B服务的执行结果,也就是说在B服务执行的同时A服务可以继续往下执行。
若要实现异步调用,需要通过消息队列的方法来实现。
MQ全称为Message Queue,消息队列(MQ)是一种应用程序对应用程序的通信方法,应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。
常用的消息队列产品有RabbitMQ、ActiveMQ、RocketMQ、Kafka,RabbitMQ稳定可靠,数据一致,支持多协议,有消息确认,基于Erlang语言。
在RabbitMQ中分为生产者和消费者,即发送端和接收端。
发送端操作流程:1.创建连接2.创建通道3.声明队列4.发送消息
接收端操作流程:1.创建连接2.创建通道3.声明队列4.监听队列5.接收消息
在安装 RabbitMQ 后,会自动创建一个默认的管理员用户,用户名为 guest
,密码也为 guest
。不过需要注意的是,默认情况下,guest 用户只能在本地访问 RabbitMQ,无法在远程计算机上进行访问。如果您需要在远程计算机上访问 RabbitMQ,需要创建一个新的管理员用户,并为其设置密码。具体操作方法参考这三篇文章,其实第一篇就够了(我当时只参考了第一篇):
windows10环境下的RabbitMQ安装步骤及创建用户,密码,绑定角色-CSDN博客
RabbitMQ 新增一个用户名(登录名)和密码_rabbitmqctl add_user-CSDN博客
在服务器上的RabbitMQ新增完用户后,将相应的信息写进程序:
另外还有一些注意事项:
服务器程序一直出错的原因是:框里面的第一个反斜杠没有打开!!!
树莓派连接mqtt时一直返回5
上网查后,发现是未授权
当时一直在想是不是rabbitmq没有配置好,但是其实之前在控制台里面用户权限什么的都配置好了,上网查了很多资料都无法解决。
参照上面文章中的第二点后,修改完的文件如图:
python程序连接mqtt的时候没有输入mqtt的用户名和密码,所以会默认访问guest用户,而guest用户又恰好是默认不支持其他IP的用户远程访问的,所以要授权guest用户可以远程访问。(注意:执行完上面的操作后,一定要重启RabbitMQ才会生效!!!)终于,成功了!
安装教程:
1.Tomcat需要java环境,因此需要首先安装jdk:
最详细jdk安装以及配置环境(保姆级教程)-CSDN博客
2.安装Tomcat:
全网最详细的Tomcat安装和配置教程,图文详解_tomcat安装步骤-CSDN博客
3. 访问tomcat的网址:http://127.0.0.1:8080/
4.启动Tomcat时可能会出现乱码的情况:
Tomcat启动时出现乱码的解决方式_tomcat乱码-CSDN博客
5.需要在Rabbitmq中新建一个用户,为其打开权限等操作,并确保输入用户名和密码后,该新用户可以登录到Rabbitmq界面上
6.如何知道Tomcat是否启动成功
在Rabbitmq界面可以看到队列、交换机等信息
并且已经有连接:
补充:
web应用放到tomcat里面才能跑得起来,而tomcat里面对项目有规定特定的格式,就是war包的格式,这就是为什么我们需要把项目打成war才能丢进去。
简单总结下,tomcat是一个中间件,在B/S架构中,浏览器发出的http请求经过tomcat中间件,转发到最终的目的服务器上,响应消息再通过tomcat返回给浏览器。
tomcat所做的事情主要有:开启监听端口监听用户的请求,解析用户发来的http请求然后访问到你指定的应用系统,然后你返回的页面经过tomcat返回给用户。
进入到tomcat中时,必须首先打开tomcat文件夹的bin文件下的startup.bat,然后在网址中输入http://127.0.0.1:8080/才能顺利运行。
java用到的三方库,通过maven来进行管理。
在运行程序时,会报错:Web server failed to start. Port 8080 was already in use.这说明8080端口已被占用。
可以通过该方法解决:WIN10 关闭占用某一端口号的进程_关闭某个端口的进程-CSDN博客
运行成功如图所示:
在小车端,通过socket套接字来发送数据,在服务器端通过netty,现在的互联网环境下,分布式系统大行其道,而分布式系统的根基在于网络编程,而 Netty 恰恰是 Java 领域网络编程的王者。如果要致力于开发高性能的服务器程序、高性能的客户端程序,必须掌握 Netty,而本课程的目的就是带领你进入基于 Netty 的网络编程世界。
netty的基础是NIO,NIO(non-blocking IO)非阻塞IO
三大组件 Channel和Buffer
channel有一点类似于stream,它是读写数据的双向通道,可以从Channel将数据读入Buffer,也可以将Buffer的数据写入channel;而stream要么是输入,要么是输出,channel比stream更为底层。
常见的Channel有:
FileChannel (做文件的数据传输通道)
DatagramChannel (UDP网络编程时的数据传输通道)
SocketChannel (TCP网络编程时的数据传输通道,客户端和服务器端均能用)
ServerSocketChannel (TCP网络编程时的数据传输通道,专用于服务器端)
buffer用来缓冲读写数据,常见的buffer为:
ByteBuffer
JSON
JSON是一种通用的轻量级数据交换文本格式,在物联网数据传输过程中,JSON是一种十分常用的格式,它很容易让人阅读和编写。
JSON中的数据是以“键值对”的方式来存放的,比如有三组数据:
temperature=25;humidity=50;height=100;
在上面三组数据中,temperature、humidity、height是“键”,25、50、100是“值”,其实“键”就是数据名,“值”就是数据值,在JSON格式中,以{“数据名”:数据值} 的方式呈现。
上面三组数据经过JSON格式打包后,即为:{"temperature":25,"humidity":50,"height":100}
语音即使出现半包或者粘包的现象也无妨,所以在程序上也图传不太相同,没有其他多余的步骤。
DelimiterBasedFrameDecoder是一个分隔符解码器
APP的布局 在activity.xml中编写,布局是有层级的,将视频显示放在最底层
在Android Studio中要填写好正确的公网IP和相应的端口:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。