private static List <Line> RecognizeLines(Bitmap bw, RecognitionOptions options) { List <Point> edgePoints = EdgePointExtraction.ExtractEdgePoints(bw); List <RawLine> rawLines = PseudoHoughTransform.RecognizeLines(edgePoints, options); return(LineFilter.FilterLines(edgePoints, rawLines, options)); }
/* * Convenience function to recognize set of lines in points. */ public static List <RawLine> RecognizeLines(List <Point> points, RecognitionOptions options) { int[,] hough = HoughTransform(points, options); List <Point> houghPeaks = FindHoughPeaks(hough, options); return(ExtractRawLines(houghPeaks, options)); }
/* * Convenience function to recognize table in an image. */ public static Option <Table> RecognizeTable(Bitmap sourceImage, List <double> columnWidthsHintOrNull = null) { Bitmap bw = ImageUtil.ToBlackAndWhite(sourceImage); Bitmap rotBw = ImageUtil.RotateCounterClockwise(bw); var horizOptions = RecognitionOptions.HorizontalOptions(); horizOptions.imageWidth = bw.Width; horizOptions.imageHeight = bw.Height; var vertOptions = RecognitionOptions.VerticalOptions(); vertOptions.imageWidth = rotBw.Width; vertOptions.imageHeight = rotBw.Height; List <Line> horizLines = RecognizeLines(bw, horizOptions); List <Line> vertLines = RecognizeLines(rotBw, vertOptions); if (horizLines.Count == 0 || vertLines.Count == 0) { return(new None <Table>()); } else { var lnorm = new LineNormalization(horizLines, vertLines, sourceImage); return(TableBuilder.NewBuilder(lnorm, Options.Create(columnWidthsHintOrNull)).table); } }
public static RecognitionOptions CommonOptions() { var options = new RecognitionOptions(); options.maxAngleFactor = 0.03f; options.houghWindowWidth = 20; options.houghWindowHeight = 10; return(options); }
/* * Locate peaks in A/DO space array. * For each point, searches some narrow window of points around it, * and if there is no point bigger than current point, records it as a peak. * If there are several points with same value, records their average as a peak. */ public static List <Point> FindHoughPeaks(int[,] hough, RecognitionOptions options) { int width = hough.GetLength(0); int height = hough.GetLength(1); int maxHough = 0; foreach (var h in hough) { if (h > maxHough) { maxHough = h; } } List <Point> thresholdedPoints = new List <Point>(); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if ((double)hough[x, y] / maxHough > options.houghThreshold) { thresholdedPoints.Add(new Point(x, y)); } } } List <Point> peaks = new List <Point>(); HashSet <Point> usedPoints = new HashSet <Point>(); foreach (Point pt in thresholdedPoints) { if (!usedPoints.Contains(pt)) { List <Point> adjPoints = thresholdedPoints .Where(p => Math.Abs(p.X - pt.X) < options.houghWindowWidth && Math.Abs(p.Y - pt.Y) < options.houghWindowHeight).ToList(); int maxAdj = adjPoints.Select(p => hough[p.X, p.Y]).Max(); if (hough[pt.X, pt.Y] == maxAdj) { List <Point> adjPeaks = adjPoints.Where(p => hough[p.X, p.Y] == maxAdj).ToList(); int avgX = (int)Math.Round(adjPeaks.Select(p => p.X).Average()); int avgY = (int)Math.Round(adjPeaks.Select(p => p.Y).Average()); peaks.Add(new Point(avgX, avgY)); foreach (var adj in adjPoints) { usedPoints.Add(adj); } } } } return(peaks); }
/* * Get array of angles that we want to test. * Creates array of angles, that differ by 1px at distance of image width. */ private static double[] GetAngleMap(RecognitionOptions options) { int maxDy = (int)Math.Ceiling(options.imageWidth * options.maxAngleFactor); double[] angleMap = new double[maxDy * 2 + 1]; for (int dy = -maxDy; dy <= maxDy; dy++) { angleMap[maxDy + dy] = (double)dy / (double)options.imageWidth; } return(angleMap); }
/* * Extract raw lines from peaks in A/DO space. */ public static List <RawLine> ExtractRawLines(List <Point> houghPeaks, RecognitionOptions options) { double[] angleMap = GetAngleMap(options); List <RawLine> lines = new List <RawLine>(); foreach (var pt in houghPeaks) { lines.Add(new RawLine { yInt = pt.Y, k = angleMap[pt.X] }); } return(lines); }
/* * Main function, that performs Pseudo-Hough transform as described above. * Returns A/DO space array. */ public static int[,] HoughTransform(List <Point> points, RecognitionOptions options) { double[] angleMap = GetAngleMap(options); int[,] hough = new int[angleMap.Length, options.imageHeight]; for (int a = 0; a < angleMap.Length; a++) { foreach (Point pt in points) { int y = (int)Math.Round(pt.Y - pt.X * angleMap[a]); if (y >= 0 && y < options.imageHeight) { hough[a, y]++; } } } return(hough); }
public static bool HasCyclicPatterns(bool[] linePoints, int from, int to, RecognitionOptions options) { for (int w = options.cyclicPatternsMinWidth; w <= options.cyclicPatternsMaxWidth; w++) { int[] acc = new int[w]; for (int i = from; i <= to; i++) { if (linePoints[i]) { acc[(i - from) % w]++; } } int threshold = (to - from) / w / 5; double cyclicPatternSize = (double)acc.Where(a => a < threshold).Count() / w; if (cyclicPatternSize > 0.2) { return(true); } } return(false); }
public static Bitmap CyclicPatternsInLines(List <Point> edgePoints, List <RawLine> rawLines, RecognitionOptions options) { rawLines = rawLines.OrderBy(l => l.yInt).ToList(); List <Bitmap> images = new List <Bitmap>(); foreach (var rawLine in rawLines) { bool[] linePoints = new bool[options.imageWidth]; foreach (var pt in edgePoints) { if (Math.Abs(rawLine.yInt - (pt.Y - pt.X * rawLine.k)) < 2) { linePoints[pt.X] = true; } } var lines = SegmentDetector.GetSegments(linePoints, rawLine); if (lines.Count > 0) { images.Add(CyclicPatternsImage(linePoints, lines.First().p1.X, lines.Last().p2.X)); } } return(ImageUtil.VerticalConcat(images)); }
/* * Forms solid lines from set of raw lines. * Discards lines that have cyclic patterns in them. */ public static List <Line> FilterLines(List <Point> edgePoints, List <RawLine> rawLines, RecognitionOptions options) { List <Line> lines = new List <Line>(); foreach (var rawLine in rawLines) { // convert rawLine to list of black/white pixels bool[] linePoints = new bool[options.imageWidth]; foreach (var pt in edgePoints) { if (Math.Abs(rawLine.yInt - (pt.Y - pt.X * rawLine.k)) < 2) { linePoints[pt.X] = true; } } var segments = SegmentDetector.GetSegments(linePoints, rawLine); if (segments.Count > 0) { Line solidLine = SegmentDetector.GetSolidLine(segments); if (!options.detectCyclicPatterns || !CyclicPatternDetector.HasCyclicPatterns(linePoints, solidLine.p1.X, solidLine.p2.X, options)) { lines.Add(solidLine); } } } return(lines); }
private void RunOCR(Bitmap sourceImage) { this.sourceImagePV.Image = sourceImage; Bitmap bw = ImageUtil.ToBlackAndWhite(sourceImage); this.bwImagePV.Image = bw; Bitmap rotBw = ImageUtil.RotateCounterClockwise(bw); this.rotBwImagePV.Image = rotBw; var horizOptions = RecognitionOptions.HorizontalOptions(); horizOptions.imageWidth = bw.Width; horizOptions.imageHeight = bw.Height; var vertOptions = RecognitionOptions.VerticalOptions(); vertOptions.imageWidth = rotBw.Width; vertOptions.imageHeight = rotBw.Height; List <Point> horizEdgePoints = EdgePointExtraction.ExtractEdgePoints(bw); this.edgePointsPV.Image = EdgePointExtraction.DrawPoints(bw, horizEdgePoints); int[,] horizHough = PseudoHoughTransform.HoughTransform(horizEdgePoints, horizOptions); List <Point> horizHoughPeaks = PseudoHoughTransform.FindHoughPeaks(horizHough, horizOptions); Bitmap horizHoughPlainImage = PseudoHoughTransform.HoughTransformImage(horizHough); Bitmap horizHoughImage = PseudoHoughTransform.HoughTransformImageWithPeaks(horizHough, horizHoughPeaks); List <RawLine> horizRawLines = PseudoHoughTransform.ExtractRawLines(horizHoughPeaks, horizOptions); List <Line> horizLines = LineFilter.FilterLines(horizEdgePoints, horizRawLines, horizOptions); List <Point> vertEdgePoints = EdgePointExtraction.ExtractEdgePoints(rotBw); this.rotEdgePointsPV.Image = EdgePointExtraction.DrawPoints(rotBw, vertEdgePoints); int[,] vertHough = PseudoHoughTransform.HoughTransform(vertEdgePoints, vertOptions); List <Point> vertHoughPeaks = PseudoHoughTransform.FindHoughPeaks(vertHough, vertOptions); Bitmap vertHoughPlainImage = PseudoHoughTransform.HoughTransformImage(vertHough); Bitmap vertHoughImage = PseudoHoughTransform.HoughTransformImageWithPeaks(vertHough, vertHoughPeaks); List <RawLine> vertRawLines = PseudoHoughTransform.ExtractRawLines(vertHoughPeaks, vertOptions); this.cyclicPatternsPV.Image = CyclicPatternDetector.CyclicPatternsInLines(vertEdgePoints, vertRawLines, vertOptions); RecognitionOptions vertNoFilterOptions = vertOptions; vertNoFilterOptions.detectCyclicPatterns = false; List <Line> vertUnfilteredLines = LineFilter.FilterLines(vertEdgePoints, vertRawLines, vertNoFilterOptions); List <Line> vertLines = LineFilter.FilterLines(vertEdgePoints, vertRawLines, vertOptions); Bitmap rawLinesImage = DrawLines(bw, horizLines, vertUnfilteredLines, 2); Bitmap filteredLinesImage = DrawLines(bw, horizLines, vertLines, 4); this.filteredLinesPV.Image = filteredLinesImage; this.houghPV.Image = ImageUtil.VerticalConcat(new List <Bitmap> { ImageUtil.HorizontalConcat(new List <Bitmap> { rawLinesImage, horizHoughImage, horizHoughPlainImage }), ImageUtil.RotateClockwise(vertHoughImage), ImageUtil.RotateClockwise(vertHoughPlainImage) }); var lnorm = new LineNormalization(horizLines, vertLines, sourceImage); Bitmap normalizedLinesImage = DrawLines(bw, lnorm.normHorizLines, lnorm.normVertLines, 2); this.normalizedLinesPV.Image = normalizedLinesImage; var tb = TableBuilder.NewBuilder(lnorm, new None <List <double> >()); Bitmap tableRecognitionImage = tb.DebugImage(bw); this.tableRecognitionPV.Image = tableRecognitionImage; Option <Table> recognizedTable = tb.table; recognizedTable.ForEach(table => { Bitmap recognizedTableImage = new Bitmap(bw); Graphics g = Graphics.FromImage(recognizedTableImage); table.DrawTable(g, new Pen(Color.Red, 2)); g.Dispose(); this.recognizedTablePV.Image = recognizedTableImage; }); if (recognizedTable.IsEmpty()) { Console.WriteLine("no table was recognized"); } this.recognizedTablePV.AddDoubleClickListener((pt, e) => { recognizedTable.ForEach(table => { table.GetCellAtPoint(pt.X, pt.Y).ForEach(cell => { table.GetCellImage(bw, cell.X, cell.Y).ForEach(cellImage => { new GradeRecognitionDebugView(cellImage, "<gen>").ShowDialog(); }); }); }); }); }