/// <summary> /// Find total squared error between quads /// </summary> private float QuadTotalSquaredError(GridQuad x, GridQuad y) { return Math.Abs(x.p[0].X - y.p[0].X) + Math.Abs(x.p[0].Y - y.p[0].Y) + Math.Abs(x.p[1].X - y.p[1].X) + Math.Abs(x.p[1].Y - y.p[1].Y) + Math.Abs(x.p[2].X - y.p[2].X) + Math.Abs(x.p[2].Y - y.p[2].Y) + Math.Abs(x.p[3].X - y.p[3].X) + Math.Abs(x.p[3].Y - y.p[3].Y); }
/// <summary> /// Find the quad most similar to quad, or null if no similar quads are found /// </summary> private GridQuad GetMostSimilarQuad(GridQuad quad, GridQuad[] quadsToChase) { GridQuad mostSimilarQuad = null; float mostSimilarityValue = 20f; foreach(GridQuad testQuad in quadsToChase) { if (QuadTotalSquaredError(testQuad, quad) < mostSimilarityValue) { mostSimilarQuad = testQuad; mostSimilarityValue = QuadTotalSquaredError(testQuad, quad); } } return mostSimilarQuad; }
private GridQuad[] FilterOutBadQuads(List<GridQuad> quads) { const float MAXAREA = 400f * 300f / 64f; const float MINAREA = (400f * 300f / 64f) / 5f; const float MAXWIDTH = 400f / 8f; const float MINWIDTH = (400f / 8f) / 1.7f; const float MAXHEIGHT = 300f / 8f; const float MINHEIGHT = (300f / 8f) / 3f; var bs1 = quads.Where(x => x.Area < MAXAREA && x.Area > MINAREA); var bs2 = bs1.Where(x => x.Width < MAXWIDTH && x.Width > MINWIDTH); var bs3 = bs2.Where(x => x.Height < MAXHEIGHT && x.Height > MINHEIGHT); var bs4 = bs3.Where(x => x.Height < x.Width * 1.7f && x.Width < x.Height * 3f); var bs5 = bs4.OrderBy(x => x.Area).ToArray(); float minDiffToBox64Ago = 99999f; int indexAtMinDiff = 999; if(bs5.Length <= 64) throw new ApplicationException( string.Format("Found {0} quads, of which only {1} are plausible.", bs1.Count(), bs5.Length)); for (int i = 64; i < bs5.Length; i++) { float diffToBox64Ago = bs5[i].Area - bs5[i - 64].Area; if (diffToBox64Ago < minDiffToBox64Ago) { indexAtMinDiff = i; minDiffToBox64Ago = diffToBox64Ago; } } float widthMaxError = 6f; float heightMaxError = 5f; medianBox = bs5[indexAtMinDiff - 32]; var bsfinal = bs5.Where(x => x.Width > medianBox.Width - widthMaxError && x.Width < medianBox.Width + widthMaxError && x.Height > medianBox.Height - heightMaxError && x.Height < medianBox.Height + heightMaxError) .OrderBy(b => b.Area) .ToArray(); return bsfinal; }
/// <summary> /// Returns a quad the same as quad but proportionally in direction dir. /// </summary> private GridQuad EstimateQuadPosition(GridQuad quad, Dir dir) { PointF p0 = new PointF(quad.p[0].X, quad.p[0].Y); PointF p1 = new PointF(quad.p[1].X, quad.p[1].Y); PointF p2 = new PointF(quad.p[2].X, quad.p[2].Y); PointF p3 = new PointF(quad.p[3].X, quad.p[3].Y); if (dir == Dir.S) { p0.Y += quad.Height; p1.Y += quad.Height; p2.Y += quad.Height; p3.Y += quad.Height; } else if (dir == Dir.N) { p0.Y -= quad.Height; p1.Y -= quad.Height; p2.Y -= quad.Height; p3.Y -= quad.Height; } else if (dir == Dir.E) { p0.X += quad.Width; p1.X += quad.Width; p2.X += quad.Width; p3.X += quad.Width; } else if (dir == Dir.W) { p0.X -= quad.Width; p1.X -= quad.Width; p2.X -= quad.Width; p3.X -= quad.Width; } return new GridQuad(new PointF[] {p0, p1, p2, p3}); }
private GridQuad[,] ChaseThe64Quads(GridQuad[] quadsToChase) { List<GridQuad> quadTrain = new List<GridQuad>(); GridQuad nextQuad = medianBox; // Go to the top right through the quads while (true) { GridQuad guessBox = EstimateQuadPosition(nextQuad, Dir.W); GridQuad guessQuad = GetMostSimilarQuad(guessBox, quadsToChase); if (guessQuad == null) break; nextQuad = guessQuad; } while (true) { GridQuad guessBox = EstimateQuadPosition(nextQuad, Dir.N); GridQuad guessQuad = GetMostSimilarQuad(guessBox, quadsToChase); if (guessQuad == null) break; nextQuad = guessQuad; } // We're now at the top right. We can now iterate across the 8x8 grid to find all the 64 quads GridQuad[,] grid = new GridQuad[8, 8]; grid[0, 0] = nextQuad; for (int row = 0; row < 8; row++) { if (row > 0) { GridQuad guessBox = EstimateQuadPosition(grid[row - 1, 0], Dir.S); nextQuad = GetMostSimilarQuad(guessBox, quadsToChase); if (nextQuad == null) break; grid[row, 0] = nextQuad; } for (int column = 1; column < 8; column++) { if (nextQuad == null) break; GridQuad guessBox = EstimateQuadPosition(nextQuad, Dir.E); nextQuad = GetMostSimilarQuad(guessBox, quadsToChase); grid[row, column] = nextQuad; } } return grid; }
public void FindBoard() { // Convert the image to grayscale and filter out the noise GrayImage = BoardImage.Convert<Gray, Byte>().PyrDown().PyrUp(); // Do canny filter CannyImage = GrayImage.Canny(170.0, 50.0); // Do Edge finder Lines = CannyImage.HoughLinesBinary( 1, //Distance resolution in pixel-related units Math.PI / 360.0, //Angle resolution measured in radians. 50, //threshold 30, //min Line width 20 //gap between lines )[0]; //Get the lines from the first channel // Find board BoardFinder boardLineFinder1 = new BoardFinder(); boardLineFinder1.BuildLineSets(Lines, Lines, Math.PI / 10.0, Math.PI / 40.0); // Make lines image Image<Bgr, Byte> linesImage = BoardImage.CopyBlank(); foreach (LineSegment2D line in Lines) linesImage.Draw(line, new Bgr(System.Drawing.Color.Gray), 1); foreach (LineSegment2D line in boardLineFinder1.HorizLines) linesImage.Draw(line, new Bgr(System.Drawing.Color.Red), 1); foreach (LineSegment2D line in boardLineFinder1.VertLines) linesImage.Draw(line, new Bgr(System.Drawing.Color.Green), 1); LinesImage = linesImage; // Remove perspective from image RemovePerspective(boardLineFinder1.GetBoardRegression()); // Convert the warped image to grayscale and filter out the noise WarpedGrayImage = WarpedImage.Convert<Gray, Byte>().PyrDown().PyrUp(); // Do canny filter on warped image WarpedCannyImage = WarpedGrayImage.Canny(160, 90); // Do Edge finder on warped image WarpedVertLines = WarpedCannyImage.HoughLinesBinary( 1, //Distance resolution in pixel-related units Math.PI / 360, //Angle resolution measured in radians. 30, //threshold 40, //min Line width 20 //gap between lines )[0]; //Get the lines from the first channel WarpedHorizLines = WarpedCannyImage.HoughLinesBinary( 1, //Distance resolution in pixel-related units Math.PI / 360, //Angle resolution measured in radians. 60, //threshold 70, //min Line width 40 //gap between lines )[0]; //Get the lines from the first channel // Find board BoardFinder boardLineFinder2 = new BoardFinder(); boardLineFinder2.BuildLineSets(WarpedVertLines, WarpedHorizLines, Math.PI / 60.0, Math.PI / 60.0); // Make lines image Image<Bgr, Byte> warpedLinesImage = WarpedImage.CopyBlank(); Image<Gray, Byte> warpedGridLinesImage = WarpedGrayImage.CopyBlank(); //foreach (LineSegment2D line in WarpedVertLines.Union(WarpedHorizLines)) // warpedLinesImage.Draw(line, new Bgr(System.Drawing.Color.Gray), 1); foreach (LineSegment2D line in boardLineFinder2.HorizLines) { warpedGridLinesImage.Draw(line, new Gray(100), 1); warpedLinesImage.Draw(line, new Bgr(System.Drawing.Color.Red), 1); } foreach (LineSegment2D line in boardLineFinder2.VertLines) { warpedGridLinesImage.Draw(line, new Gray(100), 1); warpedLinesImage.Draw(line, new Bgr(System.Drawing.Color.Green), 1); } WarpedLinesImage = warpedLinesImage; GridQuadsImage = WarpedImage.Copy(); BoardImageWithBoxes = BoardImage.Copy(); try { // Find grid quads List<GridQuad> quads = FindBoardQuads(boardLineFinder2); GridQuad[] quadsToChase = FilterOutBadQuads(quads); GridQuad[,] quadsToDraw = ChaseThe64Quads(quadsToChase); for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) { if (quadsToDraw[i, j] != null) DrawRectangle(GridQuadsImage, quadsToDraw[i, j].p, new Bgr(120, 190, 20), 2); } DrawRectangle(GridQuadsImage, medianBox.p, new Bgr(220, 250, 20), 2); // Set up return array Quads = new GridQuad[8, 8]; for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) { if (quadsToDraw[i, j] != null) { Quads[i, j] = new GridQuad(quadsToDraw[i, j].p); m_InverseWarpMatrix.ProjectPoints(Quads[i, j].p); DrawRectangle(BoardImageWithBoxes, Quads[i, j].p, new Bgr(120, 190, 20), 2); } } } catch { } }