工业视觉_70:多对象轮廓的相位差匹配(旋转无关性匹配)

效果图如下:

/*
工业视觉_70:多对象轮廓的相位差匹配(旋转无关性匹配)
                
未来20年里,机器换人,智能使用机器人,改造传统工厂成智慧工厂,将成为最火的行业之一.
工业视觉,目标很明确:"快,准,稳"三个字. 快:开发快,运行速度快;准:高精度;稳:稳健可靠
使用高级语言做工程主要优势在:已经有丰富的数据结构和成熟的类型库,如List,Dictionary,Lambda,Accord,...
所以,目前"机器换人"项目大多采用工控电脑,搭建 Windows7+VS2019+EMGU(或AForge+Accord),这一方案见效最快.
Halcon,Emgu的视觉库都很强大,而AForge+Accord库更全面,更丰富(如:数学,人工智能,机器学习).
                
    许多产品,类似圆,正方形,没有明显的主轴,但是又必须匹配角度的.
在产品的研发中,本人提出了"相位差匹配"思路:
1,提取对象轮廓,按时针方向计算相对于中心的半径集;
2,不同工件的半径集作个移相匹配.找出误差最小时的角度.角度可取(0,1,2,3,...,359)
3,当然,给半径集进行小波分析,或富立叶级数变换,还能意外的发现.
4,计算出了产品相片中的方位角,便于用机器人的姿态变换去拾取与装配.

本文系作者在"安吉八塔机器人公司"产品线研发中的拓展与随笔.
[已经发布,链接: ] 

    ---------     编撰:    项道德(微信:daode1212),2021-07-21
             
 */

    pictureBox1.Image = Image.FromFile("工业视觉_9.png");
    //自定义函数MSG()即MessageBox.Show().
    //MSG("马上呈现:  螺旋线搜索法,建立有序边缘点集");
    Bitmap bmp = (Bitmap)(pictureBox1.Image);
    int ww = bmp.Width, hh = bmp.Height;
    bmp = LockBits03(bmp);//自定义函数, 黑区边缘化-->序列化,彩色标注

    //本地绘制:
    Graphics g = Graphics.FromImage(bmp);
    //Pen pen0;
    //int clr = 0;
    //Point z0 = serialPoints.ElementAt(0).Key;
    //foreach (Point z in serialPoints.Keys)
    //{
    //    pen0 = new Pen(Color.FromArgb(clr % 255, clr % 128, clr % 64), 2);
    //    g.DrawLine(pen0, z0, z);
    //    clr++; z0 = z;
    //}

    //分组,取中心,计算半径集,绘制频谱曲线:
    var grp = serialPoints.Select(o => o).GroupBy(o=>o.Value);//分组
    //Graphics g = Graphics.FromImage(bmp);
    SolidBrush bh = new SolidBrush(Color.FromArgb(111, 0, 111));
    Pen pen1 = new Pen(Color.FromArgb(222, 0, 222), 1);
    Pen pen2 = new Pen(Color.FromArgb(0, 0, 122), 1);
    int[] A = new int[4];//长半径所对应的方位角[角度]
            
    //针对各组,计算中心坐标:
    int h = 0;
    Dictionary<int, List<double>> DL = new Dictionary<int, List<double>>();
    foreach (var m in grp)
    {
        if (m.Count() > 200)
        {
            float xCenter = (float)m.Average(o => o.Key.X);
            float yCenter = (float)m.Average(o => o.Key.Y);
            g.FillEllipse(bh, xCenter, yCenter, 8, 8);
            g.DrawString("#" + h, new Font("", 10), bh, xCenter - 12, yCenter + 12);
            List<double> LD = new List<double>();
            float yh = 0;
            for (int i = 0; i < m.Count(); i++)
            {
                float dx = m.ElementAt(i).Key.X - xCenter;
                float dy = m.ElementAt(i).Key.Y - yCenter;
                float R = (float)Math.Sqrt(dx * dx + dy * dy);
                float xh = ww * i / (float)m.Count();
                yh = hh / 2 + h * 60 - 70;
                g.DrawLine(pen1, xh, yh, xh, yh - R / 3);
                LD.Add(R);
            }
            g.DrawString("#" + h, new Font("", 10), bh, ww / 2, yh - 60);

            //画最小半径:
            float minR = (float)LD.Min();
            for (int i = 0; i < m.Count(); i++)
            {
                if (LD.ElementAt(i) == minR)
                {
                    float dx = m.ElementAt(i).Key.X;
                    float dy = m.ElementAt(i).Key.Y;
                    g.DrawLine(pen1, xCenter, yCenter, dx, dy);
                }
            }

            //画最大半径:
            float maxR = (float)LD.Max();
            for (int i = 0; i < m.Count(); i++)
            {
                if (LD.ElementAt(i) == maxR)
                {
                    float dx = m.ElementAt(i).Key.X;
                    float dy = m.ElementAt(i).Key.Y;
                    g.DrawLine(pen2, xCenter, yCenter, dx, dy);
                    A[h] = (int)(toD*Math.Atan2(m.ElementAt(i).Key.Y - yCenter, m.ElementAt(i).Key.X - xCenter));//toD=180/PI
                }
            }

            DL.Add(h, LD);
            h++;
        }
    }

    //检查各集合中元素数目:
    //string s = string.Format("{0},{1},{2},{3},", DL[0].Count(),DL[1].Count(),DL[2].Count(),DL[3].Count());
    //MSG(s);

    //全部约束到0,1,2,...,359中:
    Dictionary<int, List<double>> DL360 = new Dictionary<int, List<double>>();
    foreach(var m in DL.Keys)
    {
        double[] Pm= DL[m].ToArray();
        List<double> Ld = new List<double>();
        for (int i=0;i<360;i++)
        {
            int u=(i*Pm.Length / 360);
            Ld.Add(Pm[u]);
            //g.DrawLine(pen2, 2*i, 60*m+hh/2, 2*i, 60*m+hh/2+(float)Pm[u]/4);
        }
        DL360.Add(m,Ld);
    }

    //找出最大半径所对应的角度,可作相伴偏移计算的起始位置:
    string s1 ="Angles for 0,1,2,3: "+ string.Join("|", A);
    MSG(s1);//148,80,19,-31,  148-(-31)=179[0:3]接近180度, 80-19=61[1:2]接近60度
            
    //检查各集合中元素数目:
    //string s2 = string.Format("{0},{1},{2},{3},", DL360[0].Count(), DL360[1].Count(), DL360[2].Count(), DL360[3].Count());
    //MSG(s2);

    //=======================原始的相位差(实际应用中应该有统一的起始角度)==============================
    //对比DL360[0]与DL360[3],搜索最小误差的角度偏移::
    double minStd = int.MaxValue;int uGd = 0;
    double[] d0 = DL360[0].ToArray();double[] d3 = DL360[3].ToArray();
    for (int u = 0; u < 360; u++)
    {
        double sm = 0;
        for (int i = 0; i < 360; i++)
        {
            int j =( u + i) % 360;
            sm += Math.Abs(d3[j] - d0[i]);
        }
        if (sm<=minStd)
        {
            minStd = sm;
            uGd = u;
        }
    }
    //MSG(minStd+",0--3 at :"+uGd);
    g.DrawString((int)minStd + ", for [0]--[3], AngleDiff=" + uGd, new Font("", 10), bh, ww / 2 - 70, hh - 30);

    //对比DL360[1]与DL360[2],搜索最小误差的角度偏移:
    minStd = int.MaxValue;
    double[] d1 = DL360[1].ToArray();
    double[] d2 = DL360[2].ToArray();
    for (int u = 0; u < 360; u++)
    {
        double sm = 0;
        for (int i = 0; i < 360; i++)
        {
            int j =( u + i) % 360;
            sm += Math.Abs(d2[j] - d1[i]);
        }
        if (sm<=minStd)
        {
            minStd = sm;
            uGd = u;
        }
    }
    //MSG(minStd+",1--2 at :"+uGd);
    g.DrawString( (int)minStd +", for [1]--[2], AngleDiff=" + uGd, new Font("", 10), bh, ww / 2-70, hh-50);

    pictureBox1.Image = bmp;

//自定义函数LockBits03代码如下:
        
/// <summary>
/// 边缘化与彩色分类,各类中:边缘序列化,从少绿到多绿.
/// </summary>
/// <param name="bmp">传入位图</param>
/// <returns>彩色边缘位图</returns>
private Bitmap LockBits03(Bitmap bmp)
{

    Bitmap srcBmp = bmp;// new Bitmap(bmp.Width, bmp.Height);
    edgePoints.Clear(); serialPoints.Clear();

    Bitmap dstBmp = new Bitmap(bmp.Width,bmp.Height);

    //1,生成边缘点集edgePoints:
    int k = 0;
    for (int xi = 2; xi < bmp.Width - 2; xi += 2)
    {
        for (int yi = 2; yi < bmp.Height - 2; yi += 2)
        {
            if (Math.Abs(srcBmp.GetPixel(xi - 1, yi).G - srcBmp.GetPixel(xi + 1, yi).G) > 127
                || Math.Abs(srcBmp.GetPixel(xi, yi - 1).G - srcBmp.GetPixel(xi, yi + 1).G) > 127)
            {
                edgePoints.Add(new Point(xi, yi), k);
            }
        }
    }            
            

    //2,生成序列化点集:
    int x0 =edgePoints.ElementAt(0).Key.X , y0 =edgePoints.ElementAt(0).Key.Y;//起点
    int r = 1; //搜索距离
    int m = 0; //分类标志
    int x, y;//当前搜索点坐标
    while (edgePoints.Count > 0)
    {
        if (!serialPoints.ContainsKey(new Point(x0, y0))) serialPoints.Add(new Point(x0, y0), m);
        edgePoints.Remove(new Point(x0, y0));

        //1,上线,向右:
        y = y0 - r;
        for (x = x0 - r; x < x0 + r; x++)
        {
            if (edgePoints.ContainsKey(new Point(x, y)))
            {
                x0 = x; y0 = y;
                r = 0; break;
            }
        }

        //2,右线,向下:
        x = x0 + r;
        for (y = y0 - r; y < y0 + r; y++)
        {
            if (edgePoints.ContainsKey(new Point(x, y)))
            {
                x0 = x; y0 = y;
                r = 0; break;
            }
        }


        //3,下线,向左:
        y = y0 + r;
        for (x = x0 + r; x >= x0 - r; x--)
        {
            if (edgePoints.ContainsKey(new Point(x, y)))
            {
                x0 = x; y0 = y;
                r = 0; break;
            }
        }

        //4,左线,向上:
        x = x0 - r;
        for (y = y0 + r; y >= y0 - r ; y--)
        {
            if (edgePoints.ContainsKey(new Point(x, y)))
            {
                x0 = x; y0 = y;
                r = 0; break;
            }
        }

        if (r > 20)
        {
            m++; 
        }
                    
        r++;
        if (r > srcBmp.Width *.5) break;
    }


    //3,显示:
    string txt = "";
    txt += "serialPoints.Values.Max=" + serialPoints.Values.Max();
    txt += "\r\nedgePoints.Count=" + edgePoints.Count + ",  serialPoints.Count=" + serialPoints.Count;
    int clr = 0;//绿色成分从少到多
    foreach (Point z in serialPoints.Keys)
    {
        dstBmp.SetPixel(z.X + 1, z.Y, Color.FromArgb((17 * serialPoints[z]) % 255, (clr) % 255, (31 * serialPoints[z]) % 255));
        dstBmp.SetPixel(z.X - 1, z.Y, Color.FromArgb((17 * serialPoints[z]) % 255, (clr) % 255, (31 * serialPoints[z]) % 255));
        dstBmp.SetPixel(z.X, z.Y - 1, Color.FromArgb((17 * serialPoints[z]) % 255, (clr) % 255, (31 * serialPoints[z]) % 255));
        dstBmp.SetPixel(z.X, z.Y + 1, Color.FromArgb((17 * serialPoints[z]) % 255, (clr) % 255, (31 * serialPoints[z]) % 255));
        dstBmp.SetPixel(z.X, z.Y, Color.FromArgb((17 * serialPoints[z]) % 255, (clr) % 255, (31 * serialPoints[z]) % 255));
        clr++;
    }

    int h = 0;
    for (int z = 0; z <= m; z++)
    {
        int c = serialPoints.Where(o => o.Value == z).Count();
        if (c > 400)
        {
            txt += "\r\nID:" + z + ",\tCount=" + c + "\t h=" + h;
            h++;
        }
    }

    this.textBox1.Text = txt;
    return dstBmp;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值