/* * Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved. * * This file is part of BoofCV (http://boofcv.org). * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package boofcv.alg.fiducial.calib.grid; import boofcv.abst.filter.binary.InputToBinary; import boofcv.alg.fiducial.calib.squares.*; import boofcv.alg.shapes.polygon.BinaryPolygonDetector; import boofcv.struct.image.GrayU8; import boofcv.struct.image.ImageGray; import georegression.struct.point.Point2D_F64; import georegression.struct.shapes.Polygon2D_F64; import org.ddogleg.struct.FastQueue; import java.util.ArrayList; import java.util.List; /** * Detect a square grid calibration target and returns the corner points of each square. This calibration grid is * specified by a set of squares which are organized in a grid pattern. All squares are the same size. The entire * grid must be visible. Space between the squares is specified as a ratio of the square size. The grid will be * oriented so that returned points are in counter clockwise (CCW) ordering, which appears to be CW in the image. * * <p>There is also always at least two solutions to the ordering. For sake of consistency it will select * the orientation where index 0 is the closest to the origin.</p> * * </p> * <center> * <img src="doc-files/square_grid.jpg"/> * </center> * Example of a 4 by 3 grid (row then column). * <p> * * @author Peter Abeles */ public class DetectSquareGridFiducial<T extends ImageGray> { // dimension of square grid. This only refers to black squares and not the white space int numCols; int numRows; // converts input image into a binary image InputToBinary<T> inputToBinary; // detector for squares BinaryPolygonDetector<T> detectorSquare; // Converts detected squares into a graph and into grids SquaresIntoRegularClusters s2c; SquareRegularClustersIntoGrids c2g; // output results. Grid of calibration points in row-major order List<Point2D_F64> calibrationPoints = new ArrayList<>(); int calibRows; int calibCols; SquareGridTools tools = new SquareGridTools(); // storage for binary image GrayU8 binary = new GrayU8(1,1); List<List<SquareNode>> clusters; /** * COnfigures the detector * * @param numRows Number of black squares in the grid rows * @param numCols Number of black squares in the grid columns * @param spaceToSquareRatio Ratio of spacing between the squares and the squares width * @param inputToBinary Converts input image into a binary image * @param detectorSquare Detects the squares in the image. Must be configured to detect squares */ public DetectSquareGridFiducial(int numRows, int numCols, double spaceToSquareRatio, InputToBinary<T> inputToBinary , BinaryPolygonDetector<T> detectorSquare) { this.numRows = numRows; this.numCols = numCols; this.inputToBinary = inputToBinary; this.detectorSquare = detectorSquare; s2c = new SquaresIntoRegularClusters(spaceToSquareRatio,Integer.MAX_VALUE, 1.35); c2g = new SquareRegularClustersIntoGrids(numCols*numRows); calibRows = numRows*2; calibCols = numCols*2; } /** * Process the image and detect the calibration target * * @param image Input image * @return true if a calibration target was found and false if not */ public boolean process( T image ) { binary.reshape(image.width,image.height); inputToBinary.process(image,binary); detectorSquare.process(image, binary); FastQueue<Polygon2D_F64> found = detectorSquare.getFoundPolygons(); clusters = s2c.process(found.toList()); c2g.process(clusters); List<SquareGrid> grids = c2g.getGrids(); SquareGrid match = null; double matchSize = 0; for( SquareGrid g : grids ) { if (g.columns != numCols || g.rows != numRows) { if( g.columns == numRows && g.rows == numCols ) { tools.transpose(g); } else { continue; } } double size = tools.computeSize(g); if( size > matchSize ) { matchSize = size; match = g; } } if( match != null ) { if( tools.checkFlip(match) ) { tools.flipRows(match); } tools.putIntoCanonical(match); if( !tools.orderSquareCorners(match) ) return false; extractCalibrationPoints(match); return true; } return false; } List<Point2D_F64> row0 = new ArrayList<>(); List<Point2D_F64> row1 = new ArrayList<>(); /** * Extracts the calibration points from the corners of a fully ordered grid */ void extractCalibrationPoints(SquareGrid grid) { calibrationPoints.clear(); for (int row = 0; row < grid.rows; row++) { row0.clear(); row1.clear(); for (int col = 0; col < grid.columns; col++) { Polygon2D_F64 square = grid.get(row,col).corners; row0.add(square.get(0)); row0.add(square.get(1)); row1.add(square.get(3)); row1.add(square.get(2)); } calibrationPoints.addAll(row0); calibrationPoints.addAll(row1); } // calibCols = grid.columns*2; // calibRows = grid.rows*2; } public List<Point2D_F64> getCalibrationPoints() { return calibrationPoints; } public int getCalibrationRows() { return calibRows; } public int getCalibrationCols() { return calibCols; } public BinaryPolygonDetector<T> getDetectorSquare() { return detectorSquare; } public List<List<SquareNode>> getClusters() { return clusters; } public SquaresIntoRegularClusters getSquaresIntoClusters() { return s2c; } public SquareRegularClustersIntoGrids getGrids() { return c2g; } public GrayU8 getBinary() { return binary; } public int getColumns() { return numCols; } public int getRows() { return numRows; } }