系列文章目录

STM32智能送药小车(三):0.96寸7针OLED的配置与编程_ssfight1的博客-CSDN博客

STM32智能送药小车(二):搭建stm32cpp环境_ssfight1的博客-CSDN博客

STM32智能送药小车(一):STM32cubemx的配置_ssfight1的博客-CSDN博客


目录

系列文章目录

前言

一、Openmv实现数字识别

1.这里简单讲一下我们使用模板匹配的思路:

2.我们利用特征点检测的思路:

二、巡线

1.基本巡线

2.识别十字

三、串口通信

1.数据打包

2.Openmv及STM32的串口通信

总结



前言

我们使用Openmv中的特征点检测进行数字识别,并通过设置红色阈值实现巡线,最后通过串口通信将数字及红线坐标发送给主控芯片。


一、Openmv实现数字识别

对于数字识别,一开始我们使用的是模板匹配,后来发现实现过程较为繁琐,且准确率不如特征点检测,故最终采用特征点检测的方法识别

1.这里简单讲一下我们使用模板匹配的思路:

我们首先采用方框检测,将数字卡片的外边框找到,再对外边框内的图像进行模板匹配,这样做的准确度确实是要比采用整张图像要好得多,但这种方法也有一个非常大的弊端,由于一些外界的干扰,方框大小很容易小于我们采集的模板大小,导致程序不能正常运行,虽然我们适当增加了roi的宽度和长度,但仍会发生这种错误。后来我们经过测试发现,特征点匹配可以达到我们想要的要求。

附上部分代码:

for r in img.find_rects(threshold = 10000):img.draw_rectangle(r.rect(), color = (255, 0, 0)) # in r.corners(): #img.draw_circle(p[0], p[1], 5, color = (0, 255, 0))if r:if r[2]<69 or r[3]300 or r[3]>180:#如果小于模板图像的大小,那么ROI设置为整幅图像 template[0]=0 template[1]=0 template[2]=320#宽度要调 template[3]=240#高度要调else:template[0]=r[0]template[1]=r[1]template[2]=r[2]+5#宽度要调template[3]=r[3]+5#高度要调print(template)#近端for n in templates1:template1 = image.Image(n)#对每个模板遍历进行模板匹配r1 = img.find_template(template1, 0.70, step=4, search=SEARCH_EX,roi=(template[0], template[1], template[2], template[3])) #, roi=(10, 0, 60, 60))#find_template(template, threshold, [roi, step, search]),threshold中#的0.7是相似度阈值,roi是进行匹配的区域(左上顶点为(10,0),长80宽60的矩形),#注意roi的大小要比模板图片大,比frambuffer小。#把匹配到的图像标记出来if r1:#img.draw_rectangle(r1)d="1"x=r1[0]y=r1[1]else:d="NO NUM"img.draw_string(x,y, d, scale = 2, mono_space = False,char_rotation = 0, char_hmirror = False, char_vflip = False,string_rotat

2.我们利用特征点检测的思路:

对于特征点检测,我们通过检测图像特征点,并与我们保存在SD卡的模板进行比对,找到满足特征点数最多的那个模板,即为我们要找的数字,然后我们通过比对一定次数,选出比对成功次数最多的那个数字,作为我们最终的识别结果。在小车对于任务的实现中,我们给出以下思路:

因为近端病房只采用数字1和2,所以我们便想到了一个策略:当识别到为近端病房时,只对数字1和2的特征点进行比对,当远端病房时,对其余数字进行特征点比对,这样很大程度上增加了我们识别的准确性。

部分代码如下:

if describe_Times<15:kpts_run = img.find_keypoints(max_keypoints=220, threshold=1, normalized=False)match1 = image.match_descriptor(kpts1, kpts_run, threshold=85)match2 = image.match_descriptor(kpts2, kpts_run, threshold=85)match3 = image.match_descriptor(kpts3, kpts_run, threshold=85)match4 = image.match_descriptor(kpts4, kpts_run, threshold=85)match5 = image.match_descriptor(kpts5, kpts_run, threshold=85)match6 = image.match_descriptor(kpts6, kpts_run, threshold=85)match7 = image.match_descriptor(kpts7, kpts_run, threshold=85)match8 = image.match_descriptor(kpts8, kpts_run, threshold=85)match1_count = match1.count()match2_count = match2.count()match3_count = match3.count()match4_count = match4.count()match5_count = match5.count()match6_count = match6.count()match7_count = match7.count()match8_count = match8.count()match_count = max(match3.count(),match4.count(),match5.count(),match6.count(),match7.count(),match8.count())#,match3_2.count(),match4_2.count(),match5_2.count(),match6_2.count(),match7_2.count(),match8_2.count())if (match1_count == match_count):match1_times=match1_times+1x[0] = match1.cx()#sending_data(1,x)#print(x1, end = ',')print(1,"matched:%d "%(match1.count()))if (match2_count == match_count):match2_times=match2_times+1x[1] = match2.cx()#sending_data(2,x)#print(x2, end = ',')print(2,"matched:%d "%(match2.count()))if (match3_count == match_count):match3_times=match3_times+1t_list[2]=match3_timesx[2] = match3.cx()#x3 = match3.cx()#sending_data(3,x)#print(x3, end = ',')print(3,"matched:%d "%(match2.count()))if (match4_count == match_count):match4_times=match4_times+1t_list[3]=match4_timesx[3] = match4.cx()#x4 = match4.cx()#sending_data(4,x)#print(x4, end = ',')print(4,"matched:%d "%(match4.count()))if (match5_count == match_count):match5_times=match5_times+1t_list[4]=match5_timesx[4] = match5.cx()#x5 = match5.cx()#sending_data(5,x)#print(x5, end = ',')print(5,"matched:%d "%(match5.count()))if (match6_count == match_count):match6_times=match6_times+1t_list[5]=match6_timesx[5] = match6.cx()#x6 = match6.cx()#sending_data(6,x)#print(x6, end = ',')print(6,"matched:%d "%(match6.count()))if (match7_count == match_count):match7_times=match7_times+1t_list[6]=match7_timesx[6] = match7.cx()#x7 = match7.cx()#sending_data(7,x)#print(x7, end = ',')print(7,"matched:%d "%(match7.count()))if (match8_count == match_count):match8_times=match8_times+1t_list[7]=match3_timesx[7] = match8.cx()#x8 = match8.cx()#sending_data(8,x)print(8,"matched:%d "%(match8.count()))describe_Times=describe_Times+1else:turn_num=t_list.index(max(t_list))+1sending_data(turn_num,x[turn_num])#数字和坐标print(turn_num,x[turn_num])break;

二、巡线

1.基本巡线

思路:将摄像头一帧图片的上半部分划分为三个平行的感兴趣区,在三个感兴趣区中分别寻找最大的红色色块,获得三个中心坐标,然后给予其不同的权重后计算平均质心坐标,用此质心坐标计算得到巡线时的偏转角度。

为什么只将一帧图片的上半部分?因为下半部分容易受到小车的阻挡或者阴影干扰

附上部分代码:

img = sensor.snapshot()for r in ROIS:#在划分的上半图像中找红线blobs = img.find_blobs(RED_THRESHOLD, roi=r[0:4], merge=True) # r[0:4]是感兴趣区域的矩形元组.if blobs:# Find the blob with the most pixels.largest_blob = max(blobs, key=lambda b: b.pixels()) # 在blobs中找最大像素值img.draw_rectangle(largest_blob.rect())# 将此区域的像素数最大的颜色块画矩形和十字形标记出来img.draw_cross(largest_blob.cx(),largest_blob.cy())Moban_flag=1#print(largest_blob.cx())centroid_sum += (80-largest_blob.cx())*r[4] # 最大值像素点的x坐标*权值 与 图像中心线的偏差值weight_sum = weight_sum + r[4] #权值综合#计算centroid_sum,centroid_sum等于每个区域的最大颜色块的中心点的x坐标值乘本区域的权值if weight_sum:center_pos = (centroid_sum / weight_sum)weight_sum = 0deflection_angle = 0ns = 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(".")sending_data(0,deflection_angle)#发送偏移角度

2.识别十字

我们通过判断图像左上方以及右上方区域是否有红线对十字进行识别,若左上角和右上角都有红色部分,那么我们认为小车到达十字路口处。

部分代码:

ROI = [(10,0,40,50),(110,0,50,50)]#x分别为10和110,Y为0blobs1 = img.find_blobs(RED_THRESHOLD,roi = ROI[0],area_threshold=150,merge=True)#先找左上角红线if blobs1:blobs2 = img.find_blobs(RED_THRESHOLD,roi = ROI[1],area_threshold=150,merge=True)if blobs2:print("+")sending_data(10,10)#表示识别到十字

三、串口通信

1.数据打包

有些同学可能会问为什么不直接通过串口发送字符串然后使用sscanf来解析呢呢?

串口发送字符串(ASCAII编码)的方式比较简单, 用C语言的sscanf()语句解析从语法上是完全可以的, 但是在实际工程上测试,解析不是很稳定,易丢数据。 ASCAII编码易出错,缺乏纠错功能。所以我采用二进制传输的,整数直接发送,浮点数放大去除小数位,然后以C语言的int,short,char的拆分逐8位形式逐位发送。 接收以后先计算校验累加,再重组。这种方式长期使用稳定可靠。
这样发出来的数据(int,short型)都是低位的字节在前,比如发送整型数9,得到的数据为(0x09 0x00 0x00 0x00)

附上代码:

def sending_data(data1,data2):global uart;#frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B];#data = bytearray(frame)data = ustruct.pack("<bbii",#<b,#格式为俩个字符俩个短整型(2字节) 0xAA,#帧头1 0xAE, #帧头2 data1,#用于判断 data2 #数字 )uart.write(data); #必须要传入一个字节数组#接收def receive_data():global uartif uart.any():tmp_data = uart.readline();return tmp_data;#print(tmp_data)

2.Openmv及STM32的串口通信

直接附上STM32解码代码:

void Optical_Flow_Receive_Prepare(u8 data){/* 局部静态变量:接收缓存 */static u8 RxBuffer[10];/* 数据长度 *//* 数据数组下标 */static u8_data_cnt = 0;/* 接收状态 */static u8 state = 0; /* 帧头1 */if(state==0&&data==TITLE1){state=1;}/* 帧头2 */else if(state==1&&data==TITLE2){state=2;_data_cnt = 0;}/* 接收数据租 */else if(state==2){RxBuffer[++_data_cnt]=data;if(_data_cnt>=8){state = 0;Data_Processing(RxBuffer,_data_cnt);}}/* 若有错误重新等待接收帧头 */elsestate = 0;}void Data_Processing(u8 *data_buf,u8 num){int theta_org,rho_org;/* 读取偏移角度原始数据*/theta_org = (int)(*(data_buf+1)<<0) | (int)(*(data_buf+2)<<8) | (int)(*(data_buf+3)<<16) | (int)(*(data_buf+4)<<24) ;theta_err = theta_org; /* 读取偏移尺寸原始数据 */rho_org = (int)(*(data_buf+5)<<0) | (int)(*(data_buf+6)<<8) | (int)(*(data_buf+7)<<16) | (int)(*(data_buf+8)<<24) ;rho_err = rho_org;}

总结

Openmv功能强大,不仅可以用来做图像的识别处理,其他大家可以去星瞳科技官网去学习,里面有例程的讲解以及丰富的资料