无人机跟踪器——视频处理模块介绍(部署OpenIPC地面站)
视频采集系统介绍
系统背景
既然是基于机器视觉的跟踪器,那视频采集就是第一个需要考虑的问题。首先明确需求:RK3588能够采集视频数据,用户也可以在地面端获取视频反馈。
如果是地面上的设备,RK3588可以直接用板载摄像头或者USB摄像头获取图像,用户想要看到摄像头的数据直接用RK3588的HDMI口接显示器即可。但是项目要做的是无人机机载跟踪器,就需要用到数字图传设备进行远距离视频传输,这时候我们就要考虑机载电脑, 图传模块和地面站之间如何优雅交互了,我主要考虑了两种方案:
- 使用专业的图传设备进行视频推流,机载电脑和地面站都负责视频拉流,实现一发多收。
- 使用RK3588充当图传设备,RK3588通过板载摄像头进行视频采集,做完处理后进行视频编码和推流。
方案1的好处就是专业的事交给专业的设备,采用专业的图传设备可以提高系统的稳定性,并且减少工作量。缺点是机载电脑和图传间多了一个视频编解码过程,RK3588的视频采集延迟会拉大。
方案2省去了图传设备,集成度更高,但是实现难度更大。RK3588为了实现高频率稳定跟踪会消耗大量CPU资源,视频推流会进一步挤占CPU资源,容易发生跟踪频率变低,且视频推理帧率也不稳定的问题。
目前项目从稳定性角度考虑,采用的是方案1,图传设备使用的是RunCam家的WifiLink1代,可以提供720p120fps的视频推流,其采用OpenIPC开源方案,所以可以通过在RK3588中部署OpenIPC地面站将图像数据传递给用户程序使用。

系统内容
上述内容介绍了视频处理模块的硬件组成,接下来就要开始构建软件部分。软件主要包括无线网卡驱动的移植,OpenIPC地面站部署和视频处理模块的构建。
- 无线网卡驱动(RTL8812AU):为了能够接收到图传的码流数据,需要在RK3588中移植RTL8812AU驱动,实现UDP码流的采集
- OpenIPC地面站部署:采用wfb-ng框架, 负责对网卡驱动进一步设置(gs.key, wifi_channel, wifi_region和peer)
- 视频处理模块:有了UDP码流就要对其进行解码和管理,本项目基于Gstreamer开发了一套应用层的视频处理模块。
- 支持V4L2(摄像头)和UDP(图传)视频流输入,支持硬件编解码(MPP)和色域转换(RGA)
- 支持对用户程序的输入图像序列进行MP4存储
- 提供可拓展接口,用户可以自定义注册信号(视频通道关闭信号等),自定义视频缓存区模式和自定义缓冲区读取方式
关于为什么要用Gstreamer进行模块开发,是因为Gstreamer是一个模块化管理的框架,用户可以像搭积木式的将自己需要的功能构建成管道(pipeline),且提供了一套信号机制(基于GLib),用户可以自定义信号处理函数。
视频处理模块实现
这里重点介绍一下视频处理模块的实现,由于编写这个模块的时候深受linux内核源码影响,所以把linux的一些设计思想也融入进去。该模块由C语言实现,采用宏定义的方式配置模块功能。总体流程图如下图所示。
模块关键结构体
注意: 旧版代码拓展性差,配置麻烦,已经被新版替换,请查看视频处理模块优化文章。
0. 配置参数
该模块的配置采用宏定义,用户需要修改宏定义参数并重新编译该模块。
1 |
|
1.GstApiData
这个结构体的作用相当于Linux驱动中的cdev, 其中的gst_file_operations相当于cdev的file_operations,其中包含read和write函数需要用户实现或使用系统默认。该结构体的功能包括
- 负责管理所需Gstreamer组件的初始化以及管道的搭建和运行
- 用户程序通过该结构体访问视频数据
1 | typedef struct { |
2. FrameQueue和Frame
Frame是实际存储图像的结构体,FrameQueue作为缓冲区将采集的图像存储起来。FrameQueue通过头尾指针记录缓冲区位置,从头指针取出数据,从尾指针添加数据,使用_Atomic(Frame*)
定义原子变量,通过头尾指针的自旋实现无锁队列。可以注意到,Frame的buffer属性是void**,而不是void *, 会在下面的内存池中进行解释。
1 | typedef struct Frame{ |
3. MemoryPool和mem
考虑到每次采集数据都需要调用malloc为图像分配内存,由于图像数据较大,malloc会通过mmap的方式申请内存,这会导致每次free的时候就释放掉该块内存而不是暂时存放在堆中。而模块每秒采集120帧的图像,意味着每秒就有316.4MB的内存频繁申请和释放,其中由于缺页异常会频繁触发系统调用,占用CPU资源。所以我设计了一个简易的内存池专门负责管理该模块的内存分配。
mem作为MemoryPool(内存池)的基本结构,其成员buffer指向实际分配的内存。现在就有两个问题需要解决:
- 如何将mem中的buffer传递给Frame,让用户程序可以通过Frame获取mem的buffer进行数据读写。
- 如何避免mem暴露给用户程序,也就是Frame中不应该包含mem结构体成员,因为mem是该模块独有的。
这里我就想到了Linux内核中常用的list_head和container_of, 所以我将Frame的buffer指向mem->buffer的成员地址,用户依旧可以用Frame->buffer进行读写。当用户释放Frame的时候,模块内的释放函数通过container_of和Frame->buffer找到mem结构体地址从而完成内存池的释放。是不是很优雅。
1 | typedef struct mem{ |
OpenIPC地面站部署
这里记录一下如何在RK3588部署OpenIPC地面站,采用的是WFB-NG方案。
RTL8812AU驱动安装
WFB-NG支持RTL8812AU和RTL8812EU两个驱动,由于图传采用的是RTL8812AU网卡,所以我们这里在RK3588上安装RTL8812AU,驱动源码可以在这里下载,官网提供了通过DKMS。经过尝试,这种方法在PC端是可以使用的,因为操作系统提供了完整的驱动编译环境。但是在RK3588上,由于linux操作系统是瑞芯微定制的特定版本,无法直接在板子里进行驱动编译和安装,哪怕我使用了版本对应的linux-headers, 依旧无法顺利编译成功,所以我选择了用瑞芯微提供的SDK编译Linux镜像通过进行驱动安装。
1. 下载驱动源码安装包
1 | git clone https://github.com/svpcom/rtl8812au.git |
官方提供了5.2.20和5.6.4.2版本,官方推荐5.2.20版本(说是性能更好),但是存在兼容性问题,对于高版本的Linux内核用5.6.4.2版本可以避免这个问题。
2. 放入Linux内核
①将驱动放入Linux内核驱动模块,这里我放到./kernel/drivers/net/wireless/rockchip_wlan
目录下
②修改当前目录下的Makefile, 添加 obj-$(CONFIG_88XXAU) += rtl8812au/
③修改rtl8812au/Kconfig文件内容(对于5.10.110版本的linux需要修改, 主要是格式问题)
1 | config 88XXAU |
3. 配置Linux内核和编译
做完上述步骤后就可以通过对Linux进行配置和编译,这里我们通过Linux提供的可视化配置界面进行设置
①导入指定配置文件 make ARCH=arm64 defconfig
,其中defconfig根据用户自己的配置进行设置。
②打开可视化配置界面make ARCH=arm64 menuconfig
进入 –>Device Drivers–>Network device support
选中 Universal TUN/TAP device driver support (WFB-NG需要)
进入–>Wireless LAN–>Rockchip Wireless LAN support
选中 Realtek 88XXau USB WiFi
③ 保存配置文件make savedefconfig
,生成的defconfig覆盖原来的defconfig
④ 使用瑞芯微SDK的脚本对整个项目进行编译
到这,我们就可以获得一个包含RTL8812AU驱动的Linux镜像文件,烧录即可。
WFB-NG框架安装
有了RTL8812AU驱动,当我们插入RTL8812AU网卡时,通过ifconfig可以看到网卡已经存在,然后就可以安装WFB-NG框架。
1. 下载WFB-NG源码
1 | git clone https://github.com/svpcom/wfb-ng.git |
2. 安装
① cd wfb-ng
② sudo ./scripts/install_gs.sh wlan1
, wlan1替换成RTL8812AU网卡的名字
③ sudo systemctl restart wifibroadcast@gs
, 即可启动OpenIPC地面站服务
想要和天空端互联,还需要替换gs.key和修改wifibroad.conf文件。然后通过wfb-cli gs
即可查看是否有数据交互。