
boofcv.alg.fiducial.calib.chess.DetectChessSquarePoints Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of recognition Show documentation
Show all versions of recognition Show documentation
BoofCV is an open source Java library for real-time computer vision and robotics applications.
/*
* 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