/*
工业视觉_73:机器人喷涂_工件区域喷涂最佳路径生成
未来20年里,机器换人,智能使用机器人,改造传统工厂成智慧工厂,将成为最火的行业之一.
工业视觉,目标很明确:"快,准,稳"三个字. 快:开发快,运行速度快;准:高精度;稳:稳健可靠
使用高级语言做工程主要优势在:已经有丰富的数据结构和成熟的类型库,如List,Dictionary,Lambda,Accord,...
所以,目前"机器换人"项目大多采用工控电脑,搭建 Windows7+VS2019+EMGU(或AForge+Accord),这一方案见效最快.
Halcon,Emgu的视觉库都很强大,而AForge+Accord库更全面,更丰富(如:数学,人工智能,机器学习).
机器人喷涂,是指:喷漆,喷胶,喷塑.
经相机返回的图像,包含了不同工件的颜色与形状.
我们可以通过视觉对工件区域生成运动路径用机器人喷之.
二维的,用四轴机器人即可实现.
三维的,用深度相机采集点云数据,计算法向量,计算机器人的空间姿态(如欧拉角,旋转矩阵,四元数等),用六轴机器人喷之.
所有相机,均要做好纠偏与标定等工作,以便与机器人进行精确匹配.
本文系作者在"安吉八塔机器人公司"产品线研发中的拓展与随笔.
[已经发布,链接:...]
--------- 编撰: 项道德(微信:daode1212),2021-07-24
*/
//一,基本图像与绘制工具准备:##########################################################
pictureBox1.Image = Image.FromFile("2D147.jpg");//多对象,白色为背景
MSG("马上呈现: 工件区域喷涂最佳路径生成,黑白处理");//自定义函数"MSG()"即"MessageBox.Show()"
Bitmap bmp = (Bitmap)(pictureBox1.Image);
int ww = bmp.Width, hh = bmp.Height;
string txtAll = "";
Graphics g = Graphics.FromImage(bmp);
SolidBrush bh0 = new SolidBrush(Color.FromArgb(0, 0, 0));
SolidBrush bh1 = new SolidBrush(Color.FromArgb(161, 0, 161));
SolidBrush bh2 = new SolidBrush(Color.FromArgb(111, 111, 0));
SolidBrush bh3 = new SolidBrush(Color.FromArgb(211, 211, 211));
Pen pen0 = new Pen(Color.FromArgb(182, 122, 182), 1);
Pen pen1 = new Pen(Color.FromArgb(255, 122, 0), 1);
Pen pen2 = new Pen(Color.FromArgb(0, 110, 255), 1);
Pen pen3 = new Pen(Color.FromArgb(0, 255, 110), 1);
//二,运用指针,将边缘线膨胀:
List<Point> LP = new List<Point>();
List<Point> LP2 = new List<Point>();
//为减少计算,使用八位灰度位图: PixelFormat.Format8bppIndexed
unsafe
{
BitmapData data = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
var ptr = (byte*)data.Scan0.ToPointer();
int wBit = data.Stride;
for (int j = 1; j < data.Width - 1; j +=2)
{
for (int i = 1; i < data.Height - 1; i +=2)
{
byte up = ptr[(i - 1) * wBit + j];
byte dn = ptr[(i + 1) * wBit + j];
byte lt = ptr[i * wBit + (j - 1)];
byte rt = ptr[i * wBit + (j + 1)];
byte me = ptr[i * wBit + j];
//边缘及有彩色(ptr[i * wBit + j]>16)的:
if (Math.Abs(up - dn) + Math.Abs(lt - rt) > 127|| me >16)
{
LP.Add(new Point(j, i));
}
}
}
bmp.UnlockBits(data);
}
foreach (var p in LP)
{
g.FillEllipse(bh0, p.X - 5, p.Y - 5, 10, 10);
}
pictureBox1.Image = bmp;
MSG("马上呈现:黑区点集");
//三,针对黑色区域:
//为减少计算,使用八位灰度位图: PixelFormat.Format8bppIndexed
unsafe
{
BitmapData data = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
var ptr = (byte*)data.Scan0.ToPointer();
int wBit = data.Stride;
for (int j = 1; j < data.Width - 1; j += 16)
{
for (int i = 1; i < data.Height - 1; i += 16)
{
bool b00 = ptr[(i - 1) * wBit + (j - 1)] == 0;
bool b01 = ptr[(i - 1) * wBit + j] == 0;
bool b02 = ptr[(i - 1) * wBit + (j + 1)] == 0;
bool b10 = ptr[i * wBit + (j - 1)] == 0;
bool b11 = ptr[i * wBit + j] == 0;
bool b12 = ptr[i * wBit + (j + 1)] == 0;
bool b20 = ptr[(i + 1) * wBit + (j - 1)] == 0;
bool b21 = ptr[(i + 1) * wBit + j] == 0;
bool b22 = ptr[(i + 1) * wBit + (j + 1)] == 0;
if ((b00 && b01 && b02 && b10 && b11 && b12 && b20 && b21 && b22))//框内全为黑
{
LP2.Add(new Point(j, i));
}
}
}
bmp.UnlockBits(data);
}
foreach (var p in LP2)
{
g.FillEllipse(bh1, p.X - 2, p.Y - 2, 4, 4);
}
txtAll = "LP.Count():" + LP.Count() + ",LP2.Count():" + LP2.Count();
textBox1.Text = txtAll;
pictureBox1.Image = bmp;
MSG("马上呈现: 双层循环距离对比算法生成有序点集");
//四,生成有序点集d1---双层循环距离对比算法:
Dictionary<Point, int> d1 = new Dictionary<Point, int>();//字典,用于记录新的集合
Stack<Point> STK = new Stack<Point>();//堆栈,用于记下当前点的邻近的点
int sx = LP2.ElementAt(0).X; int sy = LP2.ElementAt(0).Y; //搜索的开始点
STK.Push(new Point(sx, sy));// 堆栈初始点
int xg = int.MaxValue; int yg = int.MaxValue; //记录搜索时的最近点,初始时,应移动搜索区间之外
double disMin = int.MaxValue; //记录最近距离
double dis;//计算当前距离
double dMaxForSplit = 24;//不同聚类之间的距离,影响分类数目
int m = 0;//聚类的编号
//迭代搜索:
while (LP2.Count > 0)
{
if (LP2.Contains(new Point(sx, sy))) LP2.Remove(new Point(sx, sy));
foreach (Point u in LP2)
{
int xi = u.X;
int yi = u.Y;
//dis = Math.Sqrt(0.75*(xi - sx) * (xi - sx) + (yi - sy) * (yi - sy)); //横线优先
//dis = Math.Sqrt((xi - sx) * (xi - sx) + 0.75*(yi - sy) * (yi - sy)); //竖线优先
dis = Math.Sqrt((xi - sx) * (xi - sx) + (yi - sy) * (yi - sy)); //不限制
if (dis <= disMin)
{
disMin = dis; //更新最近距离值
xg = xi; yg = yi; //更新搜索时的最近点
if (dis < dMaxForSplit)
{
STK.Push(u);//符合聚类之间的距离的,先压入到堆栈中
}
}
}
//预搬运,以提高速度:
if (STK.Count > 0)
{
foreach (var u in STK)
{
LP2.Remove(u);
if (!d1.ContainsKey(u)) d1.Add(u, m);
}
}
if (disMin <= dMaxForSplit)//符合聚类之间的距离的,更新到D1中,继续
{
sx = xg; sy = yg;
if (!d1.ContainsKey(new Point(xg, yg))) d1.Add(new Point(xg, yg), m);
}
else //不符合聚类之间的距离的,到堆栈中取出一元,继续
{
if (STK.Count > 0)
{
Point Pg = STK.Pop();
sx = Pg.X; sy = Pg.Y;
}
}
//当距离超过聚类之间的距离,且堆栈也空时,新一聚类开始:
if (disMin > dMaxForSplit && STK.Count == 0)
{
m++; sx = xg; sy = yg;
if (!d1.ContainsKey(new Point(xg, yg))) d1.Add(new Point(xg, yg), m);
}
disMin = int.MaxValue; //最近距离初始化
}
Point p0 = new Point(sx, sy);
foreach (Point z in d1.Keys)
{
pen0 = new Pen(Color.FromArgb((137 * d1[z]) % 255, 255 - (67 * d1[z]) % 255, (39 * d1[z]) % 255), 8);
double ds = Math.Sqrt((sx - z.X) * (sx - z.X) + (sy - z.Y) * (sy - z.Y));
if (ds < 24)//机器人开启喷枪
{
g.DrawLine(pen0, sx, sy, z.X, z.Y);
}
else //机器人关闭喷枪
{
bh0 = new SolidBrush(Color.FromArgb((137 * d1[z]) % 255, 255 - (67 * d1[z]) % 255, (39 * d1[z]) % 255));
g.FillEllipse(bh0, sx - 4, sy - 4, 8, 8);
g.FillEllipse(bh0, z.X - 4, z.Y - 4, 8, 8);
pen0 = new Pen(Color.LightGray, 1);
g.DrawLine(pen0, sx, sy, z.X, z.Y);
}
sx = z.X; sy = z.Y;
}
//显示结果:
textBox1.Text = txtAll;
pictureBox1.Image = bmp;