目录
1.赛题及硬件方案分析:
2.用到的主要器件清单:
3.各部分思路及代码实现
(1).小车舵机、马达驱动
(2).蓝牙通信
(3).单片机与OpenMV的串口通信
(4).单片机与OpenMV的通信协议
(5).单片机main文件中的函数:
(6).巡线
(7).识别十字路口
(8).数字识别
(9).远端病房四个并排数字识别
(10).返回时倒车
4.总结感言
刚得到消息,我们队被推荐为国奖,国一国二还没定,大概率是国二,因为测评时发挥部分的小车二在转弯时因为电池原因急转弯失败,发挥部分就没有成功验收。刚收到消息就更新了一下,本方案的成本可以说是此赛题方案中成本最低的。
通过题目可以很容易的看出来,这道题有最难的两个点:巡线+数字识别。
此题的巡线又不同于以往电赛题目的黑白巡线,此题是巡红白线。这样就会带来一个问题,以往巡黑白线我们最常用的是红外传感器,但是此题中的红线和白色背景两者间的吸光能力差别很小,一般的红外传感器根本难以分辨,这就需要用到较为不常用的灰度传感器或者是颜色传感器。据我所知,开赛前就已经准备好这两种传感器的小组还是较少的,包括我们组就没有准备,只能用摄像头代替,用对应的算法进行巡线。
数字识别用到的硬件就没什么好说的了,肯定用到摄像头,我们组用的是赛前已经准备好的OpenMV,OpenMV上手还是很快的,但是需要有一定的Python基础 。在比赛过程中也有很多大佬认为OpenMV同时进行巡线和数字识别的话,其帧率会很低,我们组当然也遇到了以上问题,具体的解决方法在后面会讲到。
另外,题目中要求检测到200g药品的装载和卸载,一般的话可以用压力传感器,但为了方便我们直接用的红外对管,以检测障碍物的方式判断药品是否装载。
STM32F407ZGT6单片机
OpenMV4智能处理摄像头
舵机转向四驱车
锂电池、5V、12V降压模块
L298N电机驱动模块
红外传感器
蓝牙模块HC-05
LED指示灯
以上器件都是一辆小车用到的,如果要做发挥部分的话就需要两辆车了,不过小车2用到的器件基本不变。我们采取的方案是一辆小车用一个摄像头,我看网上有的大佬一辆小车左右两边各有一个摄像头,这样的话写代码就会较为简便,不过一般的OpenMV价格在300到500之间,除非你的经费充足且赛前已经准备好,否则还是老老实实用一个摄像头肝吧.....就像我们组,当时只有一个OpenMV,第二个还是临时找关系借的。
这一部分的代码就不贴上了,都是基本的PWM输出,可以参照正点原子的相关例程,并且两辆小车的代码都一样,只不过舵机让车轮保持直行时的PWM脉宽不同,到时候自己找到并在代码中改数字即可。
蓝牙通信的代码说白了就是串口通信,代码如下:
#include "stm32f4xx.h"#include "bluetooth.h"/*蓝牙通信:USART3,所用引脚为PC10,PC11*/void bluetooth_U3_Init(){GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); //使能GPIOA时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); //使能USART2时钟//串口1对应引脚复用映射GPIO_PinAFConfig(GPIOC, GPIO_PinSource10, GPIO_AF_USART3); //GPIOA9复用为USART1GPIO_PinAFConfig(GPIOC, GPIO_PinSource11, GPIO_AF_USART3); //GPIOA10复用为USART1//USART1端口配置GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11; //GPIOA9与GPIOA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //复用功能GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN; //上拉GPIO_Init(GPIOC, &GPIO_InitStructure); //初始化PA9,PA10//USART1 初始化设置USART_InitStructure.USART_BaudRate = 9600;//波特率设置USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式USART_Init(USART3, &USART_InitStructure);//初始化串口1USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); //开启相关中断//Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn; //串口1中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器、USART_Cmd(USART3, ENABLE); //使能串口1}/* 蓝牙使能引脚:PA9 */void bluetooth_IO_Init(){GPIO_InitTypeDef GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE); //??GPIOA??GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //??50MHzGPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //??????GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //??GPIO_Init(GPIOC, &GPIO_InitStructure); //???PA9,PA10}
注意:以上代码中蓝牙使能引脚PA9可以不进行初始化,这样就会一直使能,如果要进行初始化的话需要设置为开漏输出 。
此代码与蓝牙通信代码一样,都是串口通信,只不过我用的是USART5,所用引脚为PC12,PD2
因为OpenMV只能通过串口传输字符(除非使用OpenMV的数据流打包,但是我们传输的一般一次只有一个字符,所以不用数据流打包),比如,你要把 52 这个数传给单片机,OpenMV会自动把 52 这个数转换为两个字符'5'和‘2’传输,所以我们要在单片机和OpenMV之间定义一个统一的通信协议,代码如下(此代码中OpenMV传输的整数的范围在-99到99之间):
/* openmv发送过来的字符串转换为数据:帧头为' ',帧尾为'.',转换后只保留整数,中间用小数点判断,函数返回值为0则数据格式出错*/signed char CharCon(signed char c[],signed char x){int i = 0;int j = 0;while(c[i] != '.') {c[i] = c[i] - '0';i++;}j = i;if(c[0] == ('-'-'0')){i = 4;c[0] = 0;}switch(i){case 1:x = c[i-1];break;case 2:x = c[i-1] + 10*c[i-2];break;case 3:x = -(c[i-1] + 10*c[i-2]);break;case 4:x = -(c[j-1] + 10*c[j-2]);break;default:return 0;break;} return x;}
OpenMV发送的数据格式:
uart.write(" ")uart.write(str(int(deflection_angle)))uart.write(".")
main函数中需要用到状态机的编程逻辑,在单片机收到OpenMV发送的数据时需要判断接收到的是数据帧,还是命令,比如收到的是'f',则接收到的是笔直向前的命令。
// PB10是红外对管的输入引脚#include "stm32f4xx.h"#include "sys.h"#include "delay.h"#include "duoji.h"#include "dianji.h"#include "bluetooth.h"#include "to_openmv.h"#include "hongwai.h"#include "usart.h"#define Center 88 //小车前轮保持居中不偏时单片机输出的PWN脉宽#define Hongwai_IO PBin(10) //装载完药品,hongwai_IO = 0#define Red PAout(14)#define Green PEout(5)#define White PAout(13)//以下定义的是状态机的各种状态,在最终的代码中有些并没有用到#define Start 0 //初始的等待状态#define Turn_left 1#define Turn_right 2#define Turn_no 3#define Stop 4#define Wait 5#define Slower 6#define Faster 7#define Forward 8#define Backward 9#define Back 10#define Change 11#define Stoping 12#define Changezuo 13#define Changeyou 14#define Fuwei 15#define Finish 16#define Nfuwei 17#define Zanwait 18int t = 0;int speed = 200; //马达速度signed char angle = 0; //巡线时舵机转的角度signed char change_angle = 30; //路口停下来识别数字时微调的角度uint16_t bluetooth = 0; //接收到的蓝牙消息uint16_t temp = 0;char state = 0; //状态机中的当前状态char flag = 0; //是否已经识别到要去的病房号signed char rec_flag = 0; //单片机与OpenMV通信中的命令标志位signed char openmv_rec[4] = {0}; //OpenMV发送的数据signed char i = 0;signed char task = 0; //当前执行的是任务几/* 指示灯引脚初始化 */void LED_Init(){ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); GPIO_InitTypeDef GPIO_Instruct; GPIO_Instruct.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14; GPIO_Instruct.GPIO_Mode = GPIO_Mode_OUT; GPIO_Instruct.GPIO_Speed = GPIO_Speed_100MHz; GPIO_Instruct.GPIO_OType = GPIO_OType_PP; GPIO_Instruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_Instruct); GPIO_Instruct.GPIO_Pin = GPIO_Pin_5; GPIO_Init(GPIOE, &GPIO_Instruct);}void All_Init(){ LED_Init(); hongwai_IO_Init(); dianji_IO_Init(); dianji_PWM_Init(1000 - 1, 84 - 1); duoji_PWM_Init(2000 - 1, 840 - 1); //0 bluetooth_U3_Init(); //bluetooth_IO_Init(); openmv_U5_Init(); // computer_U1_Init(); uart_init(9600); delay_init(168);}void turn_r(int t){ TIM_SetCompare1(TIM3, 88); PFout(5) = 0; PFout(7) = 1; PCout(4) = 1; PCout(5) = 0; TIM_SetCompare1(TIM5, t); TIM_SetCompare1(TIM13, t); delay_ms(3000);}int main(void){ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); All_Init(); while (1) { switch (state) { case Start: turn(0); while (flag == 0); //已经识别到病房号 Green = 0; White = 1; delay_ms(1000); White = 0; while (flag == 1) { if (Hongwai_IO == 0) { delay_ms(1000); if (Hongwai_IO == 0) { state = Forward; break; } } } break; case Forward: turn(angle); zhengzhuan(speed); break; case Turn_right: zhengzhuan(speed); turn_right(); delay_ms(400); break; //假设急转弯1.5S已经转过去,具体参数需要调试 case Turn_left: turn_left(); zhengzhuan(200); delay_ms(400); break; //假设急转弯1.5S已经转过去,具体参数需要调试 case Stop: stop(); delay_ms(20); fanzhuan(speed); delay_ms(300); stop(); turn(0); state = Stoping; break; case Wait: stop(); turn(0); Red = 1; if (Hongwai_IO == 1) { delay_ms(500); if (Hongwai_IO == 1) { USART_SendData(UART5, 's'); Red = 0; state = Back; flag = 0; } } break; case Zanwait: stop(); turn(0); Red = 1; if (Hongwai_IO == 1) { delay_ms(500); if (Hongwai_IO == 1) { USART_SendData(USART3, 'o'); delay_ms(10); USART_SendData(UART5, 's'); Red = 0; state = Back; flag = 0; } } break; case Back: fanzhuan(speed - 50); // turn(-angle); delay_ms(400); state = Backward; USART_SendData(UART5, 's'); break; case Backward: turn(3 - angle); fanzhuan(speed - 50); break; case Change: turn(angle); zhengzhuan(speed); state = Stop; delay_ms(500); break; case Changezuo: fanzhuan(speed); delay_ms(400); stop(); turn(30); zhengzhuan(speed); change_angle = 30; delay_ms(500); state = Stoping; USART_SendData(UART5, 's'); break; case Changeyou: fanzhuan(speed); delay_ms(400); stop(); turn(-30); zhengzhuan(speed); change_angle = -30; delay_ms(500); state = Stoping; USART_SendData(UART5, 's'); break; case Fuwei: turn(change_angle); fanzhuan(speed); delay_ms(500); stop(); turn(0); zhengzhuan(speed); delay_ms(400); stop(); break; case Nfuwei: turn(change_angle); fanzhuan(speed); delay_ms(500); stop(); turn(0); break; case Stoping: stop(); turn(0); break; case Finish: stop(); turn(0); Green = 1; flag = 0; state = Start; break; default: break; } }}/* 蓝牙接收 */void USART3_IRQHandler(){ if (USART_GetITStatus(USART3, USART_IT_RXNE)) { bluetooth = USART_ReceiveData(USART3); } USART_ClearITPendingBit(USART3, USART_IT_RXNE);}/* openmv接收 */void UART5_IRQHandler(){ if (USART_GetITStatus(UART5, USART_IT_RXNE)) { char j = 0; signed char temp = USART_ReceiveData(UART5); if (temp == ' ') { rec_flag = 1; i = 0; } else if (rec_flag == 1) { if (temp == '.') { openmv_rec[i] = '.'; angle = CharCon(openmv_rec, angle); rec_flag = 0; for (j = 0; j < 4; j++) openmv_rec[j] = 0; } else { openmv_rec[i] = temp; i++; } } else //rec_flag == 0,接收的为指令 { switch (temp) { case 's': state = Stop; break; case 'l': state = Turn_left; break; case 'r': state = Turn_right; break; case 'f': state = Forward; break; case 'z': state = Wait; break; case 'b': state = Backward; break; case 'd': state = Finish; break; case 'e': state = Zanwait; break; case 'o': flag = 1; break; case 'c': state = Change; break; case 'x': state = Changezuo; break; case 'y': state = Changeyou; break; case 'w': state = Fuwei; break; case 'n': state = Nfuwei; break; case 't': state = Stoping; break; default: break; } } } USART_ClearITPendingBit(UART5, USART_IT_RXNE);}
一下部分是OpenMV部分的代码,包括巡线和数字识别的算法,以及赛题各任务的判断逻辑
思路:将摄像头一帧图片的上半部分划分为三个平行的感兴趣区,在三个感兴趣区中分别寻找最大的红色色块,获得三个中心坐标,然后给予其不同的权重后计算平均质心坐标,用此质心坐标计算得到巡线时的偏转角度。
为什么只将一帧图片的上半部分?因为下半部分容易受到小车的阻挡或者阴影干扰
RED_THRESHOLD = [(200,10,10),(255,50,50)]#色块阈值BLACK_THRESHOLD = [(0, 48, -58, 49, -36, -1)]ROIS = [(0, 15, 160, 25, 0.9), (0, 40, 160, 25, 0.6), (0, 65, 160, 30, 0.6)]#--------------------------------------- 判断直线角度 ------------------------------------------------ centroid_sum = 0 dx = 0 for r in ROIS: blobs = img.find_blobs(RED_THRESHOLD, roi=r[0:4], merge=True) # r[0:4] is roi tuple. if blobs: # Find the blob with the most pixels. largest_blob = max(blobs, key=lambda b: b.pixels()) # 返回最大值像素点 #img.draw_rectangle(largest_blob.rect()) # 将此区域的像素数最大的颜色块画矩形和十字形标记出来 #img.draw_cross(largest_blob.cx(),largest_blob.cy()) #print(largest_blob.cx()) centroid_sum += (80-largest_blob.cx())*r[4] # r[4] is the roi weight. weight_sum = weight_sum + r[4] #计算centroid_sum,centroid_sum等于每个区域的最大颜色块的中心点的x坐标值乘本区域的权值 if weight_sum: center_pos = (centroid_sum / weight_sum) weight_sum = 0#--------------------------------------- 什么也识别不到则认为是到达了虚线 --------------------------------#----------------------------------------- 求解直线角度----------------------------------------------- deflection_angle = 0 ns = center_pos/60 # QQVGA 160x120. deflection_angle = -math.atan(ns) deflection_angle = math.degrees(deflection_angle) deflection_angle = 0 - int(deflection_angle) #print(str(deflection_angle))#------------------------------------------ 直行 ---------------------------------------------------- uart.write(" ") uart.write(str(int(deflection_angle))) uart.write(".")
我们需要识别十字路口,然后停车进行数字识别或者进行其他操作。思路:感兴趣区域取左上角和右上角,当两个区域同时检测到红色色块时,就表示识别到了十字路口。
ROI = [(10,0,40,50),(110,0,50,50)] blobs1 = img.find_blobs(RED_THRESHOLD, roi=ROI[0],area_threshold=150, merge=True) if blobs1: #img.draw_rectangle(blobs1.arect()) blobs2 = img.find_blobs(RED_THRESHOLD, roi=ROI[1], area_threshold=150,merge=True) if blobs2:#表示已经识别到十字路口 #img.draw_rectangle(blobs2.rect()) print("+")
一开始我们组用的是特征点检测的方法,写好代码以后发现效果并不好,不仅速度慢而且识别准确率很低,后来就改成了模板匹配的方法,这种方法唯一的缺点就是要想识别准确率高就需要截取较多的模板,不仅有多种大小,还要有多种角度,我们小组最终截取了将近200张模板,如果逐一进行匹配的话速度会非常的慢,但是根据任务要求我们可以灵活匹配,比如基础任务一我们只需要匹配数字一和二的模板,其他任务只有在一开始识别病房号时需要从3到8逐一匹配,但识别到病房号以后,在路口进行数字识别时只需要对病房号的模板进行匹配并判断其在小车的左侧还是右侧,以此来判断应该做转还是右转。核心代码如下:
#------------------------------------ 初始时刻识别病房号 ---------------------------------------------- while(wait == 0): if(task == 0): img = sensor.snapshot().lens_corr(strength = 1.6, zoom = 1.0) img=img.to_grayscale() #histogram = img.get_histogram() #Thresholds = histogram.get_threshold() #v = Thresholds.value() img.binary([(0,v)]) for t in templates12: template = image.Image(t) #对每个模板遍历进行模板匹配 r = img.find_template(template, 0.65,step=4, search=SEARCH_EX) if r: #img.draw_rectangle(r, color=255) print(t[10]) #打印模板名字 number=int(t[10]) uart.write("o") wait = 1 break else:#初始时刻任务二三识别病房号 img = sensor.snapshot().lens_corr(strength = 1.6, zoom = 1.0) img=img.to_grayscale() img.binary([(0,v)]) for t in templates3: template = image.Image(t) r = img.find_template(template, 0.65,step=4, search=SEARCH_EX) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 for t in templates4: template = image.Image(t) r = img.find_template(template, 0.55,step=4, search=SEARCH_EX) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 for t in templates5: template = image.Image(t) r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 for t in templates6: template = image.Image(t) r = img.find_template(template, 0.61,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 for t in templates7: template = image.Image(t) r = img.find_template(template, 0.65,step=4, search=SEARCH_EX) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 for t in templates8: template = image.Image(t) r = img.find_template(template, 0.65,step=4, search=SEARCH_EX) if r: shuzi=int(t[10]) array1[shuzi-1] =array1[shuzi-1]+1 if (max(array1) > 1): m = array1[0] for index in range(0,8): if array1[index] > m: m = array1[index] number = index + 1 print(str(number)) uart.write("o") wait = 1 num = task if(num >2): num = num - 2 continue else:#没识别出来则重新循环 print("no") continue#--------------------------------------- 十字路口前识别数字-------------------------------------------- while(wait == 2): if((task == 2)and(last != 3)): if(n1<2): for loop in range(0,3): img = sensor.snapshot() img.lens_corr(1.6) img=img.to_grayscale() img.binary([(0,v)]) if number==3: for t in template3: template = image.Image(t) r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: #img.draw_rectangle(r,color = 255) print(str(number)) n1=n1+1 if r[0]<60: zuo=zuo+1 else: you=you+1 if number==5: for t in template5: template = image.Image(t) r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: #img.draw_rectangle(r,color = 255) print(str(number)) n1=n1+1 if r[0]<60: zuo=zuo+1 else: you=you+1 if number==4: for t in template4: template = image.Image(t) r = img.find_template(template, 0.55,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: img.draw_rectangle(r,color = 255) print(str(number)) n1=n1+1 if r[0]<60: zuo=zuo+1 else: you=you+1 if number==6: for t in template6: template = image.Image(t) r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: img.draw_rectangle(r,color = 255) print(str(number)) n1=n1+1 if r[0]<60: zuo=zuo+1 else: you=you+1 if number==7: for t in template7: template = image.Image(t) r = img.find_template(template, 0.65,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: img.draw_rectangle(r,color = 255) print(str(number)) n1=n1+1 if r[0]<60: zuo=zuo+1 else: you=you+1 if number==8: for t in template8: template = image.Image(t) r = img.find_template(template, 0.60,step=4, search=SEARCH_EX) #, roi=(10, 0, 60, 60)) if r: img.draw_rectangle(r,color = 255) print(t) print(str(number)) n1=n1+1 if r[0]<60: zuo=zuo+1 else: you=you+1
以上代码中为了防止出现个别识别错误的情况出现,我们最少识别三帧图片并统计出现次数最多的数字。
用一个摄像头的话很难做到一下子把四个并排的数字都完整的拍下来,这样大多数人的思路就是先让小车识别中间的两个数字,识别不到再让其左右偏转角度识别最左/最右侧的数字,但是这种思路的话跑完单程绝对会超过20s的时间,超时就会扣分。我们为了解决这个问题最后采用的是排除法,先直接让小车向左偏转识别左边的两个数字,如果没有识别到对应的病房号的话就认为病房在右边,直接让小车向右转,这种方法比较依赖模板匹配的准确度,推荐现场截取模板。核心代码如下:
#--------------------------------------- 任务三识别不到数字时右转 --------------------------------------- if(n1<1):#识别三次后还不能识别出真正地数字 print('右') #time.sleep_ms(200) uart.write("w")#小车姿态复位 time.sleep_ms(400) uart.write("f") time.sleep_ms(200) uart.write("r") time.sleep_ms(1000) wait = 1 last = 3#last = 3证明已经经过了四字识别 n1 = 0 n = 0 zuo = 0 you = 0 no = 0 break#-------------------------------------- 任务三识别到了数字就左转 ---------------------------------------- if(last != 3): print('左') #uart.write("t") #time.sleep_ms(100) #uart.write("n") #time.sleep_ms(500) uart.write("f") time.sleep_ms(300) uart.write("l") time.sleep_ms(1100)#任务二可能需要的更少 wait = 1 last = 3 zuo = 0 you = 0 n = 0 n1 = 0 no = 0 break
因为我们用的是舵机转向四驱车,体型较大、转弯半径较大,不能像差速万向三轮车一样做到原地掉头,所以在返程时我们会先进行倒车操作。在进行倒车时用上述色块巡线的话会收到十字路口的横向色块干扰,所以在倒车时我们用寻找线段的方法得到偏转角度,并且为了消除十字路口的干扰,我们只计算角度在135—180度和0—45度之间的线段。
#------------------------------------------ 倒车巡线 ------------------------------------------------ if (done == 1): if(now == 0): #bls = img.find_blobs(RED_THRESHOLD, (30,20,60,20), area_threshold=100,merge=True) # r[0:4] is roi tuple. #if bls: #for b in bls: #dx =0 #break for l in img.find_lines(threshold = 1000, x_stride=2,theta_margin = 25, rho_margin = 25): if(l.theta()>135): deflection_angle = 180 - l.theta() uart.write("b") uart.write(" ") uart.write(str(int(deflection_angle))) uart.write(".") continue if(l.theta()<45): deflection_angle = 0 - l.theta() uart.write("b") uart.write(" ") uart.write(str(int(deflection_angle))) uart.write(".") continue continue else: if((task == 2)and(now == 0)): bls = img.find_blobs(RED_THRESHOLD, (30,20,60,20), area_threshold=100,merge=True) # r[0:4] is roi tuple. if bls: for b in bls: dx = 0 break for l in img.find_lines(threshold = 1000, x_stride=2,theta_margin = 25, rho_margin = 25): if(l.theta()>135): deflection_angle = 1.3*(180 - l.theta()) uart.write("b") uart.write(" ") uart.write(str(int(deflection_angle))) uart.write(".") continue if(l.theta()<45): deflection_angle = 1.3*(0 - l.theta()) uart.write("b") uart.write(" ") uart.write(str(int(deflection_angle))) uart.write(".") continue continue else: uart.write("f") else: uart.write("f")
以上就是各个主要部分的核心代码了,完全的OpenMV工程代码我写了1500行+,主要是不同任务进行的操作并不一样,比如对于第二个十字路口,任务二需要停车识别数字,而对于任务三则需要直行,这就需要有一些状态变量进行记录,因此在代码中写的最多的也是if.....else...嵌套逻辑判断。
紧张刺激的电赛还是过去了,期间熬夜通宵是不可避免的,不得不说今年的F题确实较难,很多省份只把基础题全做出来就可以获国奖,我们山东赛区的成绩暂时没出来,我们组把基础部分和发挥部分一都做了出来,但最后测评的时候发挥部分一因为小车二速度太慢,导致急转弯的时候直接被卡在了原地,很遗憾的没有验收发挥部分一。
一开始我们小组准备的是往年滚球控制这种纯测控题,但很遗憾今年并没有出,我们就只能选择F题,因为事先并没有准备车模,所以也是临时向老师借的车模,因为没有灰度传感器巡线就只能用摄像头同时巡线和数字识别,后来小车2的摄像头也是临时向学弟借的。总的来说,这次电赛还是准备不够充分,从八月电赛延期一直到十一月电赛开赛的几个月的时间,我们小组三个人有两人在准备保研面试,另一人也在全力进行考研备考,从器件和时间两方面都准备不足,导致赛初我们的进度几乎可以说是停滞不前,后来还是靠大家的全力应对以及各种熬夜通宵才取得了不错的成果。
因为第一次写博客,排版还不熟练,语言叙述得也不够详尽,代码模块也不知道怎么改成VScode那种高亮的模式,以后会坚持继续写,慢慢改进。
文章中有的要点可能没有涉及到,如果有小伙伴遇到了什么问题,可以在评论区留言,到时候我会汇总,可能会再写一篇博客作为补充。
PS:出一个闲置的opnmv,全新只用来做过这个小车,最近在忙毕设,有空可以接比赛指导,画板子,写程序等(有偿且不贵),程序的话需要十元劳动费,购买后我们就是朋友,提供讲解。