All Downloads are FREE. Search and download functionalities are using the official Maven repository.

boofcv.alg.fiducial.calib.grid.DetectSquareGridFiducial Maven / Gradle / Ivy

/*
 * Copyright (c) 2022, 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.BinaryContourFinder;
import boofcv.abst.filter.binary.InputToBinary;
import boofcv.alg.fiducial.calib.squares.*;
import boofcv.alg.shapes.polygon.DetectPolygonBinaryGrayRefine;
import boofcv.struct.ConfigLength;
import boofcv.struct.geo.PointIndex2D_F64;
import boofcv.struct.image.GrayU8;
import boofcv.struct.image.ImageGray;
import georegression.struct.shapes.Polygon2D_F64;

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.
 *
 * 

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.

* *

*
* *
* Example of a 4 by 3 grid (row then column). *

* * @author Peter Abeles */ @SuppressWarnings({"NullAway.Init"}) public class DetectSquareGridFiducial> { // 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 inputToBinary; // detector for squares DetectPolygonBinaryGrayRefine 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 calibrationPoints = new ArrayList<>(); int calibRows; int calibCols; SquareGridTools tools = new SquareGridTools(); // storage for binary image GrayU8 binary = new GrayU8(1, 1); List> clusters; List found = new ArrayList<>(); /** * 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 inputToBinary, DetectPolygonBinaryGrayRefine detectorSquare ) { this.numRows = numRows; this.numCols = numCols; this.inputToBinary = inputToBinary; this.detectorSquare = detectorSquare; // some unit tests will pass in a null value if (detectorSquare != null) { detectorSquare.getDetector().setOutputClockwiseUpY(true); detectorSquare.getDetector().setConvex(true); detectorSquare.getDetector().setNumberOfSides(4, 4); } 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 ) { configureContourDetector(image); binary.reshape(image.width, image.height); inputToBinary.process(image, binary); detectorSquare.process(image, binary); detectorSquare.refineAll(); detectorSquare.getPolygons(found, null); clusters = s2c.process(found); c2g.process(clusters); List grids = c2g.getGrids(); SquareGrid match = null; double matchSize = 0; for (int gridIdx = 0; gridIdx < grids.size(); gridIdx++) { SquareGrid g = grids.get(gridIdx); 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; } /** * Configures the contour detector based on the image size. Setting a maximum contour and turning off recording * of inner contours and improve speed and reduce the memory footprint significantly. */ private void configureContourDetector( T gray ) { // determine the maximum possible size of a square when viewed head on // this doesn't take in account the spacing between squares and will be an over estimate int maxContourSize = Math.max(gray.width, gray.height)/Math.max(numCols, numRows); BinaryContourFinder contourFinder = detectorSquare.getDetector().getContourFinder(); contourFinder.setMaxContour(ConfigLength.fixed(maxContourSize*4*2)); contourFinder.setSaveInnerContour(false); } List row0 = new ArrayList<>(); List 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).square; row0.add(new PointIndex2D_F64(square.get(0), 0)); row0.add(new PointIndex2D_F64(square.get(1), 0)); row1.add(new PointIndex2D_F64(square.get(3), 0)); row1.add(new PointIndex2D_F64(square.get(2), 0)); } calibrationPoints.addAll(row0); calibrationPoints.addAll(row1); } // calibCols = grid.columns*2; // calibRows = grid.rows*2; } public List getCalibrationPoints() { return calibrationPoints; } public int getCalibrationRows() { return calibRows; } public int getCalibrationCols() { return calibCols; } public DetectPolygonBinaryGrayRefine getDetectorSquare() { return detectorSquare; } public List> 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; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy