前言
本案例是真实的“高血压”需求,物料无明显特征,只能观察图像来分析特征,方案设计具有一定挑战性。但是只要思想不滑坡,办法总比困难多。通过一系列VM模块“组合拳”,最终检测率达到97%以上。
场景介绍
场景要求:视觉引导螺纹中心位置(X,Y,R),需要根据螺纹起始点与螺孔圆心确认并修改相应的R值。拍照位固定且每次只有一个物料,来料位置固定。物料规格为1cm1cm0.5cm。
这样看起来还挺简单,不就一个X,Y,R引导嘛,找个圆心确定X,Y坐标,再找到对应的螺纹起点和圆心点点连线确定角度。问题来了,这个起点,真不好找…
硬件选型
相机:500W
镜头:50mm+10mm接圈
光源:P-RL-170-20-B
镜头到产品的距离:170mm,相机垂直倾斜角度:0
光源到产品的距离:10mm
成像分析&方案设计
接下来,观察下方的三张图片,分析是否有共同特征,能否拿对应的特征进行起点查找。
我们将图片中的特征进行梳理总结:
1、物料为圆形,除底部缺口外并无明显特征,并且螺纹起点的形状并不固定,无法使用模板匹配定位起点,也无法使用顶点检测模块(使用模板匹配、顶点检测定位起点,思路PASS)。
2、每个物料的底部缺口是否与螺纹起点有关联(与客户联系,明确该特征不稳定,与螺纹起点关联无明确刚体关系,思路PASS)。
3、螺纹轨迹特征在图像上可能会与螺纹外圈存在连通域连通现象,无法通过二值化将螺纹的轨迹与螺纹外圈分离开来,单独分析(二值化处理分离螺纹轨迹与螺纹外圆,思路PASS)。
4、每个螺纹的起始点与螺纹外圆接近,物料螺纹成像的排布密集程度从图像上看从圆心往外圆显示为从密到疏,最外一圈有明显螺纹轨道。能否从最靠近螺纹外圆的螺纹轨道入手分析,屏蔽较为密集的部分,只留下稀疏的起点那一圈螺纹轨迹。屏蔽后,实际的检测范围是一个圆环。对于圆环,通过极坐标进行展开,得到一个常规的矩形图像,再对转换后的图像处理,找到螺纹起点后可以通过特征的X与图像长度做对比得到相应的角度信息(该方案貌似可行,我们先按照这个思路往下走)。
方案搭建
接下来我们按照上面梳理的思路搭建视觉检测方案。
第一步:
我们可以通过定位模块定位目标圆的信息,根据实际成像效果,这边我选择了blob分析模块,blob选中区域的外接矩形框中心点就是我们求的螺纹外圈中心(X,Y)。
第二步:
我们可以参考螺纹外接矩形框的大小后,使用圆环展开模块,通过调整圆环内径、外径、起始角度、角度范围、角度方向将圆形展开。结合成像效果,我们主要管控内径最终我们选择
外径:外接矩形框宽/2
+
+
+ 150
内径:外接矩形框宽/2
−
-
− 60
第三步:
我们查看图片发现仍然有不少脏污影响成像,这里可以使用blob分析模块、形态学处理模块或图像滤波等模块,我选择了blob分析模块。
针对这个问题,继续观察相似图片是否存在共同点。
观察根据上述图片,我们发现第一张图片的起点可能处于图片的临界边缘,为了降低误判的可能性,我们可以加大图像范围,选择2个圆环展开配合图像拼接。
第四步:
使用2个圆环展开模块进行展开后,使用图像拼接模块进行图像硬拼接,可以避免图像中的螺纹起始特征处于图像的临界边缘从而漏检。
拼接后,螺纹起点在图像中的位置发生改变,一共展开了540度,所以存在180度的图像重合。根据图像效果,增加图像组合模块腐蚀白色区域,让线条更加平滑。效果如下:
根据之前观察,发现图像无法使用模板匹配以及顶点检测来寻找螺纹起点,但是发现几个图像特征:
- 螺纹起点的右侧为黑色连通区域,是否能使用图像处理将对应的黑色连通区域找到并输出0度外接矩形框的左侧边缘起点(测试发现部分有干扰,导致图像不兼容,思路PASS)
- 观察螺纹起点附近白色区域,发现如果从上往下从百到黑进行采样,相邻点存在Y方向的落差,如下图的1与2所示。但要避免找错找到3与4,还需要一个参照物防呆,我们选择螺丝外圈(蓝线)进行对比,靠近蓝线的才是起点。
下一步,需要对螺丝外圈从下到上、从黑到白,选取第一条进行采样,再对图像从上到下、从黑到白,选取第一条进行采样。
第五步:
由于需要大批量的数据采样,此处使用直线查找模块进行数据采样,上边采样来获取数据后续计算相邻点位是否存在Y轴跳变,通过底边采样获取的数据后续会对存在相邻点位Y值跳变的点位进行二次评判,判断是否为真实起点。
但是遇到一个问题,当白色连通域与图像上边沿重叠,直线查找无法正常采样使用。如下图所示,我们想要找到黄色的线,但实际只能找到绿色的线。
第六步:
使用图像拼接模块上下拼接,确保直线查找模块的能够正常使用。为让采样不剔除,我们将剔除点数设置为0,剔除距离设大。
最终,我们得到了以下效果:
第七步:
将采集的数据进行汇总收集,转换的字符串作为后面脚本模块的输入。
第八步:
通过脚本模块将输入的点位数据进行处理。VM的脚本模块支持C#语言编写,整理思路并对数据处理。
脚本思路:
第一步筛选,将每个相邻点的Y做差,得到dy=Y(n+1)-Y(n),将所有的dy进行排列,选取最大的5个,然后进行第二步筛选。
第二步筛选,将目标点分别对比相同X对应的底边Y,若底边与目标点的距离非常大,则不是边缘起始点。然后进行第三步
第三步筛选,将剩下的点进行自定义的防呆,根据测试,添加Y(N+5) − - − Y(N-5) > > > 15。
最后将剩下的点进行排列,差值最大的就是待输出的目标对象。
示例代码如下:
using System;
using System.Text;
using System.Windows.Forms;
using Script.Methods;
public partial class UserScript:ScriptMethods,IProcessMethods
{
//the count of process
//执行次数计数
int processCount ;
string Y;
string CompareY;
int ccount;
/// <summary>
/// Initialize the field's value when compiling
/// 预编译时变量初始化
/// </summary>
public void Init()
{
//You can add other global fields here
//变量初始化,其余变量可在该函数中添加
processCount = 0;
}
public bool Process()
{
//You can add your codes here, for realizing your desired function
//每次执行将进入该函数,此处添加所需的逻辑流程处理
//底边的参考值
GetIntValue("CCount",ref ccount);
GetStringValue("DownY",ref CompareY);
string [] CompareYList= CompareY.Split(',');
//直线采样的离散值
GetStringValue("Ylist",ref Y);
string [] ylist= Y.Split(',');
int count=ylist.Length;
SetIntValue("count",count);
//对直线离散值转换为float
float[] Yvalue=new float[count];
for(int i=0;i<count;i++)
{
Yvalue[i]=float.Parse(ylist[i]);
}
//对离散值相邻做差
float [] sublist=new float[ccount];
for(int i=0;i<(ccount-5);i++)
{
sublist[i]=Yvalue[i+1]-Yvalue[i];
}
//拷贝相邻做差的集合并排序
float [] sublistcopy=new float[ccount];
sublistcopy=(float[])sublist.Clone();
Array.Sort(sublistcopy);
//找到相邻差值最大的五个值的索引
int index1=Array.IndexOf(sublist,sublistcopy[(ccount-1)]);
int index2=Array.IndexOf(sublist,sublistcopy[(ccount-2)]);
int index3=Array.IndexOf(sublist,sublistcopy[(ccount-3)]);
int index4=Array.IndexOf(sublist,sublistcopy[(ccount-4)]);
int index5=Array.IndexOf(sublist,sublistcopy[(ccount-5)]);
//将5个索引丢进一个集合
int[] indexlist=new System.Int32[5]{index1,index2,index3,index4,index5};
//将这五个索引值对应的离散点Y-底边Y
float[]result=new float[5];
result[0]=float.Parse(CompareYList[index1])-Yvalue[index1];
result[1]=float.Parse(CompareYList[index2])-Yvalue[index2];
result[2]=float.Parse(CompareYList[index3])-Yvalue[index3];
result[3]=float.Parse(CompareYList[index4])-Yvalue[index4];
result[4]=float.Parse(CompareYList[index5])-Yvalue[index5];
//将得到的值进行汇总并拷贝排序
float[]resultCopy=new float[5];
resultCopy=(float[])result.Clone();
Array.Sort(resultCopy);
//
for(int i=0;i<5;i++)
{
int index= indexlist[i];
SetIntValue("index",index);
if(result[i]<60&&sublist[indexlist[i]]>5&&((Yvalue[index+5]-Yvalue[index-5])>15))
{
SetFloatValue("result",sublist[indexlist[i]]);
SetFloatValue("tobottom",result[i]);
break;
}else
{
SetFloatValue("result",0);
SetFloatValue("tobottom",0);
SetIntValue("index",0);
}
}
return true;
}
}
第九步:
步骤8已计算出起点所在采样集合的对应索引,我们使用几何创建模块,将得到的集合索引绑定(X,Y)数据以直观的形式展现,帮助确定起点是否查找正确。
经过跑图测试,稳定性不错。接下来进行最后一步,将找到的点X,逆推回原图之中,最后确认R。
R
=
=
= 540
∗
*
∗ 目标X
/
/
/ 展开画面长
第十步:
变量计算模块可以进行简单的数学运算,用它来计算起点的X坐标与画面宽度的比例来计算起点在原始图像中的角度信息。使用几何创建模块来渲染显示,拟合直线的角度就是待求的R。
剩下就是标定转换,不再过多介绍。
经过上述十步操作,我们使用100余张图片图像进行测试,准确率达到97%以上。