/*
工业视觉_63:通过Susan角点进行旋转无关性匹配
未来20年里,最火的行业之一:机器换人,智能使用机器人,改造传统工厂成智慧工厂!
工业视觉,目标很明确:"快,准,稳"三个字. 快:开发快,运行速度快;准:高精度;稳:稳健可靠
所以,目前"机器换人"项目大多采用工控电脑,搭建 Windows7+VS2019+EMGU(或AForge+Accord),这一方案见效最快.
Halcon,Emgu的视觉库都很强大,而AForge+Accord库更全面,更丰富(如:数学,人工智能,机器学习).
散点集合,可来自角点,或称关键点,特征点,机器视觉中有丰富的类型.本文本取Susan角点进行旋转无关性匹配.
因为大多机床,或机器人在视觉下工作,每一次工件流动过来位置与角度常常有变,机器人要抓住它,并装配到另一处,数学计算很重多.
这就要提取工件的一些几何特征,以保证在旋转后继续能正确识别型号,确定位置与方位角.以便使机器人工作.
AI训练也是很多人热衷的.但是本文提出的思路是,找到中心(质心),计算惯性矩(中心二阶矩)找到方位角,依各扇形中的点数来进行工件的匹配与定位(与姿态).
也就是对每一工件图,生成与旋转无关的一系列值,构成特征向量,再分析这些向量之间的关系(如欧氏距离,相关系数等).
在主轴明显且角点比较稳定时,本方案比训练N次方便些.若打光方案正确,则提取的角点也更稳定.
在使用机器人时,应该对相机进行纠偏(消除畸变),标定,打光等处理,以进一步提高精度.
--------- 编撰: 项道德(微信:daode1212),2021-07-13
*/
//载入照片:
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, 120), 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();
List<IntPoint> LP = scd.ProcessImage(bmp);
//绘制各角点:
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 - 1, p.Y - 1, 2, 2);
k++;
}
MSG("绿色:Susan角点检测:" + LP.Count + "个. ");
//将所有角点划分为四个组,并找到各组的中心:
List<PointF> LP0 = new List<PointF>();
List<PointF> LP1 = new List<PointF>();
List<PointF> LP2 = new List<PointF>();
List<PointF> LP3 = new List<PointF>();
foreach (var p in LP)
{
PointF p2 = new PointF(p.X, p.Y);
if (p.X < ww / 2 && p.Y < hh / 2)
{
LP0.Add(p2);
}
if (p.X > ww / 2 && p.Y < hh / 2)
{
LP1.Add(p2);
}
if (p.X < ww / 2 && p.Y > hh / 2)
{
LP2.Add(p2);
}
if (p.X > ww / 2 && p.Y > hh / 2)
{
LP3.Add(p2);
}
}
float X0 = LP0.Average(o => o.X); float Y0 = LP0.Average(o => o.Y);
float X1 = LP1.Average(o => o.X); float Y1 = LP1.Average(o => o.Y);
float X2 = LP2.Average(o => o.X); float Y2 = LP2.Average(o => o.Y);
float X3 = LP3.Average(o => o.X); float Y3 = LP3.Average(o => o.Y);
//各组平移变换:
List<PointF> LP0b = new List<PointF>();
List<PointF> LP1b = new List<PointF>();
List<PointF> LP2b = new List<PointF>();
List<PointF> LP3b = new List<PointF>();
for (int i = 0; i < LP0.Count; i++)
{
PointF p = LP0[i];
p.X -= X0; p.Y -= Y0;
LP0b.Add(p);
}
for (int i = 0; i < LP1.Count; i++)
{
PointF p = LP1[i];
p.X -= X1; p.Y -= Y1;
LP1b.Add(p);
}
for (int i = 0; i < LP2.Count; i++)
{
PointF p = LP2[i];
p.X -= X2; p.Y -= Y2;
LP2b.Add(p);
}
for (int i = 0; i < LP3.Count; i++)
{
PointF p = LP3[i];
p.X -= X3; p.Y -= Y3;
LP3b.Add(p);
}
//============计算产品3二阶矩,以获取方位角=============
bh = new SolidBrush(Color.FromArgb(155, 0, 155));
//计算各组的二阶矩,以确定方位角:
double xM3 = 0, yM3 = 0;
for (int i = 0; i < LP3b.Count; i++)
{
PointF p = LP3b[i];
xM3 += Math.Sign(p.X) * (p.X * p.X);//
yM3 += Math.Sign(p.Y) * (p.Y * p.Y);//
}
double ori3 = Math.Atan2(yM3, xM3);
g.DrawLine(pen0, X3, Y3, (float)(X3 + 50 * Math.Cos(ori3)), (float)(Y3 + 50 * Math.Sin(ori3)));
g.FillEllipse(bh, X3 - 8, Y3 - 8, 16, 16);
//============计算产品2二阶矩,以获取方位角=============
double xM2 = 0, yM2 = 0;
for (int i = 0; i < LP2b.Count; i++)
{
PointF p = LP2b[i];
xM2 += Math.Sign(p.X) * (p.X * p.X);//
yM2 += Math.Sign(p.Y) * (p.Y * p.Y);//
}
double ori2 = Math.Atan2(yM2, xM2);
g.DrawLine(pen0, X2, Y2, (float)(X2 + 50 * Math.Cos(ori2)), (float)(Y2 + 50 * Math.Sin(ori2)));
g.FillEllipse(bh, X2 - 8, Y2 - 8, 16, 16);
//============计算产品1二阶矩,以获取方位角=============
double xM1 = 0, yM1 = 0;
for (int i = 0; i < LP1b.Count; i++)
{
PointF p = LP1b[i];
xM1 += Math.Sign(p.X) * (p.X * p.X);//
yM1 += Math.Sign(p.Y) * (p.Y * p.Y);//
}
double ori1 = Math.Atan2(yM1, xM1);
g.DrawLine(pen0, X1, Y1, (float)(X1 + 50 * Math.Cos(ori1)), (float)(Y1 + 50 * Math.Sin(ori1)));
g.FillEllipse(bh, X1 - 8, Y1 - 8, 16, 16);
//============计算产品0二阶矩,以获取方位角=============
double xM0 = 0, yM0 = 0;
for (int i = 0; i < LP0b.Count; i++)
{
PointF p = LP0b[i];
xM0 += Math.Sign(p.X) * (p.X * p.X);//
yM0 += Math.Sign(p.Y) * (p.Y * p.Y);//
}
double ori0 = Math.Atan2(yM0, xM0);
g.DrawLine(pen0, X0, Y0, (float)(X0 + 50 * Math.Cos(ori0)), (float)(Y0 + 50 * Math.Sin(ori0)));
g.FillEllipse(bh, X0 - 8, Y0 - 8, 16, 16);
//----------------产品0------------------
//依方位角进行统计,计算误差或相似性:
float[] s0 = new float[360];//记录样品特征
for (int i = 0; i < LP0b.Count; i++)
{
int q0 = (int)(toD * (-ori0 + Math.Atan2(LP0b[i].Y, LP0b[i].X) + 2 * PI) % 360);
int q1 = (q0 / 30);
s0[q1]++;
}
//绘制条形统计图:
for (int h = 0; h < 12; h++)
{
g.DrawLine(pen1, 0, 10 + 8 * h, 4 * s0[h], 10 + 8 * h);
}
//----------------产品1,计算各扇形内点的数目------------------
float[] s1 = new float[360];//记录样品特征
for (int i = 0; i < LP1b.Count; i++)
{
int q0 = (int)(toD * (-ori1 + Math.Atan2(LP1b[i].Y, LP1b[i].X) + 2 * PI) % 360);
int q1 = (q0 / 30);
s1[q1]++;
}
//绘制条形统计图:
for (int h = 0; h < 12; h++)
{
g.DrawLine(pen1, ww / 2, 10 + 8 * h, ww / 2 + 4 * s1[h], 10 + 8 * h);
}
//----------------产品2,计算各扇形内点的数目------------------
float[] s2 = new float[360];//记录样品特征
for (int i = 0; i < LP2b.Count; i++)
{
int q0 = (int)(toD * (-ori2 + Math.Atan2(LP2b[i].Y, LP2b[i].X) + 2 * PI) % 360);
int q1 = (q0 / 30);
s2[q1]++;
}
//绘制条形统计图:
for (int h = 0; h < 12; h++)
{
g.DrawLine(pen1, 0, hh / 2 + 10 + 8 * h, 4 * s2[h], hh / 2 + 10 + 8 * h);
}
//----------------产品3,计算各扇形内点的数目------------------
float[] s3 = new float[360];//记录样品特征
for (int i = 0; i < LP3b.Count; i++)
{
int q0 = (int)(toD * (-ori3 + Math.Atan2(LP3b[i].Y, LP3b[i].X) + 2 * PI) % 360);
int q1 = (q0 / 30);
s3[q1]++;
}
//绘制条形统计图:
for (int h = 0; h < 12; h++)
{
g.DrawLine(pen1, ww / 2, hh / 2 + 10 + 8 * h, ww / 2 + 4 * s3[h], hh / 2 + 10 + 8 * h);
}
//计算向量的曼哈顿距离(也可计算相关系数或欧氏距离的):====================================
float std1 = 0, std2 = 0, std3 = 0, std23 = 0;
for (int h = 0; h < 12; h++)
{
std1 += Math.Abs(s1[h] - s0[h]);
std2 += Math.Abs(s2[h] - s0[h]);
std3 += Math.Abs(s3[h] - s0[h]);
std23 += Math.Abs(s3[h] - s2[h]);
}
std1 /= 12; std2 /= 12; std3 /= 12; std23 /= 12;
//计算向量的相关系数:====================================
float sov1 = 0, sov2 = 0, sov3 = 0, sov23 = 0;
float ds0 = 0, ds1 = 0, ds2 = 0, ds3 = 0;
for (int h = 0; h < 12; h++)
{
sov1 += (s1[h] * s0[h]); ds0 += (s0[h] * s0[h]);
sov2 += (s2[h] * s0[h]); ds1 += (s1[h] * s1[h]);
sov3 += (s3[h] * s0[h]); ds2 += (s2[h] * s2[h]);
sov23 += (s3[h] * s2[h]); ds3 += (s3[h] * s3[h]);
}
sov1 = sov1 / (float)Math.Sqrt(ds0 * ds1);
sov2 = sov2 / (float)Math.Sqrt(ds0 * ds2);
sov3 = sov3 / (float)Math.Sqrt(ds0 * ds3);
sov23 = sov23 / (float)Math.Sqrt(ds2 * ds3);
//显示结果:==============================
textBox1.Text = string.Format("后三图与第一图的误差为:\r\n{0:0.##},{1:0.##},{2:0.##}", std1, std2, std3);
textBox1.Text += string.Format("\r\n三,四两图的误差为:\r\n{0:0.##}", std23);
textBox1.Text += string.Format("\r\n后三图与第一图的相关系数为:\r\n{0:0.##},{1:0.##},{2:0.##}", sov1, sov2, sov3);
textBox1.Text += string.Format("\r\n三,四两图的相关系数为:\r\n{0:0.##}", sov23);
/* 文本框中将显示:
后三图与第一图的误差为:
10.83,8.42,3.5
三,四两图的误差为:
10.58
后三图与第一图的相关系数为:
0.9,0.95,0.99
三,四两图的相关系数为:
0.92
*/
g.DrawString("通过Susan角点进行旋转无关性匹配", new Font("", 10), bh, bmp.Width / 3,hh/2-30);
pictureBox1.Image = bmp;
上图模拟各方向的工件.
下图是视觉处理后的图: