工业视觉_64:通过“蛛网式分割+Fast角点“进行旋转无关性匹配



/*
    工业视觉_64:通过"蛛网式分割+Fast角点"进行旋转无关性匹配
    未来20年里,机器换人,智能使用机器人,改造传统工厂成智慧工厂,将成为最火的行业之一.
    工业视觉,目标很明确:"快,准,稳"三个字. 快:开发快,运行速度快;准:高精度;稳:稳健可靠
    使用高级语言做工程主要优势在:已经有丰富的数据结果和成熟的类型库,如List,Dictionary,Lambda,Accord,...
    所以,目前"机器换人"项目大多采用工控电脑,搭建 Windows7+VS2019+EMGU(或AForge+Accord),这一方案见效最快.
    Halcon,Emgu的视觉库都很强大,而AForge+Accord库更全面,更丰富(如:数学,人工智能,机器学习).
    散点集合,可来自角点,或称关键点,特征点,机器视觉中有丰富的类型.本文本取Fast角点进行旋转无关性匹配.
    因为大多机床,或机器人在视觉下工作,每一次工件流动过来位置与角度常常有变,机器人要抓住它,并装配到另一处,数学计算很重多.
    这就要提取工件的一些几何特征,以保证在旋转后继续能正确识别型号,确定位置与方位角.以便使机器人工作.
    多人热衷AI训练也是行的.但是本文提出的思路是,找到工件中心(质心),通过计算惯性矩(中心二阶矩)找到方位角,
    划分成N个扇形,再按半径切片,在各切片中的统计点数来进行工件的相似度的匹配.
    也就是对每一工件图,生成与旋转无关的一系列值,构成特征向量,再分析这些向量之间的关系(如欧氏距离,相关系数等).
    在主轴明显且角点比较稳定时,本方案比AI训练N次更方便些.若打光方案正确,则提取的角点也更稳定.
    在使用机器人时,应该对相机进行纠偏(消除畸变),标定,打光等处理,以进一步提高精度.
    另一工程中,圆弧形太多,无法确定产品的方位角,使用了"相位差匹配"算法,在应用中也取得了很好的成绩,算法与代码见作者另行介绍.
    本文系作者在"安吉八塔机器人公司"产品线研发中的拓展与随笔.
        ---------     编撰:    项道德(微信:daode1212),2021-07-14
    */


//载入照片:
pictureBox1.Image = Image.FromFile("工业视觉_8(旋转无关性匹配).png");//白色背景,四区块(四象限)各一工件图
Bitmap bmp = (Bitmap)(pictureBox1.Image);
Graphics g = Graphics.FromImage(bmp);
Pen pen0 = new Pen(Color.FromArgb(180, 0, 0), 4);
Pen pen1 = new Pen(Color.FromArgb(120, 0, 180), 4);
Pen pen2 = new Pen(Color.FromArgb(80, 70, 0), 4);
Pen pen3 = new Pen(Color.FromArgb(222, 111, 122), 4);

int ww = bmp.Width;
int hh = bmp.Height;
//自定: MSG("")为内置函数MessageBox.Show("")的简写方式;
MSG("图片宽度与高度:\r\n" + string.Format("w={0},h={1}", ww, hh) + "\r\n 接着,呈现处理结果");
SolidBrush bh = new SolidBrush(Color.FromArgb(155, 80, 122));
            
//Susan角点检测:
//var scd = new Accord.Imaging.SusanCornersDetector();

//Moravec角点检测,稍慢:
//var scd = new Accord.Imaging.MoravecCornersDetector();

//Harris角点检测:
//var scd = new Accord.Imaging.HarrisCornersDetector();

//Fast角点检测,较快:
var scd = new Accord.Imaging.FastCornersDetector();

List<IntPoint> LP = scd.ProcessImage(bmp);

//绘制各SusanCorners角点:
int k = 0;
foreach (var p in LP)
{
    int u = 255 * k / LP.Count;
    bh = new SolidBrush(Color.FromArgb(255 - u, 0, u));//从红色到蓝色
    g.FillEllipse(bh, p.X - 3, p.Y - 3, 6, 6);
    k++;
}

//MSG("Fast角点检测:" + LP.Count + "个. ");

//按位置划分为四个组:
Dictionary<int, List<IntPoint>> D0 = new Dictionary<int, List<IntPoint>>(); //int:组序(0,1,2,3),List<IntPoint>:角点集
List<IntPoint> LP0 = new List<IntPoint>();
List<IntPoint> LP1 = new List<IntPoint>();
List<IntPoint> LP2 = new List<IntPoint>();
List<IntPoint> LP3 = new List<IntPoint>();
foreach (var p in LP)
{
    if (p.X<ww/2 && p.Y<hh/2)
    {
        LP0.Add(p);
    }
    if (p.X>=ww/2 && p.Y<hh/2)
    {
        LP1.Add(p);
    }
    if (p.X<ww/2 && p.Y>=hh/2)
    {
        LP2.Add(p);
    }
    if (p.X>=ww/2 && p.Y>=hh/2)
    {
        LP3.Add(p);
    }
}
D0.Add(0, LP0);D0.Add(1, LP1);D0.Add(2, LP2);D0.Add(3, LP3);

//计算各组中心(质心):
bh = new SolidBrush(Color.FromArgb(127, 127, 0));
PointF[] centerLP = new PointF[4];
for (int i=0;i<4;i++)
{
    centerLP[i].X = (float)D0[i].Average(o => o.X);
    centerLP[i].Y = (float)D0[i].Average(o => o.Y);
    g.FillEllipse(bh, centerLP[i].X - 6, centerLP[i].Y - 6, 12, 12);//绘制质心
}

//各组变换到以各自质心为原点的新集:
Dictionary<int, List<IntPoint>> D1 = new Dictionary<int, List<IntPoint>>(); //int:组序(0,1,2,3),List<IntPoint>:角点集(相对于各自的质心为原点)
for (int i = 0; i < 4; i++)
{
    List<IntPoint> LPt =new List<IntPoint>( D0[i].Select(o=>new IntPoint(o.X-(int)centerLP[i].X,o.Y-(int)centerLP[i].Y)));
    D1[i] = LPt;
}

//为获得主轴方位角,计算各集的二阶矩:
float[] Angle = new float[4];//使用角度制,范围是:-PI,...PI
for (int i = 0; i < 4; i++)
{
    float xm = 0,ym=0;
    foreach(var m in D1[i])
    {
        xm += (float)(Math.Sign(m.X) * Math.Pow(m.X, 2));
        ym += (float)(Math.Sign(m.Y) * Math.Pow(m.Y, 2));
    }
    Angle[i] =(float)(Math.Atan2(ym,xm));              
}
//绘制各主轴:
for(int i = 0; i < 4; i++)
{
    g.DrawLine(pen3, centerLP[i], new Point((int)(centerLP[i].X + 100 * Math.Cos(Angle[i])),(int)(centerLP[i].Y+ 100 * Math.Sin(Angle[i]))));
}

//蛛网式(各扇形内再依半径大小切割成三片:1/2,3/4),形成12*3=36维的特征向量:
int dltA = 30;int Num = 360 / dltA; //圆周划分成:360/30=12片扇形
PointF[] rR = new PointF[Num]; //X:小半径,Y:大半径
            
List<int[]> ptFans = new List<int[]>();//四扇区特征向量容器
            
//计算四扇区特征向量:
for (int i = 0; i < 4; i++)
{
    rR[i] = new PointF(float.MaxValue,float.MinValue);
    foreach (var m in D1[i]) //先获取最小与最大半径
    {
        double Si = Math.Sqrt(m.Y*m.Y+ m.X*m.X);
        if (Si < rR[i].X) { rR[i].X = (float)Si; }
        if (Si > rR[i].Y) { rR[i].Y = (float)Si; }
    }
    int[] ptsFan = new int[3 * Num];//计数(12扇区(0,1,2))
    foreach (var m in D1[i])
    {
        double Ai = Math.Atan2(m.Y, m.X)-Angle[i];//与主轴方位角为参考
        int Ki = (int)((360+Ai * toD) % 360) / dltA; //-PI...PI --->> 0...360 --->> 0,...,11
        double Si = Math.Sqrt(m.Y * m.Y + m.X * m.X);
        double RMin = rR[i].X; double rMax = rR[i].Y;
        double dR = rMax - RMin;
        if (Si < RMin + 0.5 * dR) //第一片
        {
            ptsFan[3 * Ki]++;
        }
        if (Si >= RMin + .5 * dR && Si < RMin + 0.75 * dR)//第二片
        {
            ptsFan[3 * Ki + 1]++;
        }
        if (Si >= RMin + 0.75 * dR)//第三片
        {
            ptsFan[3 * Ki + 2]++;
        }
    }
    ptFans.Add(ptsFan);                
}

//观察各特征向量:
int y = 25;
foreach (var m in ptFans)
{
    for (int i=0;i<m.Length;i++)
    {
        y += 3;
        g.DrawLine(pen2, 350, y, 350+4*m[i], y);
    }
    y += 37;
}

//计算特征向量的相关性:
string txt = "";
float[] dj = new float[4];//记各向量长度
for (int j=0;j<4;j++)
{
    var m = ptFans.ElementAt(j);
    for (int i = 0; i < m.Length; i++)
    {
        dj[j] += m[i] * m[i];
    }
    dj[j] = (float)Math.Sqrt(dj[j]);
}

//--------[0-3]相关性计算--------
var m0 = ptFans.ElementAt(0);
var m3 = ptFans.ElementAt(3);
float cov03 = 0;
for (int i = 0; i < m0.Length; i++)
{
    cov03 += m0[i] * m3[i];
}
cov03 /= (dj[0]*dj[3]);
txt+=("[0-3]相关性计算"+cov03);


//--------[1-2]相关性计算--------
var m1 = ptFans.ElementAt(1);
var m2 = ptFans.ElementAt(2);
float cov12 = 0;
for (int i = 0; i < m1.Length; i++)
{
    cov12 += m1[i] * m2[i];
}
cov12 /= (dj[1]*dj[2]);
txt+=("  [1-2]相关性计算"+cov12);


//显示结果:
g.DrawString("[蛛网式]旋转无关性匹配\r\n"+txt, new Font("", 10), bh, bmp.Width / 4, hh  - 60);
pictureBox1.Image = bmp;

上图:模拟四个工件,    下图:旋转无关性匹配的相关性分析.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值