private static List <EllipticalFit> AddWithoutOverlap(List <EllipticalFit> ellipseList, EllipticalFit fit) { List <EllipticalFit> outList = new List <EllipticalFit>(); if (ellipseList.Count == 0) { outList.Add(fit); return(outList); } else { bool addFit = true; for (int i = 0; i < ellipseList.Count; i++) { if (ellipseList[i].BoundingRectangle().IntersectsWith(fit.BoundingRectangle())) { // In case over overlap: only add the best one if (ellipseList[i].DifferenceFromCircle < fit.DifferenceFromCircle) { addFit = false; outList.Add(ellipseList[i]); } } else // Add both { outList.Add(ellipseList[i]); } } if (addFit) { outList.Add(fit); } return(outList); } }
private static List <EllipticalFit> FindPointsByCanny(int numberToFind, int currentCross, byte[,] cannyData, int searchSize, ref int searchShift, ref int attempt, ref bool ellipsesValid) { searchSize = (int)(Math.Round((double)searchSize / 2) * 2); // has to be even int shiftX = searchShift == 1 || searchShift == 3 ? (int)(searchSize / 4) : 0; int shiftY = searchShift == 2 || searchShift == 3 ? (int)(searchSize / 4) : 0; int searchJump = searchSize / 2; int w = cannyData.GetLength(0); int h = cannyData.GetLength(1); // Create a list with areas to look => cannot be reused due to (even very small) differences in size of scans List <Point> searchPoints = new List <Point>(); for (int x = shiftX; x < w; x += searchJump) { int searchX = x; if (x + searchSize >= w) { searchX = w - searchSize; x = w; } for (int y = shiftY; y < h; y += searchJump) { int searchY = y; if (y + searchSize >= h) { searchY = h - searchSize; y = h; } searchPoints.Add(new Point(searchX, searchY)); } } // Assumed that the paper is oriented right => faster searchPoints.Sort(delegate(Point p1, Point p2) { return(Math.Min(w - p1.X, h - p1.Y).CompareTo(Math.Min(w - p2.X, h - p2.Y))); }); /* * * * * ******** */ #if DEBUG var img = Processing.ByteToImage(cannyData); #endif // Crop small rectangles from canny List <EllipticalFit> ellipseList = new List <EllipticalFit>(); for (int i = 0; i < searchPoints.Count; i++) { // Fit an ellipse EllipticalFit fit = null; RectangleF boundingRectangle = new RectangleF(); try { var searchRectangle = new Rectangle(searchPoints[i].X, searchPoints[i].Y, searchSize, searchSize); fit = new EllipticalFit(cannyData, 255, searchRectangle); #if DEBUG using (Graphics g = Graphics.FromImage(img)) { g.DrawRectangle(new Pen(Color.Yellow), searchRectangle); } #endif // Empty search areas are already ignored in fit boundingRectangle = fit.BoundingRectangle(); // If a valid ellipse is found if (!double.IsNaN(boundingRectangle.X) && boundingRectangle.X > 0 && !double.IsNaN(boundingRectangle.Y) && boundingRectangle.Y > 0 && boundingRectangle.Width < searchSize && boundingRectangle.Height < searchSize && boundingRectangle.Width > .25 * searchSize && boundingRectangle.Height > .25 * searchSize && boundingRectangle.Width > 15 && boundingRectangle.Height > 15) { var observed = Processing.Crop(cannyData, searchRectangle); var moments = Processing.Moments(observed); double squareness = Math.Min((double)boundingRectangle.Width, (double)boundingRectangle.Height) / Math.Max((double)boundingRectangle.Width, (double)boundingRectangle.Height); // Ellipse has to be more or less round, based on enough points and have some variance in each direction if (squareness > .9 && moments[0] >= 8 && moments[3] > 0 && moments[4] > 0) { // Fill the ellipse with floodfill var observedFilled = Processing.FloodFill(observed, 1, new PointF( boundingRectangle.X - searchRectangle.X + (boundingRectangle.Width / 2), boundingRectangle.Y - searchRectangle.Y + (boundingRectangle.Height / 2))); var momentsFilled = Processing.Moments(observedFilled); if (momentsFilled[1] > (boundingRectangle.Width / 2) && momentsFilled[1] < searchRectangle.Width - (boundingRectangle.Width / 2) && momentsFilled[2] > (boundingRectangle.Height / 2) && momentsFilled[2] < searchRectangle.Height - (boundingRectangle.Height / 2)) { // Create a white circle of similar size var expectedFilled = Processing.WhiteCircle(observedFilled.GetLength(0), observedFilled.GetLength(1), new RectangleF( boundingRectangle.X - searchRectangle.X, boundingRectangle.Y - searchRectangle.Y, boundingRectangle.Width, boundingRectangle.Height)); // Compare the result of the floodfill with the white circle fit.DifferenceFromCircle = Processing.AverageDifference(observedFilled, expectedFilled); // Only if ellipse was closed they will be similar if (fit.DifferenceFromCircle < .1) { ellipseList = AddWithoutOverlap(ellipseList, fit); } } } } } catch (Exception ex) { Logger.LogLow(ex.Message); } if (ellipseList.Count == numberToFind) { break; } } ellipsesValid = EllipsePointsAreValid(Program.Test, ellipseList); // Count and colinearity have to be correct /* * using (Graphics g = Graphics.FromImage(img)) * { * for (int i = 0; i < ellipseList.Count; i++) * { * g.DrawLine(new Pen(Color.Blue, 2), ellipseList[i].MidPoint().X - 15, ellipseList[i].MidPoint().Y, ellipseList[i].MidPoint().X + 15, ellipseList[i].MidPoint().Y); * g.DrawLine(new Pen(Color.Blue, 2), ellipseList[i].MidPoint().X, ellipseList[i].MidPoint().Y - 15, ellipseList[i].MidPoint().X, ellipseList[i].MidPoint().Y + 15); * } * } * img.Save(Program.UserSettings.defaultDirectory + "\\img\\" + Convert.ToString(DateTime.Now.Minute) + " - " + Convert.ToString(DateTime.Now.Second) + " - " + Convert.ToString(DateTime.Now.Millisecond) + " - " + currentCross + " - " + searchSize + " - " + searchShift + " - " + attempt + " - " + ellipseList.Count + " - " + ellipsesValid + ".bmp"); */ Logger.LogHigh( "Attempt:\t" + attempt + "\tCanny:\t" + currentCross + "\tSearch size:\t" + searchSize + "\tSearch shift:\t" + searchShift + "\tEllipses found:\t" + ellipseList.Count + "\tEllipses valid:\t" + ellipsesValid); // If not all ellipses are found, retry with shifted searcharea if (ellipseList.Count != numberToFind && searchShift < 3) { searchShift++; attempt++; ellipseList = FindPointsByCanny(numberToFind, currentCross, cannyData, searchSize, ref searchShift, ref attempt, ref ellipsesValid); } return(ellipseList); }