勇哥目前在一款尺寸测量设备中,应用到了一些算法,在这里做记录。
下面是算法用到的一些函数。
/// <summary> /// 新建一条拟合直线 /// </summary> /// <param name="linePt"></param> /// <param name="startPos"></param> /// <param name="ptLength"></param> /// <param name="issave"></param> /// <returns></returns> public Linef newFitLine(List<Pointf> linePt, int startPos, int ptLength, int issave) { var line1ReFitLine = new List<Pointf>(); var rows = new List<double>(); var cols = new List<double>(); var xary = new List<double>(); for (int i = startPos; i < ptLength; i++) { //if(i>startPos) //{ // var x = (linePt[startPos].X - linePt[i + 1].X) / (linePt[startPos].Y - linePt[i + 1].Y); // xary.Add(x); //} line1ReFitLine.Add(linePt[i]); rows.Add(linePt[i].X); cols.Add(linePt[i].Y); } var x1 = (linePt[175].X - linePt[55].X) / (linePt[175].Y - linePt[55].Y); printinfo(string.Format("\r\n重新拟合直线:\r\n{0}\r\n", work(printPointf(line1ReFitLine), 0, 0)), issave); HObject contour = new HObject(); HTuple rowbegin = new HTuple(); HTuple colbegin = new HTuple(); HTuple rowend = new HTuple(); HTuple colend = new HTuple(); HTuple nr = new HTuple(); HTuple nc = new HTuple(); HTuple dist = new HTuple(); HOperatorSet.GenContourPolygonXld(out contour, rows.ToArray(), cols.ToArray()); HOperatorSet.FitLineContourXld(contour, "tukey", -1, 0, 5, 2, out rowbegin, out colbegin, out rowend, out colend , out nr, out nc, out dist); var newFitLine1 = new Linef(new Pointf(rowbegin, colbegin, 0, 0), new Pointf(rowend, colend, 0, 0)); printinfo(string.Format("\r\n新拟合直线: startPt,endPt:\r\n {0},{1},{2},{3}" , newFitLine1.StartPoint.X.ToString("0.0000"), newFitLine1.StartPoint.Y.ToString("0.0000"), newFitLine1.EndPoint.X.ToString("0.0000"), newFitLine1.EndPoint.Y.ToString("0.0000")), issave); return newFitLine1; } /// <summary> /// 移动线条到指定的点,并返回一条新线对象 /// </summary> /// <param name="x"></param> /// <param name="y"></param> /// <param name="line"></param> /// <returns></returns> public Linef moveLineToNewObject(double x,double y,Linef line) { double x11 = 0, y11 = 0, x22 = 0, y22 = 0; MoveLine(x, y, line.StartPoint.X, line.StartPoint.Y, line.EndPoint.X, line.EndPoint.Y, out x11, out y11, out x22, out y22); //把移动后直线构造成一个新的电芯右边的直线对象 var newline = new Linef(new Pointf(x11, y11, 0, 0), new Pointf(x22, y22, 0, 0)); newline.Points = new List<Pointf>() { new Pointf(x11, y11, 0, 0), new Pointf(x22, y22, 0, 0) }; return newline; } /// <summary> /// 计算线条上最突出的点 /// </summary> /// <returns></returns> public Pointf calLineMaxPoint(Linef 参考线,List<Pointf> points) { HTuple Row2, Col2; List<double> li2 = new List<double>(); for (int i = 0; i < points.Count; i++) { Pointf p1 = points[i], p2 = 参考线.StartPoint, p3 = 参考线.EndPoint; HOperatorSet.ProjectionPl(p1.X, p1.Y, p2.X, p2.Y, p3.X, p3.Y, out Row2, out Col2); p2 = new Pointf() { X = Row2, Y = Col2 }; double dis = 0; if (m_mcConfig.EnableCalibrationType == CalibrationType.Application) dis = ApplicationGetTwoPointWorldDistance(p1, p2); else dis = HalconGetTwoPointWorldDistance(p1, p2); li2.Add(dis); } double max1 = 0; int pos1 = 0; for (int i = 0; i < li2.Count; i++) { if (li2[i] > max1) { max1 = li2[i]; pos1 = i; } } Pointf leftMaxPt = points[pos1]; //这里leftMaxPt是边上的最突出点 return leftMaxPt; } /// <summary> /// 计算点到线的距离 /// </summary> /// <param name="maxPt"></param> /// <param name="line"></param> public double calPtToLineDistance(Pointf maxPt,Linef line) { HTuple Row4, Col4; HOperatorSet.ProjectionPl(maxPt.X, maxPt.Y, line.StartPoint.X, line.StartPoint.Y, line.EndPoint.X, line.EndPoint.Y, out Row4, out Col4); var p22 = new Pointf() { X = Row4, Y = Col4 }; double dis2 = 0; if (m_mcConfig.EnableCalibrationType == CalibrationType.Application) dis2 = ApplicationGetTwoPointWorldDistance(maxPt, p22); else dis2 = HalconGetTwoPointWorldDistance(maxPt, p22); double resdata = dis2; return resdata; } /// <summary> /// 取点到线的垂线 /// </summary> /// <returns></returns> public Linef getTanline(Pointf pt,Linef line) { HTuple row=new HTuple(); HTuple col=new HTuple(); HOperatorSet.ProjectionPl(pt.X, pt.Y, line.StartPoint.X, line.StartPoint.Y, line.EndPoint.X, line.EndPoint.Y, out row, out col); return new Linef(new Pointf(row.D, col.D), new Pointf(pt.X, pt.Y)); } /// <summary> /// 取电芯顶边拟合直线的左边与右边一部分点(首尾各避开losePtnums个点) /// </summary> /// <returns></returns> public List<Pointf> getTopLineLeftRightPts(int losePtnums, int selPtnums,List<Pointf> linePts) { var line1ResPtAry = new List<Pointf>(); for (int i = 0 + losePtnums; i < selPtnums + losePtnums; i++) { line1ResPtAry.Add(linePts[i]); } for (int i = linePts.Count - selPtnums - losePtnums; i < linePts.Count - losePtnums; i++) { line1ResPtAry.Add(linePts[i]); } return line1ResPtAry; } /// <summary> /// 得到相交线的交点 /// </summary> /// <returns></returns> public Pointf getIntersectionPt(Linef line1,Linef line2) { HTuple row=new HTuple(); HTuple col=new HTuple(); HTuple ispall = new HTuple(); HOperatorSet.IntersectionLl(line1.StartPoint.X, line1.StartPoint.Y, line1.EndPoint.X, line1.EndPoint.Y, line2.StartPoint.X, line2.StartPoint.Y, line2.EndPoint.X, line2.EndPoint.Y, out row, out col,out ispall); return new Pointf(row, col, 0, 0); } /// <summary> /// 点p是否在点集合points中 /// </summary> /// <param name="points"></param> /// <param name="p"></param> /// <returns></returns> public bool isContains(Pointc[] points,Pointc p) { bool result = false; for(int i=0;i<points.Length-1;i++) { if((((points[i+1].Y<=p.Y) && (p.Y<points[i].Y)) || ((points[i].Y<=p.Y) && (p.Y<points[i+1].Y))) && (p.X<(points[i].X -points[i+1].X)*(p.Y-points[i+1].Y)/(points[i].Y-points[i+1].Y) +points[i+1].X)) { result = !result; } } return result; } /// <summary> /// 移动线段到指定点 /// </summary> /// <param name="lineStartPointRow">点行</param> /// <param name="lineStartPointCol">点列</param> /// <param name="lineStartPointRow">待移动线段起点行</param> /// <param name="lineStartPointCol">待移动线段起点列</param> /// <param name="lineEndPointRow">待移动线段终点行</param> /// <param name="lineEndPointCol">待移动线段终点列</param> /// <param name="resultLineStartPointRow">移动后线段起点行</param> /// <param name="resultLineStartPointCol">移动后线段起点列</param> /// <param name="resultLineEndPointRow">移动后线段终点行</param> /// <param name="resultLineEndPointCol">移动后线段终点列</param> private void MoveLine(double pointRow, double pointCol, double lineStartPointRow, double lineStartPointCol, double lineEndPointRow, double lineEndPointCol, out double resultLineStartPointRow, out double resultLineStartPointCol, out double resultLineEndPointRow, out double resultLineEndPointCol) { resultLineStartPointRow = 0; resultLineStartPointCol = 0; resultLineEndPointRow = 0; resultLineEndPointCol = 0; //首先得到垂点 HTuple pitchRow, pitchCol; HOperatorSet.ProjectionPl(pointRow, pointCol, lineStartPointRow, lineStartPointCol, lineEndPointRow, lineEndPointCol, out pitchRow, out pitchCol); //得到垂点距离起点A的距离 HTuple distanceA; HOperatorSet.DistancePp(pitchRow, pitchCol, lineStartPointRow, lineStartPointCol, out distanceA); //得到垂点距离终点B的距离 HTuple distanceB; HOperatorSet.DistancePp(pitchRow, pitchCol, lineEndPointRow, lineEndPointCol, out distanceB); //根据移动位置点,距离A,和线段角度求结果线段的起点 double angle; HTuple temp; HOperatorSet.AngleLx(pitchRow, pitchCol, lineStartPointRow, lineStartPointCol, out temp); angle = (double)temp; double drow = 0; double dcolumn = 0; if (0 <= angle * (180 / Math.PI) && angle * (180 / Math.PI) <= 90) { dcolumn = Math.Abs(((double)distanceA) * Math.Cos(angle)); drow = Math.Abs(((double)distanceA) * Math.Sin(angle)); resultLineStartPointRow = pointRow - drow; resultLineStartPointCol = pointCol + dcolumn; } else if (angle * (180 / Math.PI) > 90 && angle * (180 / Math.PI) <= 180) { dcolumn = ((double)distanceA) * Math.Cos((Math.PI - angle)); drow = ((double)distanceA) * Math.Sin((Math.PI - angle)); resultLineStartPointRow = pointRow - drow; resultLineStartPointCol = pointCol - dcolumn; } else if (angle * (180 / Math.PI) < 0 && angle * (180 / Math.PI) >= -90) { dcolumn = ((double)distanceA) * Math.Cos(Math.Abs(angle)); drow = ((double)distanceA) * Math.Sin(Math.Abs(angle)); resultLineStartPointRow = pointRow + drow; resultLineStartPointCol = pointCol + dcolumn; } else if (angle * (180 / Math.PI) < -90 && angle * (180 / Math.PI) >= -180) { dcolumn = Math.Abs(((double)distanceA) * Math.Cos(angle + Math.PI)); drow = Math.Abs(((double)distanceA) * Math.Sin(angle + Math.PI)); resultLineStartPointRow = pointRow + drow; resultLineStartPointCol = pointCol - dcolumn; } //根据移动位置点,距离B,和线段角度求结果线段的终点 HOperatorSet.AngleLx(pitchRow, pitchCol, lineEndPointRow, lineEndPointCol, out temp); angle = (double)temp; if (0 <= angle * (180 / Math.PI) && angle * (180 / Math.PI) <= 90) { dcolumn = Math.Abs(((double)distanceB) * Math.Cos(angle)); drow = Math.Abs(((double)distanceB) * Math.Sin(angle)); resultLineEndPointRow = pointRow - drow; resultLineEndPointCol = pointCol + dcolumn; } else if (angle * (180 / Math.PI) > 90 && angle * (180 / Math.PI) <= 180) { dcolumn = ((double)distanceB) * Math.Cos((Math.PI - angle)); drow = ((double)distanceB) * Math.Sin((Math.PI - angle)); resultLineEndPointRow = pointRow - drow; resultLineEndPointCol = pointCol - dcolumn; } else if (angle * (180 / Math.PI) < 0 && angle * (180 / Math.PI) >= -90) { dcolumn = ((double)distanceB) * Math.Cos(Math.Abs(angle)); drow = ((double)distanceB) * Math.Sin(Math.Abs(angle)); resultLineEndPointRow = pointRow + drow; resultLineEndPointCol = pointCol + dcolumn; } else if (angle * (180 / Math.PI) < -90 && angle * (180 / Math.PI) >= -180) { dcolumn = (Math.Abs((double)distanceB) * Math.Cos((angle + Math.PI))); drow = (Math.Abs((double)distanceB) * Math.Sin((angle + Math.PI))); resultLineEndPointRow = pointRow + drow; resultLineEndPointCol = pointCol - dcolumn; } } public void printinfo(string data,int sw) { try { if (sw==1) { File.AppendAllText("d:\\visionInfo.txt", data); } } catch(Exception ex) { } } public void delPrintinfo() { try { File.WriteAllText("d:\\visionInfo.txt", ""); } catch (Exception ex) { } } public string printLinef(Linef data) { var sb = new StringBuilder(); try { sb.Append(string.Format("disp_line(WindowHandle,{0},{1},{2},{3}", data.StartPoint.X, data.StartPoint.Y, data.EndPoint.X, data.EndPoint.Y)); return sb.ToString(); } catch (Exception ex) { int k1 = 0; return "none"; } } public string printPointf(List<Pointf> data) { var sb = new StringBuilder(); try { for (int i = 0; i < data.Count; i++) { sb.Append("[0]: {" + data[i].X + "," + data[i].Y + ",0,0}\r\n"); } return sb.ToString(); } catch (Exception ex) { int k1 = 0; return "none"; } } public string printlist(List<double> data) { var sb = new StringBuilder(); try { for (int i = 0; i < data.Count; i++) { sb.Append(data[i].ToString()+ "\r\n"); } return sb.ToString(); } catch (Exception ex) { int k1 = 0; return "none"; } } /// <summary> /// 排序Pointf 默认以pointf.x 由小到大 /// </summary> /// <param name="intary"></param> /// <param name="isMax"></param> public void SortPointfX(ref List<Pointf> intary, bool isMax = false) { bool isok; do { isok = false; Pointf tmp = default(Pointf); for (int i = 0; i < intary.Count - 1; i++) { if (intary[i].X > intary[i + 1].X) { tmp = intary[i]; intary[i] = intary[i + 1]; intary[i + 1] = tmp; isok = true; } } } while (isok); if (isMax) intary.Reverse(); } /// <summary> /// 排序Pointf 默认以pointf.y 由小到大 /// </summary> /// <param name="intary"></param> /// <param name="isMax"></param> public void SortPointfY(ref List<Pointf> intary, bool isMax = false) { bool isok; do { isok = false; Pointf tmp = default(Pointf); for (int i = 0; i < intary.Count - 1; i++) { if (intary[i].Y > intary[i + 1].Y) { tmp = intary[i]; intary[i] = intary[i + 1]; intary[i + 1] = tmp; isok = true; } } } while (isok); if (isMax) intary.Reverse(); }
下面是计算电芯的长,宽,极耳朵边距的算法。
else if (algorithmType == AlgorithmType.ToGetTheEarEdge) { #region 以电芯极耳边为基线,遍历电芯左边或者右边上的点到基线上的垂直距离,排序,取最大值 var line1Tmp = new List<Pointf>(); var line1ResPtAry = new List<Pointf>(); var line2Tmp = new List<Pointf>(); var line2ResPtAry = new List<Pointf>(); var data1 = getconfigdata(); int issave = (int)data1[2]; double fLimitUp = data1[13]; //极耳1上限 double fLimitDn = data1[14]; //极耳1下限 double zLimitUp = data1[15]; //极耳2上限 double zLimitDn = data1[16]; //极耳2下限 int limitUpAdjSw = (int)data1[5]; //边距调节开关 double limitValue = data1[8]; //边距调节值 //这里要保证line1是极耳的边, line2是电芯的边 line1Tmp = line1.Points; line2Tmp = line2.Points; //var topDis = calPtToLineDistance(bottomMaxPt, rightLineNewFitLine2); HTuple Row, Col; List<double> li = new List<double>(); foreach (var p in line1.Points) { /*TODO:像素坐标*/ Pointf p1 = p, p2 = rightLineNewFitLine2.StartPoint, p3 = rightLineNewFitLine2.EndPoint; /*TODO:计算三角形的垂直*/ HOperatorSet.ProjectionPl(p1.X, p1.Y, p2.X, p2.Y, p3.X, p3.Y, out Row, out Col);// 点到线的垂直 /*TODO:计算两个点的距离*/ p2 = new Pointf() { X = Row, Y = Col }; double dis = 0; if (m_mcConfig.EnableCalibrationType == CalibrationType.Application) dis = ApplicationGetTwoPointWorldDistance(p1, p2); else dis = HalconGetTwoPointWorldDistance(p1, p2); li.Add(dis); } li.Sort(); li.Reverse(); var resdata = li[0]; if (name == "耳1边距") { try { if (limitUpAdjSw == 1 && limitValue < 0) { if (resdata >= fLimitUp) { resdata += limitValue; } } } catch { resdata = li[0]; } } else if (name == "耳2边距") { try { if (limitUpAdjSw == 1 && limitValue < 0) { if (resdata >= zLimitUp) { resdata += limitValue; } } } catch { resdata = li[0]; } } return resdata; #endregion } else if (algorithmType == AlgorithmType.ToGetTheLength) { #region 上边线的X最小值构成的点数组,对底线的垂足距离 (新的取长度算法) var data1 = getconfigdata(); int selPtnums = (int)data1[0]; // 15; //左右选择多少个点 int losePtnums = (int)data1[1]; // 2; //左右选择点的头几个点不参加计算 int issave = (int)data1[2]; double lenLimitUp = data1[9]; //长度上限 double lenLimitDn = data1[10]; //长度下限 int limitUpAdjSw =(int)data1[3]; //长度调节开关 double limitValue = data1[6]; //长度调节值 var line1Tmp = new List<Pointf>(); var line1ResPtAry = new List<Pointf>(); var line2Tmp = new List<Pointf>(); var line2ResPtAry = new List<Pointf>(); line1Tmp = line1.Points; line2Tmp = line2.Points; //取顶边点到电芯左边的垂线 var topTanLine = getTanline(line1Tmp[0], leftLineNewFitLine2); printinfo(string.Format("\r\n*垂线:\r\n*{0})\r\n", printLinef(topTanLine)), issave); //取底边的最突出的点 var bottomMaxPt = calLineMaxPoint(topTanLine, line2Tmp); //移动顶边到底边最大点 var bottomNewFitLine2 = moveLineToNewObject(bottomMaxPt.X, bottomMaxPt.Y, topTanLine); //取顶边左边和右边的部分点 var topLineNewPts = getTopLineLeftRightPts(losePtnums, selPtnums, line1Tmp); printinfo(string.Format("\r\n*顶边左右选择的点:\r\n{0}\r\n", work(printPointf(topLineNewPts), 0, 0)), issave); //取顶边最突出的点 var topMaxPt = calLineMaxPoint(bottomNewFitLine2,topLineNewPts); printinfo(string.Format("\r\n*顶边最突出的点:\r\n*disp_circle(WindowHandle, {0}, {1}, 24)\r\n", topMaxPt.X, topMaxPt.Y), issave); //移动顶边到顶边的突出点 var topNewFitLine2 = moveLineToNewObject(topMaxPt.X, topMaxPt.Y, topTanLine); var topDis = calPtToLineDistance(bottomMaxPt, topNewFitLine2); printinfo(string.Format("\r\n*顶线:\r\n{0})\r\n", printLinef(topNewFitLine2)), issave); printinfo(string.Format("\r\n*底线:\r\n{0})\r\n", printLinef(bottomNewFitLine2)), issave); //求顶线与左线的交点 var jd1=getIntersectionPt(topNewFitLine2, leftLineNewFitLine2); //求底线与左线的交点 var jd2 = getIntersectionPt(bottomNewFitLine2, leftLineNewFitLine2); //两个交点的新线条 var jdline = new Linef(new Pointf(jd1.X, jd1.Y), new Pointf(jd2.X, jd2.Y)); printinfo(string.Format("\r\n*左线加长线:\r\n{0})\r\n", printLinef(jdline)), issave); //求顶线与右线的交点 jd1 = getIntersectionPt(topNewFitLine2, rightLineNewFitLine2); //求底线与右线的交点 jd2 = getIntersectionPt(bottomNewFitLine2, rightLineNewFitLine2); //两个交点的新线条 jdline = new Linef(new Pointf(jd1.X, jd1.Y), new Pointf(jd2.X, jd2.Y)); printinfo(string.Format("\r\n*右线加长线:\r\n{0})\r\n", printLinef(jdline)), issave); //上限=75.85 上限减limitValue=75.79 if (limitUpAdjSw == 1 && limitValue < 0) { if (topDis >= lenLimitUp) { topDis += limitValue; } } return topDis; #endregion } else if (algorithmType == AlgorithmType.ToGetTheWidth) { //delPrintinfo(); var data1 = getconfigdata(); var line1Tmp = new List<Pointf>(); var line1ResPtAry = new List<Pointf>(); var line2Tmp = new List<Pointf>(); var line2ResPtAry = new List<Pointf>(); List<double> li = new List<double>(); double widthLimitUp = data1[11]; //宽度上限 double widthLimitDn = data1[12]; //宽度下限 int limitUpAdjSw = (int)data1[4]; //宽度调节开关 double limitValue = data1[7]; //宽度调节值 int leftSpace =(int)data1[17]; int rightSpace =(int) data1[18]; //本算法规定是用电芯左边的点和电芯右边的拟合直线做垂足距离的计算 //这里line1要保证是电芯右边, line2是电芯左边 line1Tmp = line1.Points; line2Tmp = line2.Points; widthMid = line2Tmp[line2.Points.Count / 2]; //取电芯左边线的中间部分拟合出一条新直线 var leftLineNewFitLine = newFitLine(line2Tmp, leftSpace, line2Tmp.Count - rightSpace, 0); //移动到右边的中间点上去 var newline1 = moveLineToNewObject(line1Tmp[100].X, line1Tmp[100].Y, leftLineNewFitLine); //计算左边线上最突出的点 var leftLineMaxPt = calLineMaxPoint(newline1, line2Tmp); //移动leftLineNewFitLine到突出点上去 leftLineNewFitLine2 = moveLineToNewObject(leftLineMaxPt.X, leftLineMaxPt.Y, leftLineNewFitLine); //计算右边线上最突出的点 var rightLineMaxPt = calLineMaxPoint(leftLineNewFitLine,line1Tmp); rightLineNewFitLine2 = moveLineToNewObject(rightLineMaxPt.X, rightLineMaxPt.Y, leftLineNewFitLine); var widthDis = calPtToLineDistance(rightLineMaxPt, leftLineNewFitLine2); int issave = 1; printinfo(string.Format("\r\n*左线:\r\n*{0})\r\n", printLinef(leftLineNewFitLine2)), issave); printinfo(string.Format("\r\n*右线:\r\n*{0})\r\n", printLinef(rightLineNewFitLine2)), issave); if (limitUpAdjSw == 1 && limitValue < 0) { if (widthDis >= widthLimitUp) { widthDis += limitValue; } } return widthDis; }
上面的算法想要达到目的如下:
A线是取该边中间部分点拟合成的一条新的线条,并且过A边的最突出点
B线是垂直于A线且过B边上最突出点的直线。
C线是A线的平行线,且过C边上最突出的点的直线
D线是垂直于A线且过D边上最突出点的直线
即生产一个过电芯四个边最突出点的矩形,然后计算长宽
上面算法的考虑,主要是要处理异形电池的问题。
所谓异形电池,是指在电池的四边都有随机的凸凹的可能性,形状不规则的电池。
如下图所示。
---------------------
作者:hackpig
来源:www.skcircle.com
版权声明:本文为博主原创文章,转载请附上博文链接!
本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:


