关于22年电赛省赛——传统视觉做倒车入库

分析

首先与机械组做好协调,使用TTL串口通信传递指令,我们使用了三位数,分别是控制前进、后退、停止,前轮的角度,云台的角度。这样我们就可以完全使用视觉控制系统完成倒车。

对于电控的部分,使用了STM32F3C8T6,舵机使用了5个2g舵机,主动轮使用了传统无刷电机,通过STM32修改PWM的占空比实现了舵机任意角度的旋转。不过电控我没看,我主要描述一下使用的视觉算法以及我期望的视觉算法。

首先我们分析一下特征,每个停车位都有一个箭头,所以我们需要识别它,至于倒车入库集体的实现交给STM32完成,所以我们视觉只需要通过串口告诉STM32:我看到了箭头即可。

传统视觉

为了提取特征,我们可以尝试进行卷积操作用来保留特征,然后通过对象识别来找到它。不过这里有一个很严重的问题,那就是嵌入式系统的算力并不高,对象识别并不准确,所以这个方案被舍弃了。

不过这种办法我也会在后面说,毕竟这种办法的精度很高

第二种就是我们使用的方法,就是特征点识别,首先我们要分析一个现实的情况:

在一个简单的光环境内,拍摄一个立体物体会有什么特点?

可以看到,在光下,自然的物体出现了阴影,也就意味着,阴影部分是一个光饱和度从低到高的过程。我用一张阴影图表示,斜线密度越大,阴影越深,光是从右上角照下来的,从图上看就是右上角的斜线密度大。

反映在灰度图片上我们可以发现,他的梯度是连续均匀的变化着的,所以我们可以得到结论,物体的边缘位置处,颜色值梯度是均匀变化的,而边缘位置近乎描述了物体的特征形状,所以通过此法可以获取到物体的特征。

如何实现这个过程呢?我们需要使用SIFT算法,SIFT主要分为以下两步:

  • 创建高斯金字塔
  • 确定梯度方向与关键点的梯度方向

首先拍摄一些目标的图片,然后我们采用从大图片逐步卷积为小图片的方法,完成上采样金字塔:

可以看到目标箭头的边角比较锐利,并未出现梯度值均匀变化的情况,而是直接的转变,这意味着这是一个与环境不相容的特征物体,所以我们取点要取在这种类型的物体边缘,这些点就是我们所需要的特征点。

构建过程简单来讲就是图片反复卷积,由大图变成小图,最后出来的就是特征。然后计算最后图片不同方向不同位置的梯度值,这样我们就得到了很多的特征点。

因为我们使用的是openMV,这些算法不需要我来完成,不过底层实现是SIFT算法。

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
import sensor, image, time
from pyb import LED
from pyb import UART

sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)

sensor.set_contrast(3)
sensor.set_gainceiling(16)
sensor.set_framesize(sensor.VGA)
sensor.set_windowing((320, 240))

clock = time.clock()

kernel_size = 2
kernel = [4,5,6,5,4,\
5,-3,-4,-3,5,\
6,-4,-5,-4,6,\
5,-3,-4,-3,5,\
4,5,6,5,4]
# 计数器
times = 0
# 采样数
kick=10
# 加载特征
# kpts1 = image.load_descriptor("/desc.orb")

# 模板匹配
def drawAim(temps,img):
for tem in temps:
r = img.find_template(tem, 0.60, step=7, search=SEARCH_EX)
if r:
img.draw_rectangle(r)

# 发送数据
# ---- 格式 ----
# 0 stop
# 1 run
# 2 back
# x +-45 转向
# xx +-90 云台转向
# 共三个部分,使用 \x十六进制 的格式发送
def postData(Data):
uart = UART(3,115200)
uart.write(Data)

# 画出特征点
def drawKeypoint(img,kpts1):
if kpts1:
img.draw_keypoints(kpts1)
time.sleep_ms(2000)
sensor.snapshot()

# 比较特征
def consider(img,kpts1,ktps2):
if (kpts1 == None):
kpts1 = img.find_keypoints(max_keypoints=50, threshold=10, scale_factor=1.2) # 用kepts1保存函数提取到的特征
drawKeypoint(img, kpts1)
else:
kpts2 = img.find_keypoints(max_keypoints=50, threshold=10, normalized=True) # 调用函数检测视野中是否存在特征,并将检测到的特征保存在kpts2中
if (kpts2):
# 将当前找到的特征和最初的目标特征kpts1进行对比
match = image.match_descriptor(kpts1, kpts2, threshold=85)
if (match.count()>20):
img.draw_rectangle(match.rect())
img.draw_cross(match.cx(), match.cy(), size=10)
postData("\0x02\0x2d\0x00")
time.sleep_ms(1500)
postData("\0x00\0x00\0x00")
print(kpts2, "matched:%d dt:%d"%(match.count(), match.theta()))

# 保存特征
def saveKPT(FILE_NAME,kpt):
image.save_descriptor(kpt, "/%s.orb"%(FILE_NAME))
LED(1).on()
LED(2).on()
LED(3).on()


while(True):
clock.tick()
img = sensor.snapshot()
kpts1 = img.find_keypoints(max_keypoints=100, threshold=10, scale_factor=1.2)
# 采样
drawKeypoint(img,kpts1)
if(kpts1!=None and times<kick):
times += 1
saveKPT(times,kpts1)
else:
print("Havent point")
# 使用采样
'''
for i in range(kick):
add = "/" + str(i+1) + ".orb"
aimKpt = image.load_descriptor(add)
consider(img,kpts1,aimKpt)
# postData("\0x00\0x2d\0x2d")
'''

这个方法对于OpenMV来说,还是有些超负荷,所以接下来的CNN更加无法适用。

卷积神经网络

这种做法是我所希望的,因为只需要投入一定量的模型就可以识别的较为准确,因为特征较为明显,所以我只使用一层隐含层,并且只有两个输出,是或不是。下面是结构图:

关于CNN的介绍我也就不再复习了,总体上可以参考我之前写过的文章,为了实现的简单与方便,我这里使用了Tensorflow完成操作,但是毕竟嵌入式的算力有限,没办法使用CUDA,如此只能简单的完成了。

玻尔兹曼机:https://blog.minloha.cn/2022/10/24/%E7%8E%BB%E8%80%B3%E5%85%B9%E6%9B%BC%E6%9C%BA/

神经网络的理解:https://blog.minloha.cn/2021/12/13/%E4%B8%80%E4%BA%9B%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E7%9A%84%E7%90%86%E8%A7%A3/

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
import numpy as np
import tensorflow as tf
from tensorflow import keras
from keras import layers, losses, optimizers
import cv2
import os
from queue import Queue

# ----- 输入寄存 ----
trainQueue = Queue()


# ---- 读取数据文件夹,一定是一个类别的
def loadImgToMatrix(imgsPath):
for img in os.listdir(imgsPath):
print(img)
aimg = cv2.imread(imgsPath + img)
aimg = cv2.cvtColor(aimg, cv2.COLOR_RGB2GRAY)
trainQueue.put(np.array(aimg))


# 训练一个网络,返回这个网络
def trainNN(aim):
criteon = losses.CategoricalCrossentropy(from_logits=True)
y_train = np.random.randint(1, size=(1, 2))
model = keras.Sequential({
layers.Conv2D(81, kernel_size=3, padding='SAME', strides=3),
layers.MaxPool2D(pool_size=2, strides=2),
layers.ReLU(),
layers.Conv2D(81, kernel_size=3, padding='SAME', strides=1),
layers.MaxPool2D(pool_size=2, strides=2),
layers.ReLU(),
layers.Flatten(),
layers.Dense(120, activation='relu'),
layers.Dense(81, activation='relu'),
layers.Dense(2),
})
for q in range(trainQueue.qsize()):
x = q.get()
# 使用自适应动量法进行参数优化
model.compile(lose='softmax', optimizer='Adam', metrics=['accuracy'])
model.fit(x, y_train, epochs=1, batch_size=81)
y_out = tf.one_hot(y_train, depth=10)
loss = criteon(y_out, aim)
print(loss)
model.summary()
return model


if __name__ == "__main__":
loadImgToMatrix("example/")
trainNN(np.array([1, 0]))

最后经过一夜的模型训练,精度几乎100%。

总结

这次比赛虽然最后还是输了,因为关键点忽然就全部消失了,而且在实际测试过程中openMV的线程卡死了,导致完全没办法跑完全程,只能说全都是经验吧!


关于22年电赛省赛——传统视觉做倒车入库
https://blog.minloha.cn/posts/1516079e4e2e5a2022101631.html
作者
Minloha
发布于
2022年10月16日
更新于
2024年4月8日
许可协议