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

boofcv.alg.fiducial.calib.circle.DetectCircleGrid 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: 1.1.6
Show newest version
/*
 * 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.circle;

import boofcv.abst.filter.binary.InputToBinary;
import boofcv.alg.fiducial.calib.circle.EllipseClustersIntoGrid.Grid;
import boofcv.alg.shapes.ellipse.BinaryEllipseDetector;
import boofcv.struct.image.GrayU8;
import boofcv.struct.image.ImageGray;
import georegression.struct.curve.EllipseRotated_F64;
import org.ddogleg.struct.DogArray;

import java.util.ArrayList;
import java.util.List;

/**
 * Base class for grid based circle fiducials.
 *
 * @author Peter Abeles
 */
public abstract class DetectCircleGrid> {

	protected BinaryEllipseDetector ellipseDetector;
	private final InputToBinary inputToBinary;

	private final GrayU8 binary = new GrayU8(1, 1);

	// description of the calibration target
	protected int numRows, numCols;

	// converts ellipses into clusters
	private final EllipsesIntoClusters clustering;
	private final EllipseClustersIntoGrid grider;

	// List of all the found valid grids in the image
	private final List validGrids = new ArrayList<>();

	// local work space for manipulating the order of points inside a grid
	private final List work = new ArrayList<>();

	// storage for found clusters
	private final List> clusters = new ArrayList<>();
	private final List> clustersPruned = new ArrayList<>();

	// verbose printing to standard  out
	private boolean verbose = false;

	/**
	 * Creates and configures the detector
	 *
	 * @param numRows number of rows in grid
	 * @param numCols number of columns in grid
	 * @param inputToBinary Converts the input image into a binary image
	 * @param ellipseDetector Detects ellipses inside the image
	 * @param clustering Finds clusters of ellipses
	 */
	protected DetectCircleGrid( int numRows, int numCols,
								InputToBinary inputToBinary,
								BinaryEllipseDetector ellipseDetector,
								EllipsesIntoClusters clustering,
								EllipseClustersIntoGrid grider ) {
		this.ellipseDetector = ellipseDetector;
		this.inputToBinary = inputToBinary;
		this.numRows = numRows;
		this.numCols = numCols;

		this.clustering = clustering;
		this.grider = grider;
	}

	/**
	 * Processes the image and finds grids. To retrieve the found grids call {@link #getGrids()}
	 *
	 * @param gray Input image
	 */
	public void process( T gray ) {
		if (verbose) System.out.println("ENTER DetectCircleGrid.process()");

		configureContourDetector(gray);
		this.binary.reshape(gray.width, gray.height);

		inputToBinary.process(gray, binary);

		ellipseDetector.process(gray, binary);
		List found = ellipseDetector.getFound().toList();

		if (verbose) System.out.println("  Found " + found.size() + " ellpises");

		clusters.clear();
		clustering.process(found, clusters);
		clustersPruned.clear();
		clustersPruned.addAll(clusters);

		if (verbose) System.out.println("  Found " + clusters.size() + " clusters");
		pruneIncorrectSize(clustersPruned, totalEllipses(numRows, numCols));
		if (verbose) System.out.println("  Remaining clusters after pruning by size " + clustersPruned.size());

		List foundEllipses = new ArrayList<>();
		for (int i = 0; i < found.size(); i++) {
			foundEllipses.add(found.get(i).ellipse);
		}

		grider.process(foundEllipses, clustersPruned);

		DogArray grids = grider.getGrids();

		if (verbose) System.out.println("  Found " + grids.size() + " grids");
		pruneIncorrectShape(grids, numRows, numCols);
		if (verbose) System.out.println("  Remaining grids after pruning by shape " + grids.size());

		validGrids.clear();
		for (int i = 0; i < grids.size(); i++) {
			Grid g = grids.get(i);
			putGridIntoCanonical(g);
			validGrids.add(g);
		}

		if (verbose) System.out.println("EXIT DetectCircleGrid.process()");
	}

	/**
	 * 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 foot print significantly.
	 */
	protected abstract void configureContourDetector( T gray );

	/**
	 * Computes the number of ellipses on the grid
	 */
	protected abstract int totalEllipses( int numRows, int numCols );

	/**
	 * Puts the grid into a canonical orientation
	 */
	protected abstract void putGridIntoCanonical( Grid g );

	/**
	 * Number of CCW rotations to put selected corner into the canonical location. Only works
	 * when there are 4 possible solutions
	 *
	 * @param g The grid
	 * @return number of rotations
	 */
	static int closestCorner4( Grid g ) {
		double bestDistance = g.get(0, 0).center.normSq();
		int bestIdx = 0;

		double d = g.get(0, g.columns - 1).center.normSq();
		if (d < bestDistance) {
			bestDistance = d;
			bestIdx = 3;
		}
		d = g.get(g.rows - 1, g.columns - 1).center.normSq();
		if (d < bestDistance) {
			bestDistance = d;
			bestIdx = 2;
		}
		d = g.get(g.rows - 1, 0).center.normSq();
		if (d < bestDistance) {
			bestIdx = 1;
		}

		return bestIdx;
	}

	/**
	 * performs a counter-clockwise rotation
	 */
	void rotateGridCCW( Grid g ) {

		work.clear();
		for (int i = 0; i < g.rows*g.columns; i++) {
			work.add(null);
		}

		for (int row = 0; row < g.rows; row++) {
			for (int col = 0; col < g.columns; col++) {
				work.set(col*g.rows + row, g.get(g.rows - row - 1, col));
			}
		}

		g.ellipses.clear();
		g.ellipses.addAll(work);
		int tmp = g.columns;
		g.columns = g.rows;
		g.rows = tmp;
	}

	/**
	 * Reverse the order of elements inside the grid
	 */
	void reverse( Grid g ) {
		work.clear();
		int N = g.rows*g.columns;
		for (int i = 0; i < N; i++) {
			work.add(g.ellipses.get(N - i - 1));
		}

		g.ellipses.clear();
		g.ellipses.addAll(work);
	}

	void flipHorizontal( Grid g ) {

		work.clear();
		for (int row = 0; row < g.rows; row++) {
			for (int col = 0; col < g.columns; col++) {
				work.add(g.get(row, g.columns - col - 1));
			}
		}

		g.ellipses.clear();
		g.ellipses.addAll(work);
	}

	void flipVertical( Grid g ) {

		work.clear();
		for (int row = 0; row < g.rows; row++) {
			for (int col = 0; col < g.columns; col++) {
				work.add(g.get(g.rows - row - 1, col));
			}
		}

		g.ellipses.clear();
		g.ellipses.addAll(work);
	}

	/**
	 * Remove grids which cannot possible match the expected shape
	 */
	static void pruneIncorrectShape( DogArray grids, int numRows, int numCols ) {
		// prune clusters which can't be a member calibration target
		for (int i = grids.size() - 1; i >= 0; i--) {
			Grid g = grids.get(i);
			if ((g.rows != numRows || g.columns != numCols) && (g.rows != numCols || g.columns != numRows)) {
				grids.remove(i);
			}
		}
	}

	/**
	 * Prune clusters which do not have the expected number of elements
	 */
	static void pruneIncorrectSize( List> clusters, int N ) {
		// prune clusters which can't be a member calibration target
		for (int i = clusters.size() - 1; i >= 0; i--) {
			if (clusters.get(i).size() != N) {
				clusters.remove(i);
			}
		}
	}

	public BinaryEllipseDetector getEllipseDetector() {
		return ellipseDetector;
	}

	public EllipseClustersIntoGrid getGrider() {
		return grider;
	}

	public EllipsesIntoClusters getClustering() {
		return clustering;
	}

	public List> getClusters() {
		return clusters;
	}

	public List> getClustersPruned() {
		return clustersPruned;
	}

	/**
	 * List of grids found inside the image
	 */
	public List getGrids() {
		return validGrids;
	}

	public GrayU8 getBinary() {
		return binary;
	}

	public int getColumns() {
		return numCols;
	}

	public int getRows() {
		return numRows;
	}

	public boolean isVerbose() {
		return verbose;
	}

	public void setVerbose( boolean verbose ) {
		this.ellipseDetector.setVerbose(System.out, null);
		this.grider.setVerbose(verbose);
		this.verbose = verbose;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy