AI视觉智能送药小车——1.复盘及核心代码

Posted weixin_45845008

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AI视觉智能送药小车——1.复盘及核心代码相关的知识,希望对你有一定的参考价值。

回顾

本在大二下的电设延延期了,有幸与车队队友半途加入,笔者之前没做过摄像头与视觉的代码(之前做的AI电磁),虽然最后有各种各样遗憾,但也临时猛学收获许多新知识。测评前顺利做完了基础和发挥(1),可惜封箱后换了场地识别出了问题。
做的途中出现过以下问题:

  • 数字移动过程中识别帧率过低
  • 识别过于依赖光线亮暗
  • 琐碎的失误:赛前未全面准备(摄像头AI、软件队友应合作提前搭好代码框架)、组队分工不当(视觉与控制没分工写、两车没一起调[框架没提前搭])、测评时拆箱后未检查装置(封箱后可能压到摄像头改变角度)

思路及代码

主控英飞凌TC377,摄像头Openmv循迹,Openart识别

  • 循迹代码
    基本思路:调红色阈值,用直线回归拟合二值化后的赛道中线line,其偏移距离rho与偏移角度theta作为err;用二值化后图像中的亮度的平均值light来判别是否为分岔元素。(之前没写过openmv,判别分岔的方案不是很好,需要摄像头角度固定且阈值在各光线下较好,比完赛后想到新的思路是设置感兴趣区在左右两侧,当居中两侧有较多亮点时可判别为分岔元素
	import sensor, image, time, math
	from image import SEARCH_EX, SEARCH_DS
	from pyb import UART
	uart = UART(3, 115200)
	'变量预定义'
	global cnt_loss
	global track
	cnt_loss=0
	track=0
	THRESHOLD = (14, 65, -128, 127, 22, 127)
	def err_revise():
	    rho_err = int(abs(line.rho())-img.width()/2)
	    if line.theta()>90:
	       theta_err = line.theta()-180
	    else:
	       theta_err = line.theta()
	    img.draw_line(line.line(), color = (0,0,255))
	    theta_err=-theta_err
	    err = theta_err + rho_err
	    return err
	def track_judge():
	    global track
	    global cnt_loss
	    light = img.statistics().l_mean()
	    print(light)
	    'track:2.岔路口,1.普通红线,0.红线丢失'
	    if (light>=23):
	        track=2
	    elif light<23 and light>3:
	        track=1
	    else:
	        track=0
	
	'main部分'
	sensor.reset()
	sensor.set_vflip(True)
	sensor.set_hmirror(True)
	sensor.set_pixformat(sensor.RGB565)
	sensor.set_framesize(sensor.QQQVGA)
	sensor.skip_frames(time = 2000)
	clock = time.clock()
	while(True):
	    clock.tick()
	    img = sensor.snapshot().binary([THRESHOLD])
	    line = img.get_regression([(100,100)], robust = True)
	    track_judge()
	    if (line):
	        err=err_revise()
	        if  track==2:
	            uart.write( str(err)+'T' )
	            print('T cro')
	        elif track==1:
	            uart.write( str(err)+'F' )
	            print('F cro')
	        else:
	            uart.write( str(err)+'L' )
	            print('loss')
	        print(str(err)+'\\n')
	        time.sleep_ms(30)

  • 数字识别
    基本思路:参考的是逐飞官方的开源例程,在图像中找黑色边框,将框中图像输入到模型识别。第一次识别的数字通过串口发送给MCU,之后再识别到数字则根据黑框中心点位置位于左右侧来发送左右转信息给MCU。
    模型训练:基本按着逐飞官方的教程来,当时Openart到已经是第二天下午,时间很紧,好在之前做过电磁的AI,官方训练的环境用anconda配置过,流程也走过。但一下午结果训练出来效果识别准确率很差,后来用大佬的训练集自己再图像增强(就增加一些其他亮度的图片)后,准确率提升了不少,但当时感觉还是特别受环境亮度影响(后来仔细想其实主要是帧率太低,应该让车在路口停下来识别,静态识别的准确率最后测评时其实有90%+)。时间来到第二天晚,识别很不稳定,而且还没和控制结合,赶工搭了控制框架能实现1和2的入病房后,2点多队友建议回去睡觉,现在想想不知是对是错了。(车赛时的模型是自己tensorflow训练后移植,说实话电赛当时也想过自己训练个部署,但没做过openart上移植模型,自己移植模型怕时间太赶,就照着官方做,其实用openmv的tf库即可,tf.load可以加载tensorflowlite模型,只需训练模型后保存为tflite模型即可
'''数字识别Openart代码'''
	import pyb
	import sensor, image, time, math
	import os, nncu
	from machine import UART
	uart = UART(1, baudrate=115200)
	
	sensor.reset()
	sensor.set_pixformat(sensor.RGB565)
	sensor.set_framesize(sensor.QVGA) # we run out of memory if the resolution is much bigger...
	sensor.set_brightness(300) # 设置图像亮度 越大越亮
	sensor.skip_frames(time = 2000)
	sensor.set_auto_gain(False)  # must turn this off to prevent image washout...
	sensor.set_auto_whitebal(False,(0,0x80,0))  # must turn this off to prevent image washout...
	clock = time.clock()
	
	
	net_path = "_1s_model_17_1.0000_xxxx.nncu"                          # 定义模型的路径
	labels = [line.rstrip() for line in open("/sd/labels_number.txt")]  # 加载标签
	net = nncu.load(net_path, load_to_fb=True)                          # 加载模型
	
	first_num=0
	is_send_num=0
	
	while(True):
	    img = sensor.snapshot()
	    print("********** Start Detections **********")
	    for r in img.find_rects(threshold = 30000):             # 在图像中搜索矩形
	        img.draw_rectangle(r.rect(), color = (255, 0, 0))   # 绘制矩形外框,便于在IDE上查看识别到的矩形位置
	        img1 = img.copy(r.rect())                           # 拷贝矩形框内的图像
	        print(r.rect()[3])
	        if r.rect()[1]>10 and r.rect()[1]<140 and r.rect()[3]>65 and r.rect()[3]<90:
	            for obj in nncu.classify(net , img1, min_scale=1.0, scale_mul=0.5, x_overlap=0.0, y_overlap=0.0):
	                sorted_list = sorted(zip(labels, obj.output()), key = lambda x: x[1], reverse = True)
	                # 打印准确率最高的结果
	                for i in range(1):
	                    if first_num==0:
	                        first_num=sorted_list[i][0]
	                    ###发送数字,为避免与Openmv发送err混淆,Openart发送数字对应的字母#######
	                    if str(first_num)=='1' and is_send_num==0:
	                        uart.write('a')
	                        print('1')
	                        is_send_num=1
	                    elif str(first_num)=='2' and is_send_num==0:
	                        uart.write('b')
	                        print('2')
	                        is_send_num=1
	                    elif str(first_num)=='3' and is_send_num==0:
	                        uart.write('c')
	                        print('3')
	                        is_send_num=1
	                    elif str(first_num)=='4' and is_send_num==0:
	                        uart.write('d')
	                        print('4')
	                        is_send_num=1
	                    elif str(first_num)=='5' and is_send_num==0:
	                        uart.write('e')
	                        print('5')
	                        is_send_num=1
	                    elif str(first_num)=='6' and is_send_num==0:
	                        uart.write('f')
	                        print('6')
	                        is_send_num=1
	                    elif str(first_num)=='7' and is_send_num==0:
	                        uart.write('g')
	                        print('7')
	                        is_send_num=1
	                    elif str(first_num)=='8' and is_send_num==0:
	                        uart.write('h')
	                        print('8')
	                        sensor.set_brightness(400)
	
	                        is_send_num=1
	
	                    ##################
	                    print('sco'+str(first_num))
	                    if sorted_list[i][0]==first_num:
	                    	'识别到需求数字,判断位于左/右侧'
	                        if (r.rect()[0]<120):
	                            uart.write('l')
	                            print(str(first_num)+' on l')
	                        else:
	                            uart.write('r')
	                            print(str(first_num)+' on r')
	
	                    img.draw_string(r.rect()[0] + 20, r.rect()[1]-20, sorted_list[0][0],color = (0,0,255), scale = 2,mono_space=False)

  • 控制代码
    基本思路
    1.药品状态标志(变量名flag.drug)
标志状态
0初始未放药
1放上药品后
2病房处停下(丢红线)等待取药
3取下药品掉头的过程
4掉头后循线到达最后一个路口前
5转完最后一个路口的返回药房
  1. 对应标志的控制
标志控制
0停车
1发车并PID循线,遇到路口时获取Openart的信息来控制左右转
2停车
3原地旋转180度
4PID循线,遇路口时按来时的方向反向转弯
5加速并按PID循线直到丢线停车
  1. 发挥1部分

    车2理论上不需要识别,只需要记录车1的转弯来对应串口发送指令控制车2路线即可。

    核心代码:

1.通信部分以及控制指令

	#include "headfile.h"
	struct OPENMV  mv,art;
	char * p=mv.str;
	int i=0;
/*
函数:openmv_get_str()
变量说明:
UART1-与openmv通信
UART0-与openart通信
UART2-与车2通信
@mv.data:每次串口中断接收到的字符
@mv.str:将一次完整的数据储存到字符串,T,F,L为一次发送结尾标志,并分别对应T-路口,F-不是路口,L-丢线
@mv.err:将字符串偏差转化为浮点型的偏差
@mv.track:对应T,L,F,2-是路口,0-丢线,1-普通赛道
*/	
	void openmv_get_str()
	
	    if (uart_query(UART_1,&mv.data))
	    
	        if (mv.data!='T' && mv.data!='F' && mv.data!='L' )
	        
	            mv.str[i]=mv.data;
	            i++;
	        
	        else if (mv.data=='T')
	        
	            mv.str[i]='\\0';
	            i=0;
	            mv.err = atof(mv.str);
	            memset(mv.str,0,sizeof(mv.str));
	            mv.track=2;
	        
	        else if (mv.data=='F')
	        
	            mv.str[i]='\\0';
	            i=0;
	            mv.err = atof(mv.str);
	            memset(mv.str,0,sizeof(mv.str));
	            mv.track=1;
	        
	        else if (mv.data=='L')
	        
	            mv.str[i]='\\0';
	            i=0;
	            mv.err = atof(mv.str);
	            memset(mv.str,0,sizeof(mv.str));
	            if (!art.con_left && !art.con_right) mv.track=0;
	        
	
	
	    
	
/*
控制指令函数——方向控制
函数:direc_control()
变量说明:
@art.con_left/right
状态1:(0秒~cnt.turn_sec秒)期间按固定占空比转弯
状态2:(cnt.turn_sec秒~cnt.turn_sec*2秒)期间按PID自然回线且不判断丢线(避免判断为到达病房)
@cnt.left/right:左转计时变量,1次中断5ms。
@cnt.turn_time:转弯固定差速持续的时间以及转弯后一段不判断丢线的时间。
@cnt.turn_num:转弯的次数(即遇到的路口数)
@flag.drug:药品的状态
@flag.str:是否发车
@flag.is_judge:发挥1的相关变量,即告诉车2已识别中端病房路口
@flag.send_direc:发挥1的相关变量,即告诉车2要转弯的方向
@flag.turn_memory[]:记下来时的方向,回去遇到路口时则从数组末端开始来控制方向
*/
	void direc_control()
	
	    if (art.con_left)
	    
	        cnt.left++;
	        if (cnt.left>cnt.turn_time*1000/5)
	        
	
	            art.con_left=2;
	            if (cnt.left>cnt.turn_time*2000/5)
	            
	                art.con_left=0;
	                cnt.left=0;
	                if (flag.drug<4&&flag.str)
	                
	                    if (!flag.is_judge)
	                    
	                        flag.is_judge=1;
	                        flag.send_direc='Z';
	                    
	                    flag.turn_memory[cnt.turn_num]=1;
	                    cnt.turn_num++;
	
	                
	                else if(flag.drug>3)
	                
	                    cnt.turn_num--;
	                
	            
	
	
	        
	    
	    else if (art.con_right)
	    
	        cnt.right++;
	        if (cnt.right>cnt.turn_second*1000/5)
	        
	        	
	            art.con_right=2;
	            if (cnt.right>cnt.turn_second*2000/5)
	            
	          		/**该if中代码某次控制中只执行一次(最后一次5ms中断进入)**/
	                art.con_right=0;
	                cnt.right=0;
	                if (flag.drug<4&&flag.str)
	                
	                    /*发挥1部分*/
	                    if (!flag.is_judge)
	                    
	                        flag.is_judge=1;
	                        flag.send_direc='X';
	                    
	                    /**********/
	                    flag.turn_memory[cnt.turn_num]=2;
	                    cnt.turn_num++;
	
	                
	                else if(flag.drug>3)
	                
	                    cnt.turn_num--;
	                
	            
	
	        
	    
	
	
/*
控制指令函数——赛道判别
函数:track_control()
变量说明:
@art.con_left/right:上面有介绍
@art.num_judge:识别到的数字
@flag.turn_memory[]:记下来时的方向,回去遇到路口时则从数组末端开始来控制方向
思路:分为三大种情况,1.如果识别的是1和2,则特殊处理。
*/
	void track_control()
	
	    if (mv.track==2 &&flag.drug!=5)
	    
	    	/*识别到1或者2*/
	        if (art.num_judge==1 || art.num_judge==2)
	        
	            if (art.num_judge==1 && flag.drug!=4 && flag.drug!=5)
	                art.con_left=1;
	            else if (art.num_judge==1 && flag.drug==4)
	            
	                art.con_right=1;
	                flag.drug=5;
	            
	
	            else if (art.num_judge==2 && flag.drug!=4 && flag.drug!=5)
	                art.con_right=1;
	            else if (art.num_judge==2 && flag.drug==4)
	            
	                art.con_left=1;
	                flag.drug=5;
	            
	        
	        /****************************/

			/**识别到其他数字*****/
			else
			
				/**如果是第一个路口(无两路口则直接下一个判断)**/
		        else if(flag.drug==4 && cnt.turn_num == 2)
		        
		
		            if(flag.turn_memor

以上是关于AI视觉智能送药小车——1.复盘及核心代码的主要内容,如果未能解决你的问题,请参考以下文章

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

智能送药小车(F 题)--2021 年全国大学生电子设计竞赛

智能车竞赛技术报告 | 智能车视觉 - 山东大学(威海) - 山魂五队

智能车竞赛技术报告 | 智能车视觉 - 中国矿业大学 - 会飞的车

智能车竞赛技术报告 | 智能车视觉 -重庆大学 - 风林火山

ai技术包括哪些技术