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

boofcv.alg.fiducial.calib.chess.DetectChessSquarePoints Maven / Gradle / Ivy

Go to download

BoofCV is an open source Java library for real-time computer vision and robotics applications.

There is a newer version: 0.26
Show newest version
/*
 * 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.chess;

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 georegression.struct.shapes.Polygon2D_I32;
import org.ddogleg.struct.FastQueue;

import java.util.List;

/**
 * Given a binary image it detects the presence of chess board calibration grids. 1) Detect blobs
 * in binary image and select square like ones. 2) Create two grids, inner and outer, 3) Put the
 * grids together, 4) extract initial estimate for corners
 *
 * @author Peter Abeles
 */
public class DetectChessSquarePoints {

	// detector for squares
	BinaryPolygonDetector detectorSquare;

	// Converts detected squares into a graph and into grids
	SquaresIntoCrossClusters s2c;
	CrossClustersIntoGrids c2g;

	// size of square grids
	private int numRows,numCols;

	// bounding quadrilateral
	private Polygon2D_I32 boundPolygon = new Polygon2D_I32();

	SquareGridTools tools = new SquareGridTools();

	FastQueue calibrationPoints = new FastQueue(Point2D_F64.class,true);

	// maximum distance two corners can be from each other
	double maxCornerDistanceSq;

	// List of nodes put into clusters
	List> clusters;

	/**
	 * Configures chess board detector.
	 *
	 * @param numRows Number of rows in square grid
	 * @param numCols Number of columns in square grid
	 * @param maxCornerDistance Maximum distance in pixels that two "overlapping" corners can be from each other.
	 */
	public DetectChessSquarePoints(int numRows, int numCols, double maxCornerDistance,
								   BinaryPolygonDetector detectorSquare)
	{
		this.maxCornerDistanceSq = maxCornerDistance*maxCornerDistance;

		this.numRows = numRows;
		this.numCols = numCols;

		this.detectorSquare = detectorSquare;

		s2c = new SquaresIntoCrossClusters(maxCornerDistance,-1);
		c2g = new CrossClustersIntoGrids();
	}

	/**
	 * Detects chessboard in the binary image.  Square corners must be disconnected.
	 * Returns true if a chessboard was found, false otherwise.
	 *
	 * @param input Original input image.
	 * @param binary Binary image of chessboard
	 * @return True if successful.
	 */
	public boolean process( T input , GrayU8 binary ) {
		boundPolygon.vertexes.reset();

		detectorSquare.process(input, binary);

		FastQueue found = detectorSquare.getFoundPolygons();
		FastQueue foundInfo = detectorSquare.getPolygonInfo();

		clusters = s2c.process(found.toList(),foundInfo.toList());

		c2g.process(clusters);
		List grids = c2g.getGrids().toList();

		for (int i = 0; i < grids.size(); i++) {
			SquareGrid grid = grids.get(i);
			if( grid.rows == numCols && grid.columns == numRows ) {
				tools.transpose(grid);
			}
			if( grid.rows == numRows && grid.columns == numCols ) {
				// this detector requires that the (0,0) grid cell has a square inside of it
				if( grid.get(0,0) == null ){
					if( grid.get(0,-1) != null ) {
						tools.flipColumns(grid);
					} else if( grid.get(-1,0) != null ) {
						tools.flipRows(grid);
					} else {
						continue;
					}
				}

				// make sure its in the expected orientation
				if( !ensureCCW(grid) )
					continue;

				// If symmetric, ensure that the (0,0) is closest to top-left image corner
				putIntoCanonical(grid);

				// now extract the calibration points
				return computeCalibrationPoints(grid);
			}
		}

		return false;
	}

	/**
	 * Ensures that the grid is in a CCW order.  It is assumed that (0,0) is a square.
	 *
	 * @return true if it was able to make it CCW or false if it failed to
	 */
	boolean ensureCCW( SquareGrid grid ) {
		if( grid.columns <= 2 && grid.rows <= 2 )
			return true;

		Point2D_F64 a,b,c;

		a = grid.get(0,0).center;
		if( grid.columns > 2)
			b = grid.get(0,2).center;
		else
			b = grid.get(1,1).center;

		if( grid.rows > 2)
			c = grid.get(2,0).center;
		else
			c = grid.get(1,1).center;

		double x0 = b.x-a.x;
		double y0 = b.y-a.y;

		double x1 = c.x-a.x;
		double y1 = c.y-a.y;

		double z = x0 * y1 - y0 * x1;
		if( z < 0 ) {
			// flip it along an axis which is symmetric
			if( grid.columns%2 == 1 )
				tools.flipColumns(grid);
			else if( grid.rows%2 == 1 )
				tools.flipRows(grid);
			else
				return false;
		}
		return true;
	}

	/**
	 * Examines the grid and makes sure the (0,0) square is the closest one ot the top left corner.
	 * Only flip operations are allowed along symmetric axises
	 */
	void putIntoCanonical( SquareGrid grid ) {

		boolean rowOdd = grid.rows%2 == 1;
		boolean colOdd = grid.columns%2 == 1;

		if( colOdd == rowOdd ) {
			// if odd and square then 4 solutions.  Otherwise just two solution that are on
			// opposite sides on the grid
			if( rowOdd && grid.rows == grid.columns ) {
				int best = -1;
				double bestDistance = Double.MAX_VALUE;
				for (int i = 0; i < 4; i++) {
					SquareNode n = grid.getCornerByIndex(i);
					double d = n.center.normSq();
					if( d < bestDistance ) {
						best = i;
						bestDistance = d;
					}
				}

				for (int i = 0; i < best; i++) {
					tools.rotateCCW(grid);
				}
			} else {
				double first = grid.get(0,0).center.normSq();
				double last = grid.getCornerByIndex(2).center.normSq();

				if( last < first ) {
					tools.reverse(grid);
				}
			}
		}
		// if only one is odd then there is a unique solution.  Since uber is already in a legit
		// configuration nothing needs ot be done
	}

	/**
	 * Find inner corner points across the grid.  Start from the "top" row and work its way down.  Corners
	 * are found by finding the average point between two adjacent corners on adjacent squares.
	 */
	boolean computeCalibrationPoints(SquareGrid grid) {
		calibrationPoints.reset();

		for (int row = 0; row < grid.rows-1; row++) {
			int offset = row%2;
			for (int col = offset; col < grid.columns; col += 2) {
				SquareNode a = grid.get(row,col);

				if( col > 0 ) {
					SquareNode b = grid.get(row+1,col-1);
					if( !setIntersection(a,b,calibrationPoints.grow()))
						return false;
				}

				if( col < grid.columns-1) {
					SquareNode b = grid.get(row+1,col+1);
					if( !setIntersection(a,b,calibrationPoints.grow()))
						return false;
				}
			}
		}

		return true;
	}

	private boolean setIntersection( SquareNode a , SquareNode n , Point2D_F64 point ) {
		for (int i = 0; i < 4; i++) {
			SquareEdge edge = a.edges[i];
			if( edge != null && edge.destination(a) == n ) {
				Point2D_F64 p0 = edge.a.corners.get(edge.sideA);
				Point2D_F64 p1 = edge.b.corners.get(edge.sideB);

				point.x = (p0.x+p1.x)/2.0;
				point.y = (p0.y+p1.y)/2.0;
				return true;
			}
		}
		return false;
	}

	public List> getGraphs() {
		return clusters;
	}

	public SquaresIntoCrossClusters getShapeToClusters() {
		return s2c;
	}

	public CrossClustersIntoGrids getGrids() {
		return c2g;
	}

	public BinaryPolygonDetector getDetectorSquare() {
		return detectorSquare;
	}

	public FastQueue getCalibrationPoints() {
		return calibrationPoints;
	}

	public int getNumRows() {
		return numRows;
	}

	public int getNumCols() {
		return numCols;
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy