使用Minifly打造基于视觉感知的跟踪无人机
前言:本文介绍一种可行的解决方案来实现基于视觉感知的跟踪无人机。由于本人能力和资源有限,所以在无人机系统的选择上,选用正点原子开发的开源算法无人机Minifly四轴和摄像头。视觉感知模块(目标检测与跟踪)采用OpenCV + MobileNet SSD + KCF。本文已分享经验和记录开发过程为主,推荐使用其他更好的无人机模块和图像识别算法。
知识基础:Linux、Python 3、STM32(嵌入式相关)
解释一下为什么要用Linux,其实只用Windows也可以,但实际运行中发现OpenCV的效率在Linux上更高。该方案建议安装Windows 7(驱动、软件支持较好) + Linux 双系统。
Python在人工智能中的影响不用多说,简单了解一下这种语言即可。
STM32F103 和 STM32F411(Cortex-M)是本方案种的无人机系统的处理器核心,并且采用FreeRTOS作为操作系统,所以关于STM32的C语言库函数开发是必要的,尽管本方案涉及到它的部分不多。
整体框架如下:

正文:
一、开发软件及平台
Deepin Linux --- deepin操作系统是中国人开发的Linux发行版。主要优点:安装简单、界面美观、集成wine QQ 微信,真正做到开箱即用。
PyCharm --- PyCharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如Python解释器选择、Pip包管理器、调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制等,选用免费的社区版即可。
Keil MDK5 --- MDK-ARM软件为基于Cortex-M、Cortex-R4、ARM7、ARM9处理器设备提供了一个完整的开发环境。
二、四轴飞行器原理
四轴飞行器主要是由电机、电调、电池、浆叶、机架、遥控器、飞控组成。飞行器基本原理是通过飞控控制四个电机旋转带动浆叶产生升力,分别控制每一个电机和浆叶产生不同的升力从而控制飞行器的姿态和位置。四轴在空中可以实现八种运动,分别为垂直上升、垂直下降、向前运动、向后运动、向左运动、向后运动、顺时针改变航向、逆时针改变航向。飞行器在空中任何一种姿态都可以通过姿态角旋转后得到。

2.1姿态角的旋转关系图
俯仰角(pitch):机体坐标系 X 轴与水平面的夹角,围绕X轴旋转。当X轴的正半轴位于过坐标原点的水平面之上(抬头)时,俯仰角为正,否则为负。
偏航角(yaw):机体坐标系 X 轴在水平面上投影与地面坐标系 X轴之间的夹角,围绕Y轴旋转。机头右偏航为正,反之为负。
滚转角(roll):机体坐标系 Z 轴与通过机体 Z 轴的铅垂面间的夹角,围绕 Z 轴旋转。机体向右滚为正,反之为负。
简单来说,通过Pitch可以控制机体向前后飞行,Roll可以控制机体左右飞行、Yaw可以控制机头偏转。下文会针对遥控器模块做很多深入的分析,遥控器对四轴的 “控制数据”包含了这三个重要的值。
至于四轴如何通过各种传感器、数学模型和公式、PID自动控制原理来做到真正的飞行控制已不在本文的内容范围。如果想获得更好的飞行控制效果,关于PID控制原理倒是可以细究一下,PID控制原理提出的历史也比较长,在自动控制的应用中也非常广泛。
在本方案中由于四轴的空间自由度太高导致调试的不便,本方案采用定高、定点飞行。(需要购买光流定点模块)需要注意的是,Minifly并不能支持两个以上的模块,下文会涉及到对摄像头和四轴的简单改造。
三、Minifly程序分析
3.1 代码框架
资料下载:http://www.openedv.com/thread-105197-1-1.html
3.1.1 Minifly遥控器代码框架(FirmwareF103):

图3.1.1 Minifly遥控器代码框架
通过Minifly遥控器发给四轴的控制信息有两条链路:
1. 摇杆状态 -->模数转换 --> 控制数据生成 -->ATKP包 -->无线电模块 --> 四轴
2. 上位机数据 --> USB转串口 -->ATKP包 -->无线电模块 -->四轴
为实现无人机的自动控制,必须采用第二条链路来进行数据的传递控制数据,要搞清楚什么数据能被无人机接收并解析,也就是ATKP包的具体内容。在下文中将结合具体程序解答。
3.1.2Minifly四轴代码框架(FirmwareF411):

图3.1.2 Minifly四轴代码框架
本方案采用遥控器作为中转站控制四轴飞行,也就是图3.1.2中的绿框部分。
3.2 通信协议
通信协议相关的源码以FirmwareF103工程代码为例:
ATKP通信协议部分主要在 atkp.h 中,ATKP 数据包格式及 msgID 功能字定义代码如下:
/*上行帧头*/
#define UP_BYTE1 0xAA
#define UP_BYTE2 0xAA
/*下行帧头*/
#define DOWN_BYTE1 0xAA
#define DOWN_BYTE2 0xAF
#define ATKP_MAX_DATA_SIZE 30
/*ATKP 通讯数据结构*/
typedef struct {
u8 msgID;
u8 dataLen;
u8 data[ATKP_MAX_DATA_SIZE];
}atkp_t;
四轴通信协议中下行指令有两种控制信息DOWN_REMOTOR 指令 ID 是用来指定是遥控器下行给四轴的命令。然后使用 Data[0]分区分发送控制命令和控制数据发送。控制命令和控制数据枚举如下
/*遥控数据类别*/
typedef enum
{
REMOTOR_CMD,
REMOTOR_DATA,
}remoterType_e;
控制命令主要是控制四轴实现一些功能性操作的命令,比如一键起飞降落、一键翻滚、一键紧急停止等。控制数据主要是发送给四轴姿态控制数据。当 Data[0] == REMOTOR_CMD时,Data[1]为控制命令;当 Data[0]== REMOTOR_DATA 时,Data[1]之后为控制数据。控制数据结构如下:
/*遥控控制数据结构*/
typedef __packed struct
{
float roll;
float pitch;
float yaw;
float thrust;
float trimPitch;
float trimRoll;
bool ctrlMode;
bool flightMode;
bool RCLock; } remoterData_t;
/*关于飞行与控制模式枚举*/
enum ctrlMode
{
ALTHOLD_MODE,
MANUAL_MODE,
};
enum flightMode
{
HEAD_LESS,
X_MODE,
};
发送控制数据时,数据格式如下:

当需要控制数据时,先使用 remoterData_t 定义一个 send 结构体数据,然 后调用 sendRmotorData((u8*)&send, sizeof(send)) 即可发送控制数据了。代码示意如下:
remoterData_t send;
send.roll = 0.0; …………/*给 send 结构体赋值*/
sendRmotorData((u8*)&send, sizeof(send)); /*发送遥控控制数据*/
void sendRmotorData(u8 *data, u8 len)
{
if(radioinkConnectStatus() == false)
return;
atkp_t p;
p.msgID = DOWN_REMOTOR;
p.dataLen = len + ;
p.data[] = REMOTOR_DATA;
memcpy(p.data+, data, len);
radiolinkSendPacket(&p);
}
通过以上代码和表格我们就能知道发送ATKP包的具体内容,现在看起来可能一头雾水,举两个例子简单解释一下:
1.控制命令:一键起飞降落命令完整格式:
AA AF 50 02 00 03 AE
分析:
0xAA 0xAF (下行帧头)
0x50(msgID:DOWN_REMOTOR 下行指令))
0x02(LEN + 1))
0x00(DATA[0] = 0x00 控制命令)
0x03(CMD_FLIGHT_LAND 一键起飞/降落 参看头文件remoter_ctrl.h中的宏定义)
0xAE(CHECK SUM 校验和 从帧头到数据最后一位逐字节相加)
2.控制数据:让四轴在手动模式下已50%油门和Roll角为5的姿态下飞行
AAAF501D010000a04000000000000000000000484200000000000000000000000031
尽管看起来很长,逐步分析一下:
0xAA 0xAF 0x50(下行帧头 、 下行指令msgID)
0x1D (数据长度29 -1 =28 也就是结构体remoterData_t的长度,注意字节对齐)
0x01 (data[0] = 0x01控制数据)
0x0000A040(send.roll = 5.0f IEEE754标准32位浮点数 小端字节序)
0x00000000(send.pitch = 0.0f)
0x00000000(send.yaw = 0.0f)
0x00004842(send. thrust = 50.0f 50%油门)
0x00000000(send. trimPitch = 0.0f trim是修正系统误差,默认0)
0x00000000(send. trimRoll = 0.0f)
0x00 (u8-CtrlMode 0x00-手动模式 0x01-定高定点模式)
0x00 (bool-FlyMode true-X模式 false-无头模式)
0x00 (bool-RCLok 解锁相关,用不上)
0x00 (1byte-字节对齐)
0x31 (前面所有字节的校验和)
关于大小端:数据存储的大端字节序还是小端字节序取决于CPU,STM32 采用小端字节序。
关于四轴各项控制参数的范围请参看源码FirmwareF103 – COMMUNICATE –remoter_ctrl.c。
3.3 二次编译
下载最新的源码后(V1.3),需要微调代码,重新编译并升级固件。
3.3.1.遥控器
如图3.3.1 MDK打开工程FirmwareF103找到相关代码并注释掉箭头位置,使得上位机数据能通过USB串口被遥控器正常接收并发放给四轴飞行器。

图3.3.1.1
保存代码,如图3.3.1.2在编译器配置中勾选生成BIN文件,再进行编译,最后编译日志一定要提示生成新的BIN文件。下载BIN固件请参看固件升级手册。

图3.3.1.2

图3.3.1.3
3.3.2 无人机
如图3.3.2.1:MDK打开工程FirmwareF411,四轴飞行高度调整(建议高度为1.4m 即140.f),修改后同上配置后编译下载

图3.3.2.1
注意:四轴的固件下载可能存在失败的情况,需要多次下载
四、驱动模块的开发
4.1 Wi-Fi摄像头
首先说明Minifly官方提供的微型WiFi摄像头并不好用,它使用私有的通信协议,视频流编码格式为H.264,只能按照提供的客户端软件来进行访问,能达到20FPS。通过分析其Web客户端技术,发现其以CGI协议为访问接口,但是并不包含视频流的CGI指令,只有snapshot的指令,也就是发送截屏的指令,返回一个JPG格式图片,最高只能达到8FPS。
PyCharm安装opencv-python、imutils包,连接Minifly,通过Python我们可以实现传图:
import cv2
import imutils # CGI IPcamare
url = 'http://192.168.1.1:80/snapshot.cgi?user=admin&pwd='
# im.src = "videostream.cgi?stream="+Status.sever_push_stream_number+"&id="+d.id;
# url = 'http://192.169.1.1:80/
# url = 'http://192.168.1.1:80/videostream.cgi?user=&pwd=&resolution=32&rate=0'
# url = 'http://192.168.1.1:80/livestream.cgi?user=admin&pwd='
cnt = 0
while True:
timer = cv2.getTickCount()
cap = cv2.VideoCapture(url)
fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer)
if cap.isOpened():
cnt += 1
width, height = cap.get(3), cap.get(4)
print(cnt, '[', width, height, ']')
ret, frame = cap.read()
frame = imutils.resize(frame, width=640)
# frame = cv2.flip(frame, -180)
cv2.putText(frame, "FPS : " + str(int(fps)), (100, 50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50, 170, 50), 2)
cv2.imshow('frame', frame)
else:
print("Error")
break
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
4.2 串口数据
第三章中已经分析了ATKP包的控制指令及控制数据格式,现在我们需要用Python构造能够生成这些格式的数据,并且通过串口发送到Minifly遥控器。需要安装pyserial包。
同样先以简单的控制指令为例如一键起飞/降落:
# coding=utf-8
import serial
import time cmd_onekey_fly = 'AAAF50020003AE' # 一键起飞/降落
ser = serial.Serial('COM7', 500000, timeout=0.5) # 打开串口资源(Windows) 通过设备管理查看是COM几,确保驱动安装正确
u_byte = bytes.fromhex(cmd_onekey_fly) # 字符串形式转为十六进制字节形式 ser.write(u_byte) # 发送到串口(遥控器)实现一键起飞
time.sleep(3)
ser.write(u_byte) # 发送到串口 实现一键降落 ser.close() # 关闭资源
如果要发送控制数据,应先构造一个生成数据字符串的过程,以逐步拼接的方式完成:
固定的帧头、LEN、data[0] + 浮点数小端字节序 + 控制模式 + 对齐字节 + 校验和
给出代码:
# coding=utf-8
import serial
import struct
import time def float_to_hex(data): # float --> Hex 小端字节序
return (struct.pack('<f', data)).hex() cmd_onekey_fly = 'AAAF50020003AE' # 一键起飞/降落
cmd_stop = 'AAAF50020004AF' # 紧急停机 cmd_Head = 'AAAF501D01' # 控制信息头 AA AF[HEAD] 50[REMOTER] 1D 01(data[1-29])
Trim = '' # Trim信息(不校准) Mode = '' # 飞行控制模式 u8-CtrlMode bool-FlyMode bool-RCLok 1byte-字节对齐
send_str = ''
flydata = [5, 0, 0, 50] # 飞行数据【rol-滚转角, pit-俯仰角, yaw-偏航角, thr-油门】 send_str = cmd_Head + float_to_hex(flydata[0]) + float_to_hex(flydata[1]) + float_to_hex(flydata[2]) + \
float_to_hex(flydata[3]) + Trim + Mode u_byte = bytes.fromhex(send_str) checksum = 0
cnt = 0 for a_byte in u_byte:
checksum += a_byte
cnt = cnt + 1 H = hex(checksum % 256)
print(H, cnt)
print(send_str)
send_str = send_str + H[-2] + H[-1]
if H[-2] == 'x': # 0xF -> 0x0F
send_str = send_str + '' + H[-1] print(send_str)
运行结果:
0x31 33
AAAF501D010000a040000000000000000000004842000000000000000000000000
AAAF501D010000a04000000000000000000000484200000000000000000000000031
注意:
1. 上述控制指令和控制数据可在串口调试工具或代码中实现发送和飞行调试,波特率设置为500 000,注意安装驱动,Linux上为免驱的USB虚拟的串行口,设备路径/dev/ttyACM0。
2. 一键起飞/降落指令发送一次立即起飞,再发送一次进入着陆状态。
3. 发送控制数据时必须持续不断以最快速度重复发送,两次发送的时间间隔为1ms最佳,大概发送300次为1秒。
4. 关于控制模式,Mode = '00000000' 时为手动模式,不用让无人机起飞,一般在测试串口通信是否联通,也可通过改变rol-滚转角, pit-俯仰角, yaw-偏航角来看数据控制的效果。
5. 定高定点模式下Mode = '01000000',先让无人机起飞,油门必须保持为50%(thrust = 50意为油门摇杆位置没有改变,四轴的程序会自动调整高度)。
五、目标检测和跟踪
基于计算机视觉的应用比较成熟,并不是本文要讨论的重点。这里简单介绍一种检测与跟踪的方法,模型采用MobileNets SSD 和核相关滤波算法(KCF)的目标检测与跟踪实现。

图5.1 MobileNets:高效(深度)神经网路
(1)目标跟踪开始时,将被跟踪目标所在区域的图像块送入 SSD 算法所建立的各个离线模型中,检测出目标类型。SSD算法进行目标检测时,首先产生多个不同尺度、不同长宽比的目标框假设。然后,再将多个不同的卷积滤波器应用于各个卷积层上,从而得出各个目标框假设的分值和位置偏移,终确定一系列候选目标框。然后再通过非极大值抑制策略来确定终的检测结果。
(2)跟踪开始后,获得每一帧时,都使用 SSD算法检测出目标所在的位置。同时将该帧图片信息存储在新的训练集中。
(3)通过 SSD 算法,使用新的训练集对已有的模型进行继续训练,得到新的目标外观模型。这样一来,原有的模型得到了更新,而更新时所用的训练样本来自于在线获得的目标信息,从而使得更新后的模型中具有了专属于被跟踪目标的一些外观信息。因此,用更新后的模型进行后续帧中的目标检测时,精度能够得到进一步提高。另外,由于在线获得的图像样本数量较少,所以在线训练的计算量不大,不会对算法的速度产生明显影响。
(4)当新的训练集中图像数量达到预先设定的阈值时,说明对于原有模型的更新达到了一定的程度。此时用新的模型替代原有的模型,用于在后续帧中进行目标检测。同时清空新的训练集。
(5)重复前面的步骤(2)至步骤(4),直到跟踪过程结束为止。
上述算法流程可用下图归纳表示:

图5.2 目标检测与跟踪算法
六、联合调试
6.1 模块整合
除了一套Minifly四轴,还需另外要准备的两个模块如同所示:

图6.1.1 四轴、WiFi图传模块、光流模块
上文已提到MiniFly并不能同时扩展多个模块,必须做如下改动:
WiFi摄像头模块最外面一层PCB板(用于固定到四轴模块的母排)用电烙铁拆卸,用导线引出VCC、GND供电,连接一个微型拨动开关,再把导线焊接在四轴PCB的电池连接处,WiFi摄像头模块位置如图,摄像头用泡沫胶固定,再固定光流模块,将摄像头模块压住。
注意:VCC 、GND一定不要接反,摄像头排线容易松动,固定时小心。

图6.1.2 四轴及模块的改动
此时四轴飞行时的耗电严重,需另购较大的电池,推荐 702035 规格 400mAH 容量。
四轴与遥控器, 摄像头与上位机 两者之间的通信经常互相干扰,官方给出的说法是信道干扰,重置四轴和遥控器重新匹配直到不再干扰(四轴起飞后也能传图)。
6.2 代码改进
多线程的图像传输、图像目标检测与跟踪、四轴控制数据的生成及发送,程序框架:

图6.2.1程序总体框架
具体程序不再粘贴,Python项目网盘链接:
https://pan.baidu.com/s/1ZLsgkLoJUJLBRifi4GO73Q 提取码:pao0

图6.2.2 项目结构

图6.2.3 代码功能
七、总结
本方案的优势是减去了自己设计无人机系统的工作,并且可以通过Python来将所有模块结合起来。
由于四轴自身大小和升力的限制,摄像头的选用变得有限,在传图速度和稳定性上并不满意,时常丢失跟踪目标。虽然无人机采用了高精的传感器,但实际运行过程中因为环境的影响,飞行状态的效果有时并不太理想,导致对跟踪算法反馈的信息得不到及时调整,软硬件还需进一步优化。
使用Minifly打造基于视觉感知的跟踪无人机的更多相关文章
- DIY一个基于树莓派和Python的无人机视觉跟踪系统
DIY一个基于树莓派和Python的无人机视觉跟踪系统 无人机通过图传将航拍到的图像存储并实时传送回地面站差点儿已经是标配.假设想来点高级的--在无人机上直接处理拍摄的图像并实现自己主动控制要怎么实现 ...
- 基于meanshift的手势跟踪与电脑鼠标控制(手势交互系统)
基于meanshift的手势跟踪与电脑鼠标控制(手势交互系统) zouxy09@qq.com http://blog.csdn.net/zouxy09 一年多前开始接触计算机视觉这个领域的时候,年幼无 ...
- Unity3D学习(五):实现一个简单的视觉感知
前言 在很多第一人称或者第三人称射击游戏的单人模式中,玩家的乐趣往往来源于和各式各样的AI敌人的战斗.而战斗的爆发很多时候是因为这些AI在"看见"玩家后就会立即做出反应,比如开火. ...
- 基于视觉的 SLAM/Visual Odometry (VO) 开源资料、博客和论文列表
基于视觉的 SLAM/Visual Odometry (VO) 开源资料.博客和论文列表 以下为机器翻译,具体参考原文: https://github.com/tzutalin/awesome-vis ...
- 【原创】打造基于Dapper的数据访问层
[原创]打造基于Dapper的数据访问层 前言 闲来无事,花几天功夫将之前项目里用到的一个数据访问层整理了出来.实现单个实体的增删改查,可执行存储过程,可输出返回参数,查询结果集可根据实际情况返回 ...
- 翻译:打造基于Sublime Text 3的全能python开发环境
原文地址:https://realpython.com/blog/python/setting-up-sublime-text-3-for-full-stack-python-development/ ...
- 浏览器禁用Cookie,基于Cookie的会话跟踪机制失效的解决的方法
当浏览器禁用Cookies时.基于Cookie的会话跟踪机制就会失效.解决的方法是利用URL重写机制跟踪用户会话. 在使用URL重写机制的时候须要注意.为了保证会话跟踪的正确性,全部的链接和重定向语句 ...
- 基于视觉的Web页面分页算法VIPS的实现源代码下载
基于视觉的Web页面分页算法VIPS的实现源代码下载 - tingya的专栏 - 博客频道 - CSDN.NET 基于视觉的Web页面分页算法VIPS的实现源代码下载 分类: 技术杂烩 2006-04 ...
- VIPS:基于视觉的页面分割算法[微软下一代搜索引擎核心分页算法]
VIPS:基于视觉的页面分割算法[微软下一代搜索引擎核心分页算法] - tingya的专栏 - 博客频道 - CSDN.NET VIPS:基于视觉的页面分割算法[微软下一代搜索引擎核心分页算法] 分类 ...
随机推荐
- Java8新特性——lambda表达式.(案例:公司业务)
需求:现有一个员工姓名list,其中包含单字母索引.要求输出一个字符串,去掉所有单字母,所有名字首字母大写并用逗号隔开. package cn._2.thecompanyprocess; import ...
- CentOS7使用firewalld防火墙
firewalld的基本使用 启动: systemctl start firewalld 关闭: systemctl stop firewalld 查看状态: systemctl status fir ...
- cat more less 命令混用
在Linux系统中有三种命令可以用来查阅全部的文件,分别是cat.more和less命令.它们查阅文件的使用方法也比较简单都是 命令 文件名 ,但是三者又有着区别. 1.cat命令可以一次显示整个文件 ...
- 常用的方法论-NPS
- MySQL login-path 本地快捷登陆
目录 1.什么是 login-path 2. 配置 login-path 2.2.配置: 2.3.显示配置: 2.3.1.显示执行的login-path配置 2.3.2.显示所有的login-path ...
- SQL Server 2016 + AlwaysOn 无域集群
目录 AlwaysOn 搭建 WSFC 配置计算机的 DNS 后缀 安装故障转移集群 验证集群 创建集群 创建文件共享见证 配置 AlwaysOn 新建可用性组 创建侦听器 可读副本的负载均衡 主角色 ...
- H5学习笔记-应用缓存,Web worker,服务器发送事件
↑亮了 应用缓存用法 <!DOCTYPE HTML> <html manifest="demo.appcache"> <body> The co ...
- Adobe全系软件下载安装工具 CCMaker 1.3.6
CCMaker是俄罗斯大神El Sanchez开发的一款集Adobe软件全家桶下载.安装.激活一条龙服务的小工具. 程序小巧强大,使用微软通用运行库开发,效率高体积小. 注意,此程序需要安装微软通用C ...
- Java项目案例之---常用工具类练习
常用工具类练习 1. 请根据控制台输入的特定日期格式拆分日期,如:请输入一个日期(格式如:**月**日****年),经过处理得到:****年**月**日 import java.util.Scanne ...
- R018---RPA是什么东东?
1.缘起 这个问题,很多文章回答过,一直想站在客户角度写个答案,今天正好. 2.RPA的名字 RPA是英文Robotic Process Automation的缩写,中文爱翻译为“流程自动化机器人” ...