你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32送药小车:Openmv实现数字识别,巡线以及串口通信

[复制链接]
STMCU小助手 发布时间:2022-5-10 22:55
前言
我们使用Openmv中的特征点检测进行数字识别,并通过设置红色阈值实现巡线,最后通过串口通信将数字及红线坐标发送给主控芯片。

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

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

附上部分代码:

  1.     for r in img.find_rects(threshold = 10000):
  2.         img.draw_rectangle(r.rect(), color = (255, 0, 0))
  3.        # in r.corners(): #img.draw_circle(p[0], p[1], 5, color = (0, 255, 0))
  4.         if r:
  5.             if r[2]<69 or r[3]<75 or r[2]>300 or r[3]>180:#如果小于模板图像的大小,那么ROI设置为整幅图像
  6.                  template[0]=0
  7.                  template[1]=0
  8.                  template[2]=320#宽度要调
  9.                  template[3]=240#高度要调
  10.             else:
  11.                 template[0]=r[0]
  12.                 template[1]=r[1]
  13.                 template[2]=r[2]+5#宽度要调
  14.                 template[3]=r[3]+5#高度要调
  15.             print(template)
  16.         #近端
  17.             for n in templates1:
  18.                 template1 = image.Image(n)
  19.                 #对每个模板遍历进行模板匹配
  20.                 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))
  21.         #find_template(template, threshold, [roi, step, search]),threshold中
  22.         #的0.7是相似度阈值,roi是进行匹配的区域(左上顶点为(10,0),长80宽60的矩形),
  23.         #注意roi的大小要比模板图片大,比frambuffer小。
  24.         #把匹配到的图像标记出来
  25.                 if r1:
  26.                     #img.draw_rectangle(r1)
  27.                     d="1"
  28.                     x=r1[0]
  29.                     y=r1[1]

  30.                 else:
  31.                     d="NO NUM"

  32.                 img.draw_string(x,y, d, scale = 2, mono_space = False,
  33.                                 char_rotation = 0, char_hmirror = False, char_vflip = False,
  34.                                 string_rotat
复制代码

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

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

部分代码如下:

  1. if describe_Times<15:
  2.     kpts_run = img.find_keypoints(max_keypoints=220, threshold=1, normalized=False)
  3.     match1 = image.match_descriptor(kpts1, kpts_run, threshold=85)
  4.     match2 = image.match_descriptor(kpts2, kpts_run, threshold=85)

  5.     match3 = image.match_descriptor(kpts3, kpts_run, threshold=85)
  6.     match4 = image.match_descriptor(kpts4, kpts_run, threshold=85)

  7.     match5 = image.match_descriptor(kpts5, kpts_run, threshold=85)
  8.     match6 = image.match_descriptor(kpts6, kpts_run, threshold=85)

  9.     match7 = image.match_descriptor(kpts7, kpts_run, threshold=85)
  10.     match8 = image.match_descriptor(kpts8, kpts_run, threshold=85)

  11.     match1_count = match1.count()
  12.     match2_count = match2.count()

  13.     match3_count = match3.count()
  14.     match4_count = match4.count()

  15.     match5_count = match5.count()
  16.     match6_count = match6.count()

  17.     match7_count = match7.count()
  18.     match8_count = match8.count()

  19.     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())

  20.     if (match1_count == match_count):
  21.         match1_times=match1_times+1
  22.         x[0] = match1.cx()
  23.         #sending_data(1,x)
  24.         #print(x1, end = ',')
  25.         print(1,"matched:%d "%(match1.count()))

  26.     if (match2_count == match_count):
  27.         match2_times=match2_times+1
  28.         x[1] = match2.cx()
  29.         #sending_data(2,x)
  30.         #print(x2, end = ',')
  31.         print(2,"matched:%d "%(match2.count()))

  32.     if (match3_count == match_count):
  33.         match3_times=match3_times+1
  34.         t_list[2]=match3_times
  35.         x[2] = match3.cx()
  36.         #x3 = match3.cx()
  37.         #sending_data(3,x)
  38.         #print(x3, end = ',')
  39.         print(3,"matched:%d "%(match2.count()))

  40.     if (match4_count == match_count):
  41.         match4_times=match4_times+1
  42.         t_list[3]=match4_times
  43.         x[3] = match4.cx()
  44.         #x4 = match4.cx()
  45.         #sending_data(4,x)
  46.         #print(x4, end = ',')
  47.         print(4,"matched:%d "%(match4.count()))

  48.     if (match5_count == match_count):
  49.         match5_times=match5_times+1
  50.         t_list[4]=match5_times
  51.         x[4] = match5.cx()
  52.         #x5 = match5.cx()
  53.         #sending_data(5,x)
  54.         #print(x5, end = ',')
  55.         print(5,"matched:%d "%(match5.count()))

  56.     if (match6_count == match_count):
  57.         match6_times=match6_times+1
  58.         t_list[5]=match6_times
  59.         x[5] = match6.cx()
  60.         #x6 = match6.cx()
  61.         #sending_data(6,x)
  62.         #print(x6, end = ',')
  63.         print(6,"matched:%d "%(match6.count()))

  64.     if (match7_count == match_count):
  65.         match7_times=match7_times+1
  66.         t_list[6]=match7_times
  67.         x[6] = match7.cx()
  68.         #x7 = match7.cx()
  69.         #sending_data(7,x)
  70.         #print(x7, end = ',')
  71.         print(7,"matched:%d "%(match7.count()))

  72.     if (match8_count == match_count):
  73.         match8_times=match8_times+1
  74.         t_list[7]=match3_times
  75.         x[7] = match8.cx()
  76.         #x8 = match8.cx()
  77.         #sending_data(8,x)
  78.         print(8,"matched:%d "%(match8.count()))

  79.     describe_Times=describe_Times+1
  80. else:
  81.     turn_num=t_list.index(max(t_list))+1
  82.     sending_data(turn_num,x[turn_num])#数字和坐标
  83.     print(turn_num,x[turn_num])
  84.     break;
复制代码

二、巡线
1.基本巡线

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

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

附上部分代码:

  1.     img = sensor.snapshot()
  2.     for r in ROIS:#在划分的上半图像中找红线
  3.         blobs = img.find_blobs(RED_THRESHOLD, roi=r[0:4], merge=True) # r[0:4]是感兴趣区域的矩形元组.
  4.         if blobs:
  5.             # Find the blob with the most pixels.
  6.             largest_blob = max(blobs, key=lambda b: b.pixels()) # 在blobs中找最大像素值
  7.             img.draw_rectangle(largest_blob.rect())
  8.             # 将此区域的像素数最大的颜色块画矩形和十字形标记出来
  9.             img.draw_cross(largest_blob.cx(),largest_blob.cy())
  10.             Moban_flag=1
  11.             #print(largest_blob.cx())
  12.             centroid_sum += (80-largest_blob.cx())*r[4] # 最大值像素点的x坐标*权值 与 图像中心线的偏差值
  13.             weight_sum = weight_sum + r[4] #权值综合
  14.             #计算centroid_sum,centroid_sum等于每个区域的最大颜色块的中心点的x坐标值乘本区域的权值
  15.     if weight_sum:
  16.         center_pos = (centroid_sum / weight_sum)
  17.         weight_sum = 0
  18.     deflection_angle = 0
  19.     ns = center_pos/60  # QQVGA 160x120.
  20.     deflection_angle = -math.atan(ns)
  21.     deflection_angle = math.degrees(deflection_angle)
  22.     deflection_angle = 0 - int(deflection_angle)
  23.         #print(str(deflection_angle))
  24.     ##------------------------------------------ 直行 ----------------------------------------------------
  25.     #uart.write(" ")
  26.     #uart.write(str(int(deflection_angle)))
  27.     #uart.write(".")
  28.     sending_data(0,deflection_angle)#发送偏移角度
复制代码

2.识别十字

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

部分代码:

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

三、串口通信
1.数据打包

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

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

附上代码:

  1. def sending_data(data1,data2):
  2.     global uart;
  3.     #frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B];
  4.     #data = bytearray(frame)
  5.     data = ustruct.pack("<bbii",#<b,              #格式为俩个字符俩个短整型(2字节)
  6.                    0xAA,#帧头1
  7.                    0xAE, #帧头2
  8.                    data1,#用于判断
  9.                    data2 #数字
  10.                    )
  11.     uart.write(data);   #必须要传入一个字节数组
  12. #接收
  13. def receive_data():
  14.     global uart
  15.     if uart.any():
  16.         tmp_data = uart.readline();
  17.         return tmp_data;
  18.         #print(tmp_data)
复制代码

2.Openmv及STM32的串口通信
直接附上STM32解码代码:

  1. void Optical_Flow_Receive_Prepare(u8 data)
  2. {
  3.     /* 局部静态变量:接收缓存 */
  4.     static u8 RxBuffer[10];
  5.     /* 数据长度 *//* 数据数组下标 */
  6.     static u8  _data_cnt = 0;
  7.     /* 接收状态 */
  8.     static u8 state = 0;

  9.     /* 帧头1 */
  10.     if(state==0&&data==TITLE1)
  11.     {
  12.         state=1;
  13.     }
  14.     /* 帧头2 */
  15.     else if(state==1&&data==TITLE2)
  16.     {
  17.         state=2;
  18.         _data_cnt = 0;
  19.     }
  20.     /* 接收数据租 */
  21.     else if(state==2)
  22.     {
  23.         RxBuffer[++_data_cnt]=data;
  24.         if(_data_cnt>=8)
  25.         {
  26.             state = 0;
  27.             Data_Processing(RxBuffer,_data_cnt);
  28.         }
  29.     }
  30.     /* 若有错误重新等待接收帧头 */
  31.     else
  32.         state = 0;
  33. }


  34. void Data_Processing(u8 *data_buf,u8 num)
  35. {
  36.         int theta_org,rho_org;
  37.     /* 读取偏移角度原始数据  */
  38.     theta_org = (int)(*(data_buf+1)<<0) | (int)(*(data_buf+2)<<8) | (int)(*(data_buf+3)<<16) | (int)(*(data_buf+4)<<24) ;
  39.     theta_err = theta_org;

  40.     /* 读取偏移尺寸原始数据 */
  41.     rho_org = (int)(*(data_buf+5)<<0) | (int)(*(data_buf+6)<<8) | (int)(*(data_buf+7)<<16) | (int)(*(data_buf+8)<<24) ;
  42.     rho_err = rho_org;
  43. }
复制代码

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


收藏 评论0 发布时间:2022-5-10 22:55

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版