2023年全国大学生电子设计大赛E题视觉思路

8/7/2023 电赛

# 2023年全国大学生电子设计大赛E题视觉思路

这些都是我和队友们茶不思夜不寐苦想出来的 *** 转载请注明来源 ***

# 背景

第一次玩视觉和python,整完智能车只有十几天的时间学。
可能有些地方不太对,欢迎各位大佬指正。
题目要求详见2023年TI杯>>E题-运动目标控制与自动追踪系统 (opens new window)

# 选型

选型这块没什么好说的,市面上的视觉处理都能用。OpenMV,k210,v831,树莓派都可以。
K210由于没有USB口,链接IDE调试的时候帧率极低,实际效果需要实测才能知道。只是追踪色块还是够用的。

# 设计思路

  • 按组委会的问答看,摄像头是随意可以随意摆放的,保持激光和屏幕的距离为一米即可。我的思路是红点跟着摄像头一起动,绿点静止。
  • 激光的斑点大小小于等于1cm,光晕不算在内,激光笔的光晕可以大点,方便识别。加上滤光片可以减小环境光的影响。
  • 摄像头的视角尽量把整个屏幕看到特别的静止的摄像头,最理想的情况是摄像头的视角刚好是整个屏幕,可以减少外界堆识别的影响。
  • 黑色胶带会吸光,这点需要考虑在内。任何光线影响都是可以通过调整曝光解决的,可以试着手动调节曝光,达到理想的效果
  • 云台的可控精度一定要高,这是整个题的关键,精度不高说啥都没用。

# 视觉处理

这次视觉部分比较简单,只要寻找色块和四边形即可。可以不涉及机器学习。
1、有关基础第一题和第二题:对摄像头要求很高,如果没有高清晰度的摄像头我建议开环。第二题确实能看到铅笔画的框可以用边缘检测+find_rects(寻找矩形)或者fine_line(巡直线)
2、基础部分三次题1.8cm宽的胶带找出来很方便,要招红色斑点得调曝光值,当然如果滤光片效果好也是可以的。 3、发挥部分主要是招色块了这部分用find_blobs都能解决。

# 解题思路

手上资源有限,我们手上只有一个OpenMV H7和一个V831,我选择用更熟悉的OpenMV作为主处理,V831来找两个激光,毕竟v831的摄像头要好的多,容易找。
关于基础第三四题的思路,我们采用半开环。
第三题,摄像头跟着云台动,用摄像头找到四边形第一个角,然后写死。
第四题,摄像头跟着云台动,用摄像头找到四边形第一个角,然后划线到第二个角以此类推。
摄像头的中心点就是红色激光笔打在屏幕上的地方,理论上是可行的。但实际上云台在动的时候摄像头会抖,导致这题分就被扣完了。所以为了保险再找个红点修正误差。或者通过相似三角形把图像的坐标映射到屏幕上。加个陀螺仪然后写死比例。

# V831处理处理矩形和红点参考

V831可以参考这篇Neucrack-关于2023年电赛E题的简单思考 (opens new window)
所说的maix II DOCK就是v831

# OpenMV处理矩形和红点参考

使用OpenMV就比较简单了,把曝光值一调,红绿点就找出来了,如果想要同时识别矩形,就得把曝光值调回来,不然会一片黑。
可以采用状态机的思想来做,下面是我的shi山代码,思路可以做参考,多少会有点问题,不要直接cv。
下面的思路就是找到第一个点,然后云台写死比例,让云台绕着算出来的路径划线。

import sensor, image, time
from pyb import UART

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA)  # H7 Plus可以用QVGA看的更清楚
sensor.set_vflip(True)              # 图像镜像
sensor.set_hmirror(True)
sensor.skip_frames(time = 2000)
uart = UART(3, 115200)              # 串口初始化
clock = time.clock()
# 红色的LAB阈值
THRESHOLD = (6, 98, 5, 39, -3, 18)
# 屏幕中心点对应的图像中坐标
center_point = (80, 37)
# 我需要给队友发的几个点
first_point = [0, 0]
second_point = [0, 0]
last_point  = [0, 0]
# 状态机
state = 0
# 红点位置
red = 0
redpoint = [0,0]
# 临时变量
pian_x0 = [99,99,99]
pian_y0 = [99,99,99]
i=0

def area(x1, y1, x2, y2, x3, y3):
    """计算三角形面积"""
    return abs((x1*(y2-y3) + x2*(y3-y1) + x3*(y1-y2)) / 2.0)

def is_inside_polygon(x1, y1, x2, y2, x3, y3, x4, y4, cx, cy):
    """检查点(cx, cy)是否在四边形内部"""
    # 计算四边形的面积
    total_area = area(x1, y1, x2, y2, x3, y3) + area(x1, y1, x4, y4, x3, y3)

    # 计算点和四边形各个顶点构成的四个三角形的面积
    a1 = area(cx, cy, x1, y1, x2, y2)
    a2 = area(cx, cy, x2, y2, x3, y3)
    a3 = area(cx, cy, x3, y3, x4, y4)
    a4 = area(cx, cy, x4, y4, x1, y1)

    # 如果这四个三角形的面积之和等于四边形的面积,则点在四边形内部;否则,点在四边形外部
    return total_area == a1 + a2 + a3 + a4

# 找最大色块
def find_max(blobs):
    max_size=0
    for blob in blobs:
        if blob[2]*blob[3] > max_size:
            max_blob=blob
            max_size = blob[2]*blob[3]
    return max_blob

while(True):
    clock.tick()
    if state != 2:  # 状态2是找红点的,所以状态2不能进入下面的程序
        img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0) ## 畸变矫正,就是关掉摄像头的鱼眼效果
        # 找矩形
        for r in img.find_rects(threshold = 10000):
            img.draw_rectangle(r.rect(), color = (255, 0, 0))
            if r.magnitude() > 20000 and r.magnitude() < 60000:
                for p in r.corners(): img.draw_circle(p[0], p[1], 3, color = (0, 255, 0))
            if state == 0:
                if r.magnitude() > 20000 and r.magnitude() < 60000:
                    pianx = int(r.corners()[3][0])+3-int(center_point[0])
                    piany = int(r.corners()[3][1])+3-int(center_point[1])
                    print("pianx:%4d  piany:%4d  main:%4d\r\n" % (pianx, piany, r.magnitude()))
                    # 把第一个点的偏差发给主控
                    uart.write("pianx:%4d  piany:%4d\r\n" % (pianx, piany))
                    pian_x0[i] = pianx
                    pian_y0[i] = piany
                    i = i+ 1
                    if i>=3:
                        i=0
    #                print("x1:%4d  y1:%4d  x2:%4d  y2:%4d  x3:%4d  y3:%4d x00:%4d x01:%4d x02:%4d\r\n"%(first_point[0], \
    #                    first_point[1], second_point[0], second_point[1], last_point[0], last_point[1], \
    #                    pian_x0[0],pian_x0[1],pian_x0[2]))
                    # 因为不是狠熟悉python的用法,所以就简单粗暴点,整个是检测到激光是否稳定打到第一个点
                    if pian_x0[0] ==0 and pian_x0[1] == 0 and pian_x0[2] == 0 and pian_y0[0] == 0 and pian_y0[1] == 0 and pian_y0[2] == 0:
                        # 满足就进入状态1
                        state = 1
                        first_point[0] = int(r.corners()[3][0])+1
                        first_point[1] = int(r.corners()[3][1])+1
                        second_point[0] = int(r.corners()[2][0])-1
                        second_point[1] = int(r.corners()[2][1])+1
                        last_point[0]  = int(r.corners()[1][0])-1
                        last_point[1]  = int(r.corners()[1][1])-1
                        time.sleep_ms(500)
            # 状态1: 把剩下的三个点发给主控,实现半闭环
            if state == 1: 
                uart.write("x1:%4d  y1:%4d  x2:%4d  y2:%4d  x3:%4d  y3:%4d\r\n"%(first_point[0], \
                          first_point[1], second_point[0], second_point[1], last_point[0], last_point[1]))
                print("x1:%4d  y1:%4d  x2:%4d  y2:%4d  x3:%4d  y3:%4d\r\n"%(first_point[0], \
                first_point[1], second_point[0], second_point[1], last_point[0], last_point[1]))
                time.sleep_ms(500)
                state = 2   # 发完以后进入状态2
    # 状态2: 降低曝光,找红点
    if state == 2:
        # 调整增益和曝光值
        sensor.set_auto_gain(False)
        sensor.set_auto_exposure(False, exposure_us=1400)
        sensor.set_auto_whitebal(False)
        img2 = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0)
        blobs = img2.find_blobs([THRESHOLD])    # 找红点
        if blobs:
            max_blob = find_max(blobs)
            redpoint[0]=max_blob.cx()
            redpoint[1]=max_blob.cy()
            # 限幅,在屏幕外面的红点就不发给主控
            if 20 < redpoint[0] < 140 and 10 < redpoint[1] < 70:
                img2.draw_cross(max_blob.cx(), max_blob.cy(), color=(0, 255, 128))
                print("cx: %4d  cy: %4d"%(max_blob.cx(),max_blob.cy()))
                uart.write("cx:%4d  cy:%4d\r\n"%(redpoint[0],redpoint[1]))
        else:
            red = 0
        uart.write("red:%2d\r\n" % red)
        state = 3   # 转到状态3
    # 状态3: 继续找矩形,因为是半闭环这里主要是用来判断红点是否出界
    if state == 3:
        # 把增益和曝光调回来
        sensor.set_auto_gain(True)
        sensor.set_auto_exposure(True)
        sensor.set_auto_whitebal(False)
        img3 = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0) ## 畸变矫正
        for r in img3.find_rects(threshold = 10000):
            img3.draw_rectangle(r.rect(), color = (255, 0, 0))
            if r.magnitude() > 20000 and r.magnitude() < 60000:
                for p in r.corners(): img3.draw_circle(p[0], p[1], 3, color = (0, 255, 0))
        # 检测红点在不在矩形里面,在里面为1,外面为-1
        if is_inside_polygon(r.corners()[3][0],r.corners()[3][1], r.corners()[2][0],r.corners()[2][1],\
                             r.corners()[1][0],r.corners()[1][1], r.corners()[0][0],r.corners()[0][1],\
                             redpoint[0],redpoint[1]):
#            print("True")
            red = 1
        else:
#            print("False")
            red = -1
        uart.write("red:%2d\r\n" % red)
#        print("red: %2d" % red)
#        print(abs(r.corners()[3][0]-redpoint[0]),abs(r.corners()[3][1]-redpoint[1]))
        # 检测到红点差不多打回第一个点就退出状态,否则回去找红点
        if abs(r.corners()[3][0]-center_point[0])<13 and abs(r.corners()[3][1]-center_point[1])<15:
            state = 0
        else:
            state = 2
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148

上面代码经过验证是可行的,就是有玄学成分。
下面代码思路是找到第一个点,然后慢慢划线到第二个点,然后第三个,第四个以此类推。(未经过验证,只是理论)

import sensor, image, time
from pyb import UART

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA)  # H7 Plus可以用QVGA看的更清楚
sensor.set_vflip(True)              # 图像镜像
sensor.set_hmirror(True)
sensor.skip_frames(time = 2000)
uart = UART(3, 115200)              # 串口初始化
clock = time.clock()

# 屏幕中心点对应的图像中坐标
center_point = (80, 40)
# 状态机
state = 0
# 记录四个点的坐标
rect_points = []
# 记录面积
area = 0

while(True):
    clock.tick()
    img = sensor.snapshot().lens_corr(strength = 1.8, zoom = 1.0) ## 畸变矫正,就是关掉摄像头的鱼眼效果
    # 找矩形
    for r in img.find_rects(threshold = 10000):
        # 画出找到的矩形
        img.draw_rectangle(r.rect(), color = (255, 0, 0))
        # 记录面积
        area = r.magnitude()
        # 限制矩形大小,太大太小抖滤掉
        if area > 20000 and area < 53000:
            # 用圆出四个角
            rect_points = []
            for p in r.corners():
                img.draw_circle(p[0], p[1], 3, color = (0, 255, 0))
                rect_points.append(p)   # 记录坐标点
    # 状态0: 找第一个角
    if state == 0:
        err_p1=(rect_points[3][0]-center_point[0],rect_points[3][1]-center_point[1])
        if area > 20000 and area < 53000:
            print("p1_x:%4d  p1_y:%4d\r\n" % (err_p1[0], err_p1[1]))
            # 把第一个点的偏差发给主控
            uart.write("p1_x:%4d  p1_y:%4d\r\n" % (err_p1[0], err_p1[1]))
        if err_p1[0] < 2 and err_p1[1] < 2:
            state = 1
    # 状态1: 找第二个角
    if state == 1:
        err_p2=(rect_points[2][0]-center_point[0],rect_points[2][1]-center_point[1])
        if area > 20000 and area < 53000:
            print("p2_x:%4d  p2_y:%4d\r\n" % (err_p2[0], err_p2[1]))
            # 把第二个点的偏差发给主控
            uart.write("p2_x:%4d  p2_y:%4d\r\n" % (err_p2[0], err_p2[1]))
        if err_p2[0] < 2 and err_p2[1] < 2:
            state = 2
    # 状态2: 找第三个角
    if state == 2:
        err_p3=(rect_points[1][0]-center_point[0],rect_points[1][1]-center_point[1])
        if area > 20000 and area < 53000:
            print("p3_x:%4d  p3_y:%4d\r\n" % (err_p3[0], err_p3[1]))
            # 把第三个点的偏差发给主控
            uart.write("p3_x:%4d  p3_y:%4d\r\n" % (err_p3[0], err_p3[1]))
        if err_p3[0] < 2 and err_p3[1] < 2:
            state = 3
    # 状态3: 找第四个角
    if state == 3:
        err_p4=(rect_points[0][0]-center_point[0],rect_points[0][1]-center_point[1])
        if area > 20000 and area < 53000:
            print("p4_x:%4d  p4_y:%4d\r\n" % (err_p4[0], err_p4[1]))
            # 把第四个点的偏差发给主控
            uart.write("p4_x:%4d  p4_y:%4d\r\n" % (err_p4[0], err_p4[1]))
        if err_p4[0] < 2 and err_p4[1] < 2:
            state = 0

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

*** 上述代码未经验证,绝对会有逻辑错误,仅提供思路 ***

# V831寻找色块参考

我V831搭载的摄像头不错,不调曝光也能看到红点和绿点。
这段其实没什么好说的,都是官方例程,直接上代码吧。

from maix import image, display, camera
import serial

ser = serial.Serial("/dev/ttyS1",115200)  # 串口初始化
green = [(59, 100, -27, -5, -128, 127)]   # [绿光点大点(69, 86, -50, -32, -51, 70) 小点[(59, 100, -27, -5, -128, 127)]]
red   = [(52,  77,  10,  39, -14,   8)]   # [红光点(68, 97, 17, 42, -21, 41)]  [(61, 75, 18, 39, 0, 17)]
camera.camera.config(size=(240, 240))

grn_point = (0,0)
red_point = (0,0)

while True:
    img = camera.capture()
    g_blobs = img.find_blobs(green, merge=True)    #在图片中查找lab阈值内的颜色色块 merge 合并小框。
    r_blobs = img.find_blobs(red  , merge=True)
    if g_blobs:
        for i in g_blobs:
            grn_point = (int(i["centroid_x"]),int(i["centroid_y"]))
            img.draw_rectangle(i["x"], i["y"], i["x"] + i["w"], i["y"] + i["h"], 
                               color=(0, 0, 255), thickness=1) #将找到的颜色区域画出来
            # print("g_cx:%4d , g_cy:%4d" % (grn_point[0],grn_point[1]))
    if r_blobs:
        for j in r_blobs:
            red_point = (int(j["centroid_x"]),int(j["centroid_y"]))
            img.draw_rectangle(j["x"], j["y"], j["x"] + j["w"], j["y"] + j["h"], 
                               color=(255, 0, 0), thickness=1) #将找到的颜色区域画出来
            # print("r_cx:%4d , r_cy:%4d" % (red_point[0],red_point[1]))
    ser.write(b"err_x:%4d  err_y:%4d\r\n" % ((red_point[0]-grn_point[0]),(red_point[1]-grn_point[1])))
    display.show(img)
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

# V831调整增益和曝光

如果摄像头实在看不到红点和绿点可以和openMV一样调整增益和曝光
原文:sipeed wiki (opens new window)

from maix import camera, display, image
camera.config(size=(224, 224))
exp, gain = 16, 16 # 初值,exp 曝光[0, 65536],gain 增益[16 - 1024],随意设置得值会受到驱动限制。
for i in range(120):
    exp, gain = exp + 32, gain + 16
    camera.config(exp_gain=(exp, gain))
    img = camera.capture()
    display.show(img)
camera.config(exp_gain=(0, 0)) # 设置为 0, 0 表示放弃控制恢复成自动曝光。
1
2
3
4
5
6
7
8
9

如果需要旋转图像可以参考sipeed wiki (opens new window)

# 树莓派(opencv)寻找色块和矩形参考

我们对树莓派和opencv了解不深,不过可以参考一下这篇博客 (opens new window)
里面说的挺简洁的

# 总结

其实今年这题就纯比硬件,高精度决定上限,题目虽然看起来不难,但是要求的精度特别高。
对于云台和摄像头来说都是挑战,像我们这些穷小子是玩不来的(泣了)

最近更新: 11/18/2024, 9:46:50 AM