/// <summary> /// Check if the specified set of points form a circle shape. /// </summary> /// /// <param name="edgePoints">Shape's points to check.</param> /// <param name="center">Receives circle's center on successful return.</param> /// <param name="radius">Receives circle's radius on successful return.</param> /// /// <returns>Returns <see langword="true"/> if the specified set of points form a /// circle shape or <see langword="false"/> otherwise.</returns> /// /// <remarks><para><note>Circle shape must contain at least 8 points to be recognized. /// The method returns <see langword="false"/> always, of number of points in the specified /// shape is less than 8.</note></para></remarks> /// public bool IsCircle(List <IntPoint> edgePoints, out Point center, out float radius) { // make sure we have at least 8 points for curcle shape if (edgePoints.Count < 8) { center = new Point(0, 0); radius = 0; return(false); } // get bounding rectangle of the points list IntPoint minXY, maxXY; PointsCloud.GetBoundingRectangle(edgePoints, out minXY, out maxXY); // get cloud's size IntPoint cloudSize = maxXY - minXY; // calculate center point center = minXY + (Point)cloudSize / 2; radius = ((float)cloudSize.X + cloudSize.Y) / 4; // calculate mean distance between provided edge points and estimated circle’s edge float meanDistance = 0; for (int i = 0, n = edgePoints.Count; i < n; i++) { meanDistance += (float)Math.Abs(center.DistanceTo(edgePoints[i]) - radius); } meanDistance /= edgePoints.Count; float maxDitance = Math.Max(minAcceptableDistortion, ((float)cloudSize.X + cloudSize.Y) / 2 * relativeDistortionLimit); return(meanDistance <= maxDitance); }
/// <summary> /// Find corners of quadrilateral or triangular area, which contains the specified collection of points. /// </summary> /// /// <param name="cloud">Collection of points to search quadrilateral for.</param> /// /// <returns>Returns a list of 3 or 4 points, which are corners of the quadrilateral or /// triangular area filled by specified collection of point. The first point in the list /// is the point with lowest X coordinate (and with lowest Y if there are several points /// with the same X value). The corners are provided in counter clockwise order /// (<a href="http://en.wikipedia.org/wiki/Cartesian_coordinate_system">Cartesian /// coordinate system</a>).</returns> /// /// <remarks><para>The method makes an assumption that the specified collection of points /// form some sort of quadrilateral/triangular area. With this assumption it tries to find corners /// of the area.</para> /// /// <para><note>The method does not search for <b>bounding</b> quadrilateral/triangular area, /// where all specified points are <b>inside</b> of the found quadrilateral/triangle. Some of the /// specified points potentially may be outside of the found quadrilateral/triangle, since the /// method takes corners only from the specified collection of points, but does not calculate such /// to form true bounding quadrilateral/triangle.</note></para> /// /// <para>See <see cref="QuadrilateralRelativeDistortionLimit"/> property for additional information.</para> /// </remarks> /// public static List <IntPoint> FindQuadrilateralCorners(IEnumerable <IntPoint> cloud) { // quadrilateral's corners List <IntPoint> corners = new List <IntPoint>( ); // get bounding rectangle of the points list IntPoint minXY, maxXY; PointsCloud.GetBoundingRectangle(cloud, out minXY, out maxXY); // get cloud's size IntPoint cloudSize = maxXY - minXY; // calculate center point IntPoint center = minXY + cloudSize / 2; // acceptable deviation limit float distortionLimit = quadrilateralRelativeDistortionLimit * (cloudSize.X + cloudSize.Y) / 2; // get the furthest point from (0,0) IntPoint point1 = PointsCloud.GetFurthestPoint(cloud, center); // get the furthest point from the first point IntPoint point2 = PointsCloud.GetFurthestPoint(cloud, point1); corners.Add(point1); corners.Add(point2); // get two furthest points from line IntPoint point3, point4; float distance3, distance4; PointsCloud.GetFurthestPointsFromLine(cloud, point1, point2, out point3, out distance3, out point4, out distance4); // ideally points 1 and 2 form a diagonal of the // quadrilateral area, and points 3 and 4 form another diagonal // but if one of the points (3 or 4) is very close to the line // connecting points 1 and 2, then it is one the same line ... // which means corner was not found. // in this case we deal with a trapezoid or triangle, where // (1-2) line is one of it sides. // another interesting case is when both points (3) and (4) are // very close the (1-2) line. in this case we may have just a flat // quadrilateral. if ( ((distance3 >= distortionLimit) && (distance4 >= distortionLimit)) || ((distance3 < distortionLimit) && (distance3 != 0) && (distance4 < distortionLimit) && (distance4 != 0))) { // don't add one of the corners, if the point is already in the corners list // (this may happen when both #3 and #4 points are very close to the line // connecting #1 and #2) if (!corners.Contains(point3)) { corners.Add(point3); } if (!corners.Contains(point4)) { corners.Add(point4); } } else { // it seems that we deal with kind of trapezoid, // where point 1 and 2 are on the same edge IntPoint tempPoint = (distance3 > distance4) ? point3 : point4; // try to find 3rd point PointsCloud.GetFurthestPointsFromLine(cloud, point1, tempPoint, out point3, out distance3, out point4, out distance4); bool thirdPointIsFound = false; if ((distance3 >= distortionLimit) && (distance4 >= distortionLimit)) { if (point4.DistanceTo(point2) > point3.DistanceTo(point2)) { point3 = point4; } thirdPointIsFound = true; } else { PointsCloud.GetFurthestPointsFromLine(cloud, point2, tempPoint, out point3, out distance3, out point4, out distance4); if ((distance3 >= distortionLimit) && (distance4 >= distortionLimit)) { if (point4.DistanceTo(point1) > point3.DistanceTo(point1)) { point3 = point4; } thirdPointIsFound = true; } } if (!thirdPointIsFound) { // failed to find 3rd edge point, which is away enough from the temp point. // this means that the clound looks more like triangle corners.Add(tempPoint); } else { corners.Add(point3); // try to find 4th point float tempDistance; PointsCloud.GetFurthestPointsFromLine(cloud, point1, point3, out tempPoint, out tempDistance, out point4, out distance4); if ((distance4 >= distortionLimit) && (tempDistance >= distortionLimit)) { if (tempPoint.DistanceTo(point2) > point4.DistanceTo(point2)) { point4 = tempPoint; } } else { PointsCloud.GetFurthestPointsFromLine(cloud, point2, point3, out tempPoint, out tempDistance, out point4, out distance4); if ((tempPoint.DistanceTo(point1) > point4.DistanceTo(point1)) && (tempPoint != point2) && (tempPoint != point3)) { point4 = tempPoint; } } if ((point4 != point1) && (point4 != point2) && (point4 != point3)) { corners.Add(point4); } } } // put the point with lowest X as the first for (int i = 1, n = corners.Count; i < n; i++) { if ((corners[i].X < corners[0].X) || ((corners[i].X == corners[0].X) && (corners[i].Y < corners[0].Y))) { IntPoint temp = corners[i]; corners[i] = corners[0]; corners[0] = temp; } } // sort other points in counter clockwise order float k1 = (corners[1].X != corners[0].X) ? ((float)(corners[1].Y - corners[0].Y) / (corners[1].X - corners[0].X)) : ((corners[1].Y > corners[0].Y) ? float.PositiveInfinity : float.NegativeInfinity); float k2 = (corners[2].X != corners[0].X) ? ((float)(corners[2].Y - corners[0].Y) / (corners[2].X - corners[0].X)) : ((corners[2].Y > corners[0].Y) ? float.PositiveInfinity : float.NegativeInfinity); if (k2 < k1) { IntPoint temp = corners[1]; corners[1] = corners[2]; corners[2] = temp; float tk = k1; k1 = k2; k2 = tk; } if (corners.Count == 4) { float k3 = (corners[3].X != corners[0].X) ? ((float)(corners[3].Y - corners[0].Y) / (corners[3].X - corners[0].X)) : ((corners[3].Y > corners[0].Y) ? float.PositiveInfinity : float.NegativeInfinity); if (k3 < k1) { IntPoint temp = corners[1]; corners[1] = corners[3]; corners[3] = temp; float tk = k1; k1 = k3; k3 = tk; } if (k3 < k2) { IntPoint temp = corners[2]; corners[2] = corners[3]; corners[3] = temp; float tk = k2; k2 = k3; k3 = tk; } } return(corners); }
/// <summary> /// Check if a shape specified by the set of points fits a convex polygon /// specified by the set of corners. /// </summary> /// /// <param name="edgePoints">Shape's points to check.</param> /// <param name="corners">Corners of convex polygon to check fitting into.</param> /// /// <returns>Returns <see langword="true"/> if the specified shape fits /// the specified convex polygon or <see langword="false"/> otherwise.</returns> /// /// <remarks><para>The method checks if the set of specified points form the same shape /// as the set of provided corners.</para></remarks> /// public bool CheckIfPointsFitShape(List <IntPoint> edgePoints, List <IntPoint> corners) { int cornersCount = corners.Count; // lines coefficients (for representation as y(x)=k*x+b) float[] k = new float[cornersCount]; float[] b = new float[cornersCount]; float[] div = new float[cornersCount]; // precalculated divisor bool[] isVert = new bool[cornersCount]; for (int i = 0; i < cornersCount; i++) { IntPoint currentPoint = corners[i]; IntPoint nextPoint = (i + 1 == cornersCount) ? corners[0] : corners[i + 1]; if (!(isVert[i] = nextPoint.X == currentPoint.X)) { k[i] = (float)(nextPoint.Y - currentPoint.Y) / (nextPoint.X - currentPoint.X); b[i] = currentPoint.Y - k[i] * currentPoint.X; div[i] = (float)Math.Sqrt(k[i] * k[i] + 1); } } // calculate distances between edge points and polygon sides float meanDistance = 0; for (int i = 0, n = edgePoints.Count; i < n; i++) { float minDistance = float.MaxValue; for (int j = 0; j < cornersCount; j++) { float distance = 0; if (!isVert[j]) { distance = (float)Math.Abs((k[j] * edgePoints[i].X + b[j] - edgePoints[i].Y) / div[j]); } else { distance = Math.Abs(edgePoints[i].X - corners[j].X); } if (distance < minDistance) { minDistance = distance; } } meanDistance += minDistance; } meanDistance /= edgePoints.Count; // get bounding rectangle of the corners list IntPoint minXY, maxXY; PointsCloud.GetBoundingRectangle(corners, out minXY, out maxXY); IntPoint rectSize = maxXY - minXY; float maxDitance = Math.Max(minAcceptableDistortion, ((float)rectSize.X + rectSize.Y) / 2 * relativeDistortionLimit); return(meanDistance <= maxDitance); }
/// <summary> /// Check sub type of a convex polygon. /// </summary> /// /// <param name="corners">Corners of the convex polygon to check.</param> /// /// <returns>Return detected sub type of the specified shape.</returns> /// /// <remarks><para>The method check corners of a convex polygon detecting /// its subtype. Polygon's corners are usually retrieved using <see cref="IsConvexPolygon"/> /// method, but can be any list of 3-4 points (only sub types of triangles and /// quadrilateral are checked).</para> /// /// <para>See <see cref="AngleError"/> and <see cref="LengthError"/> properties, /// which set acceptable errors for polygon sub type checking.</para> /// </remarks> /// public PolygonSubType CheckPolygonSubType(List <IntPoint> corners) { PolygonSubType subType = PolygonSubType.Unknown; // get bounding rectangle of the points list IntPoint minXY, maxXY; PointsCloud.GetBoundingRectangle(corners, out minXY, out maxXY); // get cloud's size IntPoint cloudSize = maxXY - minXY; float maxLengthDiff = lengthError * (cloudSize.X + cloudSize.Y) / 2; if (corners.Count == 3) { // get angles of the triangle float angle1 = GeometryTools.GetAngleBetweenVectors(corners[0], corners[1], corners[2]); float angle2 = GeometryTools.GetAngleBetweenVectors(corners[1], corners[2], corners[0]); float angle3 = GeometryTools.GetAngleBetweenVectors(corners[2], corners[0], corners[1]); // check for equilateral triangle if ((Math.Abs(angle1 - 60) <= angleError) && (Math.Abs(angle2 - 60) <= angleError) && (Math.Abs(angle3 - 60) <= angleError)) { subType = PolygonSubType.EquilateralTriangle; } else { // check for isosceles triangle if ((Math.Abs(angle1 - angle2) <= angleError) || (Math.Abs(angle2 - angle3) <= angleError) || (Math.Abs(angle3 - angle1) <= angleError)) { subType = PolygonSubType.IsoscelesTriangle; } // check for rectangled triangle if ((Math.Abs(angle1 - 90) <= angleError) || (Math.Abs(angle2 - 90) <= angleError) || (Math.Abs(angle3 - 90) <= angleError)) { subType = (subType == PolygonSubType.IsoscelesTriangle) ? PolygonSubType.RectangledIsoscelesTriangle : PolygonSubType.RectangledTriangle; } } } else if (corners.Count == 4) { // get angles between 2 pairs of opposite sides float angleBetween1stPair = GeometryTools.GetAngleBetweenLines(corners[0], corners[1], corners[2], corners[3]); float angleBetween2ndPair = GeometryTools.GetAngleBetweenLines(corners[1], corners[2], corners[3], corners[0]); // check 1st pair for parallelism if (angleBetween1stPair <= angleError) { subType = PolygonSubType.Trapezoid; // check 2nd pair for parallelism if (angleBetween2ndPair <= angleError) { subType = PolygonSubType.Parallelogram; // check angle between adjacent sides if (Math.Abs(GeometryTools.GetAngleBetweenVectors(corners[1], corners[0], corners[2]) - 90) <= angleError) { subType = PolygonSubType.Rectangle; } // get length of 2 adjacent sides float side1Length = (float)corners[0].DistanceTo(corners[1]); float side2Length = (float)corners[0].DistanceTo(corners[3]); if (Math.Abs(side1Length - side2Length) <= maxLengthDiff) { subType = (subType == PolygonSubType.Parallelogram) ? PolygonSubType.Rhombus : PolygonSubType.Square; } } } else { // check 2nd pair for parallelism - last chence to detect trapezoid if (angleBetween2ndPair <= angleError) { subType = PolygonSubType.Trapezoid; } } } return(subType); }