资讯专栏INFORMATION COLUMN

2021电赛F题送药小车视觉部分的一种思路(双OpenMV法)

acrazing / 3029人阅读

摘要:总体流程如下代码实现透视校正用的四个点识别数字的五个区域反相后红色阈值各区域识别数字准确度门槛拍照并进行一堆预处理识别中心数字识别左起第一个数字识别左起第二个数字识别左起第三个数字

使用两块OpenMV解答送药小车视觉部分

前言:
最近参加了2021年电赛的F题,因为诸多原因未能完赛,现将图像识别部分的记录一下,交流学习。

一、2021电赛F题题目回顾与分析

1.题目介绍

因为只介绍视觉部分,我们就节选相关的部分吧。

设计并制作智能送药小车,模拟完成在医院药房与病房间药品的送取作业。院区结构示意如图 1 所示。院区走廊两侧的墙体由黑实线表示。走廊地面上画有居中的红实线,并放置标识病房号的黑色数字可移动纸张。药房和近端病房号(1、 2 号)如图 1 所示位置固定不变,中部病房和远端病房号(3-8 号)测试时随机设定。

工作过程:参赛者手动将小车摆放在药房处(车头投影在门口区域内,面向病房),手持数字标号纸张由小车识别病房号,将约 200g 药品一次性装载到送药小车上;小车检测到药品装载完成后自动开始运送;小车根据走廊上的标识信息自动识别、寻径将药品送到指定病房(车头投影在门口区域内),点亮红色指示灯,等待卸载药品;病房处人工卸载药品后,小车自动熄灭红色指示灯,开始返回;小车自动返回到药房(车头投影在门口区域内,面向药房)后,点亮绿色指示灯。

2.图像部分分析

由题意可知,图像部分大致可以分为

  • 识别道路
    • 路中央红线巡线
    • 路口识别
    • 终点线黑色虚线识别
  • 识别数字
    • 开始位置识别数字
    • 路口识别两个数字
    • 路口识别四个数字

2.1识别道路

识别道路有很多方案,我们组前期错误的选择了红外循线的方案。这种方案精度低,而且会受环境影响。

后期转向OpenMV的方案。

2.2识别数字

识别数字有很多方案,比如OpenMV、K210、树莓派、Jetson nano甚至x86架构的单板计算机都可以用,但是因为前期准备的原因我们只实现了OpenMV的方案。

这里还是要说一下,OpenMV算力有限,实在是难堪重任,并不是本题的最优解法。

二、识别道路部分

1.巡线-红色实线

这里我们采用的是匿名飞控给无人机写的一套OpenMV代码,略作修改。

核心思想是在图像的上、中、下、左、右各划出一个细长条的区域,在各自区域内检测是否有指定大小的红色色块,再根据五个部分红色色块的有无即可判定是直线还是路口、是何种路口以及直线的倾角和偏移量。

如下图所示,左边只有上、中、下有小方框,是直线;右边上、中、下、左、右都有小方框,是路口。

2.终点线-黑色虚线

终点线是黑色虚线,可以视为两厘米见方的黑色小矩形,可以使用OpenMV内置的矩形检测函数检测指定大小范围的矩形,当矩形数量足够多时即视为终点线。

如下图所示,识别到六个以上矩形块即可视为终点线。

3.代码实现

import sensor, image, time, math, structfrom pyb import UARTimport jsonsensor.reset()sensor.set_pixformat(sensor.RGB565)# 要检测颜色,所以使用彩色模式sensor.set_framesize(sensor.QQVGA)# 使用QQVGA降低画质提升运行速度sensor.skip_frames(time=3000)sensor.set_auto_whitebal(False)# 颜色检测一定要关闭自动白平衡clock = time.clock()uart = UART(1, 115200)uart.init(115200, bits=8, parity=None, stop=1)# 上面是串口通信的部分Red_threshold =[(13, 40, -2, 57, 11, 47),(29, 50, 13, 79, 15, 67),(33, 50, 16, 73, 2, 61)]# 红色的LAB阈值,在赛场上需要重新进行标定,可使用OpenMV IDE自带的阈值编辑器进行标定ROIS = {	"down":   (0, 105, 160, 15),	"middle": (0, 52,  160, 15),	"up":	 (0,  0,  160, 15),	"left":   (0,  0,  15, 120),	"right":  (145,0,  15, 120),	"All":	(0,  0,  160,120),}# 划分了上中下左右五个部分class LineFlag(object):	flag = 0	cross_y = 0	delta_x = 0class EndFlag(object):	endline_type = 0	endline_y = 0LineFlag=LineFlag()EndFlag=EndFlag()# 红色实线部分函数def find_blobs_in_rois(img):	global ROIS	roi_blobs_result = {}	for roi_direct in ROIS.keys():		roi_blobs_result[roi_direct] = {			"cx": -1,			"cy": -1,			"blob_flag": False		}	for roi_direct, roi in ROIS.items():		blobs=img.find_blobs(Red_threshold, roi=roi, merge=True, pixels_area=10)		if len(blobs) == 0:			continue		largest_blob = max(blobs, key=lambda b: b.pixels())		x,y,width,height = largest_blob[:4]		if not(width >=3 and width <= 45 and height >= 3 and height <= 45):			continue		roi_blobs_result[roi_direct]["cx"] = largest_blob.cx()		roi_blobs_result[roi_direct]["cy"] = largest_blob.cy()		roi_blobs_result[roi_direct]["blob_flag"] = True			if (roi_blobs_result["down"]["blob_flag"]):		if (roi_blobs_result["left"]["blob_flag"]and roi_blobs_result["right"]["blob_flag"]):			LineFlag.flag = 2 #十字路口或丁字路口		elif (roi_blobs_result["left"]["blob_flag"]):			LineFlag.flag = 3 # 左转路口		elif (roi_blobs_result["right"]["blob_flag"]):			LineFlag.flag = 4 # 右转路口		elif (roi_blobs_result["middle"]["blob_flag"]):			LineFlag.flag = 1 #直线		else:			LineFlag.flag = 0 # 未检测到	else:		if(roi_blobs_result["middle"]["blob_flag"]and roi_blobs_result["up"]["blob_flag"]):			if (roi_blobs_result["left"]["blob_flag"]and roi_blobs_result["right"]["blob_flag"]):				LineFlag.flag = 5 # 即将跨过十字路口			elif (roi_blobs_result["left"]["blob_flag"]):				LineFlag.flag = 6 # 即将跨过左拐丁字路口			elif (roi_blobs_result["right"]["blob_flag"]):				LineFlag.flag = 7 # 即将跨过右拐丁字路口			else:				LineFlag.flag = 8 # 直线(无down块)		else:			LineFlag.flag = 0				# 下面这部分是特例的判断,防止出现	# “本来是直线道路,但是太靠近左侧或者右侧,被识别成了左转或者右转”	# 这样的特殊情况	if (LineFlag.flag == 3 and roi_blobs_result["left"]["cy"]<10):		LineFlag.flag = 1	if (LineFlag.flag == 4 and roi_blobs_result["right"]["cy"]<10):		LineFlag.flag = 1	if (LineFlag.flag == 3 and roi_blobs_result["down"]["cx"]<30):		LineFlag.flag = 1	if (LineFlag.flag == 4 and roi_blobs_result["down"]["cy"]>130):		LineFlag.flag = 1		# 计算两个输出值,路口交叉点的纵坐标和直线时红线的偏移量	LineFlag.cross_y = 0	LineFlag.delta_x = 0		if (LineFlag.flag == 1 or LineFlag.flag == 2 or LineFlag.flag == 3 or LineFlag.flag == 4) :		LineFlag.delta_x = roi_blobs_result["down"]["cx"]	elif (LineFlag.flag == 5 or LineFlag.flag == 6 or LineFlag.flag == 7 or LineFlag.flag == 8):		LineFlag.delta_x = roi_blobs_result["middle"]["cx"]	else:		LineFlag.delta_x = 0		if (LineFlag.flag == 2 or LineFlag.flag == 5):		LineFlag.cross_y = (roi_blobs_result["left"]["cy"]+roi_blobs_result["right"]["cy"])//2	elif (LineFlag.flag == 3 or LineFlag.flag == 6):		LineFlag.cross_y = roi_blobs_result["left"]["cy"]	elif (LineFlag.flag == 4 or LineFlag.flag == 7):		LineFlag.cross_y = roi_blobs_result["right"]["cy"]	else:		LineFlag.cross_y = 0	# 终点线黑色虚线部分的函数def find_endline(img):	endbox_num = 0	for r in img.find_rects(threshold = 10000):		endbox_size = r.magnitude()		endbox_w = r.w()		endbox_h = r.h()		k=1		# 筛选黑色矩形大小		if (endbox_size<24000*k*k and endbox_h<25*k and endbox_w<25*k) :			endbox_num = endbox_num + 1;		# 判断是否是终点线	EndFlag.endline_type = 0	if (endbox_num>2 and endbox_num<6):		EndFlag.endline_type = 1 # 检测到第一条终点线	elif(endbox_num >=6 ):		EndFlag.endline_type = 2 # 检测到两条终点线	else:		EndFlag.endline_type = 0 # 未检测到终点线while(True):	clock.tick()	global img	img = sensor.snapshot()	# 拍照	img = img.replace(vflip=1,hmirror=1,transpose=0)	# 因为是倒装的做上下颠倒	find_blobs_in_rois(img)	# 巡线函数	find_endline(img)	# 找终点线函数	FH = bytearray([0xc3,0xc3])	uart.write(FH)	# 发送帧头	data = bytearray([LineFlag.flag, LineFlag.delta_x, LineFlag.cross_y, EndFlag.endline_type])	uart.write(data)	# 发送内容	ED = bytearray([0xc4,0xc4])	uart.write(ED)	# 发送帧尾

4.接口定义

Line.flag

数值含义
00未检测到直线
01直线
02十字路口或丁字路口
03左转路口(顶部10像素以下)
04右转路口(顶部10像素以下)
05即将跨过十字路口(无down块)
06即将跨过左拐丁字路口(无down块)
07即将跨过右拐丁字路口(无down块)
08直线(无down块)

LineFlag.delta_x

数值含义
0~160赛道红色中心线底部的X轴水平位置,左小右大;无down块时,返回中部的middle块X轴水平位置

LineFlag.cross_y

数值含义
0~120赛道红色中心线十字路口或丁字路口交叉点的Y轴竖直位置,上小下大

EndFlag.endline_type

数值含义
00未检测到终点线
01检测到第一根终点线
02检测到第二根终点线

三、识别数字部分

1.总体思路

1.1 识别方法

由题目可知,我们要同时识别两个或四个数字,这里有很多办法,我们的办法是让相机尽可能加高、使用广角镜头以便同时能看到四个数字。

如下图所示,小车的高度刚好卡在了25cm的限高,以便同时看到四个数字。

我们再图像内划分出了五个ROI区域,依次检测,即可检测到处于五种不同位置的数字了。

1.2 模型训练

OpenMV可以跑TensorFlow Lite模型,具体如何训练可以参考下面这篇博客。

https://blog.csdn.net/qq_36300069/article/details/118071444

训练模型的网站如下

https://studio.edgeimpulse.com/

这个网站可以把模型打包好导入OpenMV中,数据集是自己拍的照片,一共八十张训练集、二十张测试集。参数上我选择的是160*160像素、灰度图、训练100步,模型选的0.35的V2模型。

数据集如下图所示,左边是训练集,右边是测试集。


训练结束后取得了不错的效果,识别准确率都在70%以上。

1.3 图像处理

仅仅是神经网络模型识别准确率还不高,我们使用了一些方法对图像进行一些处理来提高识别的成功率。

  • 镜头畸变校正
  • 缩小图像(避免画面损失)
  • 翻转图像(因为倒装)
  • 透视校正
  • 反相、红色填充黑色后再反相(去除红色影响)

如下图所示,左侧是未处理的图像,右图是处理后的图像。

总体流程如下

2.代码实现

import sensor, image, time, os, tffrom pyb import UARTimport jsonsensor.reset()sensor.set_pixformat(sensor.RGB565)sensor.set_framesize(sensor.VGA)sensor.skip_frames(time=2000)net = "trainedv13.tflite"# 透视校正用的四个点TARGET_POINTS = [(143,210),                 (495,214),                 (640,480),                 (0,480)]                 # 识别数字的五个区域ROI0 = (210,170,170,170)ROI1 = (20,0,170,170)ROI2 = (150,0,170,170)ROI3 = (285,0,170,170)ROI4 = (430,0,170,170)# 反相后红色阈值xred_threshold = (51, 84, -31, -3, -26, -2)# 各区域识别数字准确度门槛keyline_0 = 0.7keyline_1 = 0.6keyline_2 = 0.65keyline_3 = 0.65keyline_4 = 0.6ans_num = 0clock = time.clock()uart = UART(3, 115200)uart.init(115200, bits=8, parity=None, stop=1)while(True):    clock.tick()		# 拍照并进行一堆预处理    img = sensor.snapshot().lens_corr(strength = 1.7, zoom = 0.55)    img = img.replace(vflip=1,hmirror=1,transpose=0)    img = img.rotation_corr(corners = TARGET_POINTS)    img = img.negate()    img = img.binary([xred_threshold], invert=False, zero=True)    img = img.negate()	# 识别中心数字    for obj in tf.classify(net, img, roi=ROI0, min_scale=1.0, scale_mul=0.8, x_overlap=0.5, y_overlap=0.5):        out = obj.output()        max_idx = out.index(max(out))        if max(out)>keyline_0:            ans_0 = max_idx + 1        else:            ans_0 = 0			# 识别左起第一个数字    for obj in tf.classify(net, img, roi=ROI1, min_scale=1.0, scale_mul=0.8, x_overlap=0.5, y_overlap=0.5):        out = obj.output()        max_idx = out.index(max(out))        if max(out)>keyline_1:            ans_1 = max_idx + 1        else:            ans_1 = 0	# 识别左起第二个数字    for obj in tf.classify(net, img, roi=ROI2, min_scale=1.0, scale_mul=0.8, x_overlap=0.5, y_overlap=0.5):        out = obj.output()        max_idx = out.index(max(out))        if max(out)>keyline_2:            ans_2 = max_idx + 1        else:            ans_2 = 0		# 识别左起第三个数字    for obj in tf.classify(net, img, roi=ROI3, min_scale=
            
                     
             
               

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/123170.html

相关文章

  • 2021全国大学生电子设计竞赛F题参赛简记

    摘要:而深度学习的手写数字识别去年被官方下架了。。。深度学习方法改成旧版本,用自己的深度学习。神经网络训练树莓派之前做过这个,还比较有底,但是要自己标千多张数据集。 20...

    geekzhou 评论0 收藏0
  • 2021年TI杯全国大学生电子设计大赛智能送药小车F 题)【本科组】(jetson nano+yo

    摘要:二标记图像数字位置使用标记软件进行,生成就能保存数字在图像中的坐标和对象名称的标签文件三机器学习训练训练框架采用轻量的,使用电脑进行训练,将摄像头采集到的数字图像新建文件夹保存,然后将标记好数字的标签文件新建一个文件夹保存。 目录 前言 一、Opencv采集数字图像  二、标记图像数字位置 ...

    wslongchen 评论0 收藏0
  • 【毕设】基于openmv和arduino的人脸感应显示的代码

    摘要:这里写目录标题作品要求一作品设计与模块选择可以感应人脸感应到人脸时开启显示屏显示显示温度速度等实验效果作品要求一作品设计与模块选择可以感应人脸人脸识别模块选用模块,因为用起来相对复杂。设置每个人拍摄图片数量。为当前最匹配的人的编号。 ...

    Enlightenment 评论0 收藏0
  • 分享 | 撞坏遥控车后,有个技术大牛爸爸是种怎样的体验

    摘要:在我已经制作完成一辆可以运行的遥控车时,公司发布了一个自驾车项目,来展示自动驾驶汽车的工作原理。需要注意的是,这里用的都是语言而非,其主要原因有两个一方面,近来似乎已成为运用机器学习技术时实际使用的语言。 最近,Mapbox 的 Android 工程师 Antonio 使用计算机视觉和机器学习技术,为他的女儿 Violeta 重新制作了一台遥控车。接下来我们看看 Antonio 是如何...

    ConardLi 评论0 收藏0
  • 2021年亚太杯数学建模竞赛解题思路

    摘要:题目年亚太数学建模竞赛问题热光伏技术中热辐射器的优化设计近年来,世界各大国纷纷将目光投向星海,制定了各种太空探索方案。 文章目录 A题 图像边缘分析与应用题目 ...

    不知名网友 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<