0%

2021全国大学生电子设计大赛——G题(植保无人机)

前言

2021年全国大学生电子设计大赛——G题(植保无人机)总结

本文主要分为如下四个部分:

  • 赛题说明
  • 思路说明
  • 硬件说明
  • 总结说明

获奖情况:全国二等奖、广东省一等奖

赛题说明

赛前材料清单

今年赛前材料清单给出来后,我们对比19年的电赛材料,认为还是在三脚架上做文章,而19年赛题的三脚架是用于立两根杆,所以我们还是趋向今年题目也是在杆上做文章。简单完成历年无人机的题目,我们还是无法很好地解决19年的赛题,主要问题在于:

  • OpenMV无法很好地识别两杆之间的线以及标识物
  • 无人机盲飞效果很差

以上两个问题导致我们无法很好地让飞机以巡线的方式去闭环飞行。直到比赛临近,我们虽然稍微完善了识别算法使得飞机能较好地巡线飞行,但依旧无法顺利完成任务。想到今年题目大概率和这个命题相似,我们心里已经没了信心。后续比赛因为疫情延期,我们几乎还是在原地踏步。

赛题发布

今年赛题的发布后我们第一反应是题目太简单了(后面去实现才知道是自己太天真)。而三脚架的真正用途也是放置二维码给飞机识别,不过分值并不高。

赛题具体内容如下:

image-20220412212614313

基本任务:“十字”出发,寻找字符A为播种起点,对地图中绿色区域进行播种任务。且同一块区域播种次数不能超过3次

发挥部分:黑色杆有可识别二维码,识别二维码后,(二维码数字 10CM) 作为距离十字中心的降落半径,成功下降。*(这部分一开始打算一同完成,但最后还是时间不允许,于是我们舍弃了这部分)


思路说明

最初思路

拿到赛题开始,我们直接有了第一步的思路,而且认为时间很富裕(后面觉得就离谱),这个思路一直等到我们比赛的第二天的实际效果依旧不佳而最终毙掉。

思路:

image-20220412213738889

思路很简单,这也是我们学校大部分无人机赛题队伍最开始实行的方案——盲飞

根据上面的线路图,无人机按照这个线路盲飞,同时无人机对地的OpenMV采集色块信息,若为绿色,则选择播种,否则,按照既定路线继续飞行。

这个方法思路很简单,也很高效,同时包括后续的发挥部分使用这一方案也是没有任何需要改动的地方,后来的实际情况是,我们学校采用这一方法的队伍基本都在比赛的时候出了大问题——飞机一旦偏航,误差累积越来越大将导致飞机无法走完全程,甚至无法回到出发点。(不过后来B站上很多飞控商家还是很完美的盲飞了…)

闭环思路

折磨两天后,我们发现效果不佳,大家都抑郁了,在队伍T哥的建议下,我们决定秉承着舍弃盲飞,转而寻找使飞机可以“闭环”的方法。

思路说明:

image-20220412215852783

既然寻找“闭环”,那么就要找到对应的“反馈量”,最后我们决定以绿色与灰色之间的边界线作为我们整个方案的反馈量。

具体做法是:列举我们这条路线中所有出现的灰色与绿色边界的组合,具体有如下几种:

image-20220412220931916

  1. 上灰下绿
  2. 左下绿
  3. 左绿右灰
  4. 左上绿
  5. 右下灰
  6. 右上灰
  7. 上绿下灰
  8. 左下灰

以上八种情况包括了这条路线中所有的情况,且没有相冲突的情况,唯一有的问题是:如何分割摄像头捕捉到的画面

对于上述问题,得益于OpenMV丰富的库,我们设计了如下方法:

  1. 首先将捕捉画面四个边缘做相应的裁剪,这一步是因为飞机要飞到指定高度,需要把视界多余部分裁掉,避免边缘视野信息对后续信息的判断产生影响。
  2. 使用find_blobs()函数,找到绿色以及灰色 色块的中心位置。以下图为例做解释:

image-20220412222013105

以22号播种区域为例,红色为整个捕获界面,黄色为裁剪后的有效信息,蓝色为find_blobs()函数下寻找到的绿色色块,黑色为find_blobs()函数下找到的灰色色块。(这里需要解释的是,设定find_blobs()函数pixels_thresholdmergemargin三个参数,可以使得统一色块归为一个),如此,再借助find_blobs()函数返回的色块对象即可得到相应的色块中心X,Y的坐标,最后,通过该坐标进行比较,得到八种情况,即可以实现最终的效果。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
if (ctr.work_mode&0x01)!=0:     #判断是否在绿色区域,设定为任务 1
img=sensor.snapshot().lens_corr(1.8, 1.0)
target.img_width=IMAGE_WIDTH
target.img_height=IMAGE_HEIGHT
pixels_max_g = 0
pixels_max_w = 0
pixels_max_b = 0

green_x = 0
green_y = 0
green_w = 0
green_h = 0
white_x = 0
white_y = 0
white_w = 0
white_h = 0

x_gap = 0
y_gap = 0
width_gap = 0
height_gap = 0

for b in img.find_blobs([thresholds_rgb[1]], roi = roi_area, pixels_threshold=500,merge=True, margin = 30):
img.draw_rectangle(b[0:4],color = (255,0,0))#圈出搜索到的目标
if pixels_max_g < b.pixels():
pixels_max_g = b.pixels()
green_x = b.cx()
green_y = b.cy()
green_w = b.w()
green_h = b.h()
#if target.flag==1:

for c in img.find_blobs([thresholds_rgb[4]], roi = roi_area, pixels_threshold=500,merge=True, margin = 30):
img.draw_rectangle(c[0:4])
if pixels_max_w < c.pixels():
pixels_max_w = c.pixels()
white_x = c.cx()
white_y = c.cy()
white_w = c.w()
white_h = c.h()

x_gap = green_x - white_x
y_gap = green_y - white_y
width_gap = green_w - white_w
height_gap = green_h - white_h
x_gap = int(gap_x_filter.Get_Value(x_gap))
y_gap = int(gap_y_filter.Get_Value(y_gap))
width_gap = int(gap_width_filter.Get_Value(width_gap))
height_gap = int(gap_height_filter.Get_Value(height_gap))

if((x_gap > 0 and y_gap > 0 and height_gap < -20) or
(-35 < x_gap < 35 and y_gap >0) or
(x_gap < 0 and y_gap > 0 and width_gap > 20)):
if((-35 < x_gap < 35 and y_gap >0) and height_gap < -25):
target.reserved1 = 0x05 #0000 0101
#print("rush right with down!")
elif((-35 < x_gap < 35 and y_gap >0) and height_gap > 90):
target.reserved1 = 0x09 #0000 1001
#print("rush right with upward!")
else:
target.reserved1 = 0x01 #0000 0001
#print("rush right!")

target.reserved2 = 0x02 #左右主轴

elif((x_gap < 0 and y_gap > 0 and width_gap < -20) or
(x_gap < 0 and -20 < y_gap < 20) or
(x_gap < 0 and y_gap < 0 and width_gap > 20)):
if(x_gap < 0 and y_gap < 0 and width_gap < 60):
target.reserved1 = 0x06 #0000 0110
target.reserved2 = 0x02 #左右主轴
else:
target.reserved1 = 0x04 #0000 0100
#print("rush down!")

target.reserved2 = 0x01 #前后主轴

elif((x_gap < 0 and y_gap < 0 and width_gap < -20) or
(x_gap > 0 and y_gap < 0 and width_gap > 20) or
(-45 < x_gap < 45 and y_gap < 0)):
if((-45 < x_gap < 45 and y_gap < 0) and height_gap < 50):
target.reserved1 = 0x0A #0000 1010
target.reserved2 = 0x02 #左右主轴
#print("rush left with upward!")
elif((-45 < x_gap < 45 and y_gap < 0) and height_gap > 105):
target.reserved1 = 0x06 #0000 0110
target.reserved2 = 0x02 #左右主轴
#print("rush left with down!")
elif(x_gap > 0 and y_gap > -70 and width_gap > 20):
target.reserved1 = 0x08 #0000 1000 最后拐角处,向上走
target.reserved2 = 0x01 #左右主轴
#print("00000")
#print("rush left with upward!")
else:
target.reserved1 = 0x02 #0000 0010
target.reserved2 = 0x02 #左右主轴
#print("rush left!")

#target.reserved2 = 0x02 #左右主轴

else:
if((x_gap > 0 and y_gap < 0) and width_gap < -15):
target.reserved1 = 0x0A #0000 1010
target.reserved2 = 0x02 #左右主轴
#print("rush left with forward!")
else:
target.reserved1 = 0x08
target.reserved2 = 0x01
#print("rush upward!")
  1. 结合上面代码,就是对于各种情况下无人机如何飞行情况的穷举了。例如:上灰下绿情况下,飞机只可能位于地图最上的边缘,那么飞机就只能往右飞行。其他情况也是如此推理。
  2. 不断调试自己飞机的边界阈值:这一步是我们最耗费时间的地方,因为飞机的晃动,高度的沉浮都会影响到这部分的判断,所以这个方法使用的前提就是飞机本身定高要相对稳定,剩下的XOY平面的偏差则通过各种判断进行校正。
  3. XOY平面位置校正:结合上面代码,我将每种情况留了对应的阈值,如果超过该情况的阈值,则飞机要进行位置校正。就上灰下绿情况下,如果灰色中心Y的与绿色中心Y的差值大于阈值,则说明飞机偏出绿色播种区域。其他情况也是类似。
  4. 播种:我们策略是OpenMV算力都用于检测边界,且我们算法情况下,飞机必定是在播种区域上方,如此,飞机匀速运动,固定时间间隔进行播种,只要保证不要过快播种导致播种次数超过三次即可。
  5. 返航与找到A表示点:这个任务结合本来我们打算使用模式匹配的方法,如匹配”A字符”、“十字”等去让无人机寻找,后面发现,单纯使用find_blobs()寻找黑色色块的方法也是同样效果的,同时还能更加简单地返还该图形的X、Y坐标,以便无人机进行位置校准(校准方法:色块中心XY坐标与整体图像视野XY坐标的差值,这要求MV视野中心尽可能地靠近无人机几何中心)。

硬件说明

  • 主控:TI官方TM4C123
  • 机架:F330碳纤维机架(耐撞,前期撞了两次天花板……)
  • 视觉模块:OPENMV4 H7 PLUS(运存勉强够用)
  • 定高:TOFSense激光测距传感器
  • 电机:无名创新A2212系列
  • 桨叶:8寸桨叶

总结说明

首先我认为我们队伍真的是十分幸运,在推翻第一版思路的情况下,我们第二版的思路在我们实践的过程中效果其实并不算理想,常常存在卡在死循环的情况出不去,这是阈值部分太过于接近所导致的。但比赛当天,无论是第一轮还是第二轮的实际飞行,效果都是远远超出了我们的预料,飞机毫无卡顿地完成了任务。直到飞机落地的那一刻我都不敢相信自己的眼睛。带着这份惊讶,我们也收获到了这份重量级的奖项。

四天三夜的比赛真的是折磨人,饮食不规律,睡眠不充足的问题直接让人憔悴许多。加之又在上课期间,课堂上的小组任务又等着自己去完成,不能拖整个小组的进度,算下来那四天平均的睡眠还不到四个小时。

关于技术方面的总结其实观察历年的赛题可以发现只有一条:飞机自身的稳定,这一条我认为是电赛无人机赛题的灵魂。出色的稳定性往往能够代替许多复杂的算法,同时也是所有算法的基石。稳定性就是在这种比赛中有如此举重若轻的作用。19年飞机能否稳定定高?能否稳定直线飞?这都是出色完成任务的前提。今年的赛题亦是如此,出色的无人机稳定性可以直接拿下这道题。

最后还是感谢一起比赛的队友,给予极大支持的老师,还有关心我身体的家人。本科生涯最后的国家级比赛,注定会在我记忆中长存。