// Persumed to be rotated correctly (but not yet cropped) public static byte[] Read(byte[,] imageData) { byte[,] array = new byte[19, 4]; if (imageData != null) { if (imageData.GetLength(0) > 0 && imageData.GetLength(1) > 0) { imageData = Processing.FloatToByte(Processing.Gaussian(imageData)); imageData = Processing.Binarize(imageData, 200); int minBlack = Math.Min(4, Math.Min(imageData.GetLength(0) / 19, imageData.GetLength(1) / 4)); Point p1 = new Point(-1, -1); Point p2 = new Point(-1, -1); for (int i = 0; i < (imageData.GetLength(0) / 2); i++) { int sum1 = 0; int sum2 = 0; for (int j = 0; j < imageData.GetLength(1); j++) { sum1 += imageData[i, j] > 0 ? 0 : 1; sum2 += imageData[imageData.GetLength(0) - i - 1, j] > 0 ? 0 : 1; } if (p1.X == -1 && sum1 >= minBlack) { p1.X = i; } if (p2.X == -1 && sum2 >= minBlack) { p2.X = imageData.GetLength(0) - i - 1; } if (p1.X != -1 && p2.X != -1) { break; } } for (int j = 0; j < (imageData.GetLength(1) / 2); j++) { int sum1 = 0; int sum2 = 0; for (int i = 0; i < imageData.GetLength(0); i++) { sum1 += imageData[i, j] > 0 ? 0 : 1; sum2 += imageData[i, imageData.GetLength(1) - j - 1] > 0 ? 0 : 1; } if (p1.Y == -1 && sum1 >= minBlack) { p1.Y = j; } if (p2.Y == -1 && sum2 >= minBlack) { p2.Y = imageData.GetLength(1) - j - 1; } if (p1.Y != -1 && p2.Y != -1) { break; } } if (p1.X != -1 && p1.Y != -1 && p2.X != -1 && p2.Y != -1) { imageData = Processing.Crop(imageData, new Rectangle(p1.X, p1.Y, p2.X - p1.X + 1, p2.Y - p1.Y + 1)); int blockWidth = (int)Math.Floor((double)imageData.GetLength(0) / 19); int blockHeight = (int)Math.Floor((double)imageData.GetLength(1) / 4); int newWidth = blockWidth * 19; int newHeight = blockHeight * 4; imageData = Processing.Resize(imageData, newWidth, newHeight); int threshold = (blockWidth * blockHeight) / 2; for (int j = 0; j < 4; j++) { for (int i = 0; i < 19; i++) { double sum = 0; for (int k = 0; k < blockHeight; k++) { for (int l = 0; l < blockWidth; l++) { sum += (int)imageData[(i * blockWidth) + l, (j * blockHeight) + k] > 1 ? 1 : 0; } } array[i, j] = sum < threshold ? (byte)0 : (byte)1; } } } else { Logger.LogLow("Could not crop barcode."); } } } return(ReadFrom2DArray(array)); }
public static byte[] Read(Image image) { return(Read(Processing.ImageToByte(image))); }
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); }
public void CalculateInitialItemAltsCheckedState(int pageNumber, float sure, float doubt) { if (ItemAltsCheckedState == null) { ItemAltsCheckedState = new ItemCheckedState[Program.Test.Paper.Blocks.X, Program.Test.Paper.Blocks.Y]; } var itemAlternativeOnLocation = Program.Test.Pages[pageNumber].ItemAlternativeOnLocation(); for (int i = 0; i < Program.Test.Paper.Blocks.X; i++) { for (int j = 0; j < Program.Test.Paper.Blocks.Y; j++) { if (itemAlternativeOnLocation[i, j]) { var crop = Processing.Crop(ScannedImage, ItemAltRectangle(i, j)); int w = crop.GetLength(0); int h = crop.GetLength(1); double val = 0; Single w2 = (Single)(w - 1) / 2; Single h2 = (Single)(h - 1) / 2; Single max = w2 * w2 * h2 * h2; double old = 0; double nw = 0; for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { val += crop[x, y] * (max - ((x - w2) * (x - w2) * (y - h2) * (y - h2)));; old += crop[x, y]; nw += crop[x, y] * (max - ((x - w2) * (x - w2) * (y - h2) * (y - h2))); } } val /= 2.55f * ((w * h * max) - ( (w * (w - 1) * (w + 1) / 12) * (h * (h - 1) * (h + 1) / 12) )); ColorDistribution.Add(val); ItemCheckedState state = ItemCheckedState.Unknown; if (val < sure) { state = ItemCheckedState.Checked; } else if (val < doubt) { state = ItemCheckedState.Doubt; } else { state = ItemCheckedState.Unchecked; } ItemAltsCheckedState[i, j] = state; } else { ItemAltsCheckedState[i, j] = ItemCheckedState.Unavailable; } } } }
private static List <PointF> CalibrationPoints(Test test, byte[,] image, int searchSize) { List <PointF> calPoints = new List <PointF>(); bool solutionFound = false; int max = 0; int indexOfMax = 0; for (int i = 0; i < 100; i++) { if (Settings.SuccessFullCanny[i] > max) { max = Settings.SuccessFullCanny[i]; indexOfMax = i; } } int currentCross = indexOfMax; if (currentCross == 0) { currentCross = Program.UserSettings.calibrationCannyStart; } int attempt = 1; // Canny -> SearchSize -> SearchShift int cannyAttempt = 1; // Don't start at zero, otherwise adds zero to currentCross first time // Try different levels of canny edge detection until the number of points matches int[] best = { -1, -1 }; int[] secondBest = { -1, -1 }; bool goingDown = false; bool goingUp = false; while (!solutionFound && currentCross >= 0 && currentCross < 100 && cannyAttempt <= Program.UserSettings.maximumCannyAttempts) { calPoints.Clear(); var cannyData = Processing.EdgeDetection(image, currentCross); // Due to edgedetection, cannyData is a bit smaller than original! float correctionX = (float)(image.GetLength(0) - cannyData.GetLength(0)) / 2; float correctionY = (float)(image.GetLength(1) - cannyData.GetLength(1)) / 2; int searchSizeDiffCount = 0; int[] searchSizeDiff = { 0, 2, -4, 6, -8, 10, -12, 14, -16, 18, -20, 22 }; List <EllipticalFit> ellipsesFound = new List <EllipticalFit>(); bool ellipsesValid = false; while (!ellipsesValid && searchSizeDiffCount < searchSizeDiff.GetLength(0)) { int adjustedSearchSize = searchSize + searchSizeDiff[searchSizeDiffCount]; int searchShift = 0; ellipsesFound = FindPointsByCanny(test.Paper.CalibrationCircles.X + test.Paper.CalibrationCircles.Y - 1, currentCross, cannyData, adjustedSearchSize, ref searchShift, ref attempt, ref ellipsesValid); searchSizeDiffCount++; attempt++; } if (ellipsesValid) { foreach (var fit in ellipsesFound) { var mid = fit.MidPoint(); calPoints.Add(new PointF(mid.X + correctionX, mid.Y + correctionY)); } calPoints.Sort(delegate(PointF pc1, PointF pc2) { return(Distance(pc1, new PointF(0, image.GetLength(1))).CompareTo(Distance(pc2, new PointF(0, image.GetLength(1))))); }); /* Results in this order: * G | | F | | E | | A-B-C-D */ Settings.SuccessFullCanny[currentCross]++; solutionFound = true; } else { if (best[0] == -1) { best[0] = currentCross; best[1] = ellipsesFound.Count; } else { if (ellipsesFound.Count > best[1]) { secondBest[0] = best[0]; secondBest[1] = best[1]; best[0] = currentCross; best[1] = ellipsesFound.Count; } else if (ellipsesFound.Count > secondBest[1]) { secondBest[0] = currentCross; secondBest[1] = ellipsesFound.Count; } } if (secondBest[1] < 0 || best[1] == secondBest[1]) // No specific direction prefered yet -> try both ways { currentCross += (int)Math.Pow(-1, cannyAttempt) * cannyAttempt * 3; } else // One direction favored { if (best[0] < secondBest[0] || goingDown) { currentCross -= 3; goingDown = true; } else if (best[0] > secondBest[0] || goingUp) { currentCross += 3; goingUp = true; } } cannyAttempt++; } } return(calPoints); }