由于参加了这次TI杯电赛,并取得了不错的省二成绩,因此将自己的心得分享出来,供大家学习与提升,也是自我的一次总结。
stm32通过openmv模块实现非接触物体尺寸形态测量内容包括:
任务
设计并制作一个非接触式物体形状和尺寸自动测量装置,测量装置放置在图中所示的测量装置区内,被测目标放置在图中被测目标放置区内,装置能测量被测目标的形状、尺寸、测量头中心点与被测目标之间的距离等参数,并用激光束指示出被测目标的中心位置。背景板竖立放置在目标后 5cm 处。
摘要
作品选用STM32F401ccu6最小系统为核心板,用舵机搭建二维云台,选用OpenMV、L10雷达测距模块测量目标物的形态尺寸以及与装置的距离,测量结果从串口输出显示在OLED屏上。
本作品能精准识别出正方形、正三角形、圆形并计算出的目标物体所对应边长,测量尺寸误差大致在±5cm左右。此外该作品还能在较短时间内识别出排球、篮球、足球的类别。
系统结构
该系统主要由STM32F4系列单片机,机器视觉模块,激光测距模块,人机交互模块以及二维云台组成。机器视觉模块识别物体形状和尺寸,激光测距测量被测物与该测量装置的距离。两者所测数据返回给单片机,单片机控制云台完成指定任务后将测量数据显示在人机交互模块上。
图形识别分析
圆形识别所需的函数为image.find_circles
其使用方法如下:
image.find_circles([roi[,x_stride=2[,y_stride=1[, threshold=2000[, x_margin=10[,y_margin=10[,r_margin=10[,r_min=2[,r_max[,r_step=2]]]]]]]]]])
# Circle
对象有四个值: x, y, r (半径), 和 magnitude。
# x_stride
是霍夫变换时需要跳过的x像素的数量。若已知圆较大,可增加 x_stride 。
# y_stride
是霍夫变换时需要跳过的y像素的数量。若已知圆较大,可增加 y_stride 。
# roi
是一个用以复制的矩形的感兴趣区域(x, y, w, h)。
# threshold
控制从霍夫变换中监测到的圆。只返回大于或等于 threshold 的圆。
# r_step
控制如何逐步检测半径。默认为2
# r_min
控制检测到的最小圆半径。增加此值可以加速算法。默认为2
正方形形识别所需函数使用方法:
image.find_rects([roi=Auto, threshold=10000])
#threshold
小于 threshold 的矩形会从返回列表中过滤出来
#roi
是一个用以复制的矩形的感兴趣区域(x, y, w, h)。
正三角形识别所需函数使用方法:
image.find_line_segments([roi[,merge_distance=0[,max_theta_difference=15]]])
#merge_distance
指定两条线段之间的可以相互分开而不被合并的最大像素数。
#max_theta_difference
是上面 merge_distancede 要合并的的两个线段的最大角度差值。
激光测据原理分析
本装置测距采用的是基于TOF技术的高速单线激光雷达L10。TOF是“time of flight”的简称,TOF相机是一种深度成像相机,通过3D sensor 向目标物体发射已到光束,,通过测量光在镜头和物体之间的传输时间来测距,从而通过多光纤实现深度图,达成3D立体深度感应,从而获取3D图像。激光器发射一个激光脉冲,并由计时器记录下出射的时间,回返光经接收器接收,并由计时器记录下回返的时间。两个时间相减即得到了光的“飞行时间”,而光速是一定的,因此在已知速度和时间后很容易就可以计算出距离。
系统总体设计
系统框图如图所示。12V直流稳压电源通过5V和3.3.V稳压模块分别向单片机和OpenMV供电;目标物体的水平距离通过激光测距模块读取,并转化为距离数字量传回单片机中;目标物体的几何形状以及坐标通过OpenMV的颜色识别读取,并转化为位置数字量传回单片机中;单片机将激光测距模块读取的距离数据处理后传给OpenMV,OpenMV经比例计算后返回物体边长的数字量;最后单片机将目标物体的形状、边长和距离数据传给OLED显示;进入自动寻找目标时,单片机根据OpenMV传回的坐标数据控制云台舵机转动角度;当测量结果稳定后启动声光提示,表示当前测量任务完成;单片机的电平信号可控制由发光二极管和有源蜂鸣器组成的声光提示部分。
最小系统拓展板
原理图:
PCB:
声光提示电路
当测量任务完成时,PB2输出高电平。因为本装置选用的是有源蜂鸣器,故PB2输出高电平蜂鸣器启动,LED灯点亮。
程序设计流程图
激光测距流程图:
水平控制流程图:
STM32与openmv核心代码
-
cubemx配置
-
按键控制部分程序
if(HAL_GPIO_ReadPin(key1_GPIO_Port,key1_Pin)==0){
HAL_Delay(100);
if(HAL_GPIO_ReadPin(key1_GPIO_Port,key1_Pin)==0){
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
HAL_Delay(500);
keystatues1+=1;
HAL_UART_ErrorCallback(&huart6);
HAL_GPIO_WritePin(beep_GPIO_Port,beep_Pin,RESET);
OLED_CLS();
shape=0;
}
}
- 字符串显示
if(shape ==1){
OLED_ShowStr(0, 0,"Shape:circle ", 2);//显示字符串
HAL_GPIO_WritePin(beep_GPIO_Port,beep_Pin,SET);
}
else if(shape ==2){
OLED_ShowStr(0, 0,"Shape:rectangle ", 2);//显示字符串
HAL_GPIO_WritePin(beep_GPIO_Port,beep_Pin,SET);
}
else if(shape ==3){
OLED_ShowStr(0, 0,"Shape:triangle ", 2);//显示字符串
HAL_GPIO_WritePin(beep_GPIO_Port,beep_Pin,SET);
}
OLED_ShowStr(0, 3, "Size:", 2);//显示字符串
OLED_ShowStr(0, 6, "Distance:", 2);//显示字符串
OLED_ShowNum(70,7,value,5,20);
- 识别球的种类
else if(keystatues2==2){ //任务四:识别球的种类
M=1; //云台不转动
HAL_GPIO_WritePin(laser_GPIO_Port, laser_Pin, GPIO_PIN_SET);//开启激光标记中心
OLED_ShowStr(0, 0,"Model:basketball ", 2);//篮球
HAL_GPIO_WritePin(beep_GPIO_Port,beep_Pin,SET);
if(model ==1){
}
else if(model ==2){
OLED_ShowStr(0, 0,"Model:football ", 2);//足球
HAL_GPIO_WritePin(beep_GPIO_Port,beep_Pin,SET);
}
else if(model ==3){
OLED_ShowStr(0, 0,"Model:volleyball ", 2);//排球
HAL_GPIO_WritePin(beep_GPIO_Port,beep_Pin,SET);
}
OLED_ShowStr(0, 6, "Distance:", 2);//显示字符串
OLED_ShowNum(70,7,value,5,20);
- 舵机跟随转动
else if( keystatues2==3){
HAL_GPIO_WritePin(laser_GPIO_Port, laser_Pin, GPIO_PIN_RESET);
for (degreex = -40; degreex <= 40; degreex += 1)
{
MG996R_Set_Angle2(degreex);
HAL_Delay(10);
}
for (degreex = 40; degreex >= -40; degreex -= 1)
{
MG996R_Set_Angle2(degreex);
HAL_Delay(10);
}
for (degreey = -30; degreey <= 30; degreey += 1)
{
MG996R_Set_Angle(degreey);
HAL_Delay(10);
}
for (degreey = 30; degreey >= -30; degreey -= 1)
{
MG996R_Set_Angle(degreey);
HAL_Delay(10);
}
- OLED显示部分代码
// Parameters : x,y -- 起始点坐标(x:0~127, y:0~7); ch[] -- 要显示的字符串; TextSize -- 字符大小(1:6*8 ; 2:8*16)
// Description : 显示codetab.h中的ASCII字符,有6*8和8*16可选择
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)
{
unsigned char c = 0,i = 0,j = 0;
switch(TextSize)
{
case 1:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 126)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0;i<6;i++)
WriteDat(F6x8[c][i]);
x += 6;
j++;
}
}break;
case 2:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 120)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i]);
OLED_SetPos(x,y+1);
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i+8]);
x += 8;
j++;
}
}break;
}
}
- stm32串口接收及发送部分程序
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) //opmv-receive
{
if(huart==&huart1)
{
HAL_UART_Receive_IT(&huart1,&my_re_buf1[++pt_w1],1);
trans(my_re_buf1[pt_w1]);
// measure();
}
if(huart==&huart6) //radar
{
HAL_UART_Receive_IT(&huart6,&my_re_buf6[++pt_w6],1);
trans6(my_re_buf6[pt_w6]);
}
}
- 定时器中断回调程序
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim==&htim4)
{
while(pt_r1<pt_w1 )
{
while(pt_r1<pt_w1){
pt_r1++;
//HAL_UART_Transmit(&huart2,&my_re_buf1[pt_r1++],1,1000); //opmv-stm32-ttl
// printf("%d",my_re_buf1);
}
}
if(pt_r1>=pt_w1)
{
pt_w1=pt_r1=0;
HAL_UART_AbortReceive_IT(&huart1);
HAL_UART_Receive_IT(&huart1,my_re_buf1,1);
//test 2
}
// while(pt_r2<pt_w2 )
// {
//
// HAL_UART_Transmit(&huart1,&my_re_buf2[pt_r2++],1,1000); //将PC发给板子的数据转发到wifi模块
// HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13); //LED
//
// }
// if(pt_r2>=pt_w2)
// {
// pt_w2=pt_r2=0;
// HAL_UART_AbortReceive_IT(&huart2);
// HAL_UART_Receive_IT(&huart2,my_re_buf2,1);
// }
//radar
while(pt_r6<pt_w6 )
{
while(pt_r6<pt_w6){
pt_r6++;
// HAL_UART_Transmit(&huart2,&my_re_buf6[pt_r6++],1,1000); //opmv-stm32-ttl
// printf("%d",my_re_buf1);
}
}
if(pt_r6>=pt_w6)
{
pt_w6=pt_r6=0;
HAL_UART_AbortReceive_IT(&huart6);
HAL_UART_Receive_IT(&huart6,my_re_buf6,1);
//test 2
}
}
}
测试方案
在保证目标物体的中心与测量装置测量中心在同一水平线的情况下,重复测量同一平面图形形状、边长、距离数据取平均值,并计算测量精度。更换目标图形后重复上述实验内容。
让目标物体出现在规定测量范围内任意位置,重复上述测试方案:记录距离、形状、边长,激光定位点与中心点的直线距离,计算测量精度。
测试结果记录
中心点对齐情况下测量精度测试数据详见表。分别在距离装置2米、2.5米、3米的情况下,目标物体的形状、尺寸、距离。
自动跟随精度测试:
测试所得数据详见下表。中心点不固定对齐,在允许范围内任意放置目标物体重复上述操作。
特色创新
我们作品的特色在于,在前期调试过程中,我们把一些有用的参数通过oled显示出来,通过按钮微调参数,实现更加精准与便捷的调参过程。并且我们设计了一个舵机占空比函数,通过角度调整占空比,从而使舵机的角度位置调节更加精确。我们最后的发挥部分,通过双舵机配合,通过正逆运动学解算出了所需转动角度。
误差分析
作品误差一是来自于环境光线的影响,调试的时间不是固定的,光线有所变化,不同光线下机器所捕捉的颜色会有差别,极易造成误判。二来是OpenMV模块自带像素较低,对尺寸的估计比较粗略,偏差必然是存在的。再者是装置搭建时存在结构误差,导致中心点的偏离。前两者的误差都能通过软件调节。
心得总结
在本次电赛G题中,我们组程序采用的是市面上最流行的HAL库编程,在做比赛的过程中,我们遇到了非常多的困难,在这期间也见过比赛中途退赛的同学,但我们坚持下来了,通过比赛学到了非常多的知识。在第一天,我们控制oled显示数据,根据自己所掌握的知识和网上的资料,我们试了无数次程序,每一次都不能成功,第一天就差点崩溃,甚至一度怀疑自己的能力,直到第二天,终于找到了问题并成功显示,团队大受鼓舞。在后面的几天中,也遇到莫名其妙的bug,比如头天晚上能成功运行的程序,到了第二天早上,却神奇的发现不能指示目标点中心,后来仔细检查发现,云台在经过多次旋转后,其中的一根导线虚短了,浪费了我们很多时间。通过这次比赛我们进行了一次为期四天的科研经历,确实学习到很多知识,熬夜通宵是常态,明白了在科研面前不能放弃,坚持下来,就一定能把实物做出来!