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

boofcv.alg.fiducial.calib.squares.SquareCrossClustersIntoGrids 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) 2021, 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.squares;

import org.ddogleg.struct.DogArray;
import org.jetbrains.annotations.Nullable;

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

import static boofcv.misc.CircularIndex.addOffset;

/**
 * Takes as input a set of unordered cross connected clusters and converts them into ordered grids with known numbers
 * of rows and columns. The output will be valid "chessboard" pattern. When rows and columns are discussed in the
 * code below it refers to both white and black squares in the chessboard. A row that starts with a white square
 * is referred to as white and one which starts with a black square as black.
 *
 * @author Peter Abeles
 */
public class SquareCrossClustersIntoGrids {

	DogArray grids = new DogArray<>(SquareGrid::new);

	// indicates if a fatal error was found in the grid
	protected boolean invalid;

	/**
	 * Converts all the found clusters into grids, if they are valid.
	 *
	 * @param clusters List of clusters
	 */
	public void process( List> clusters ) {
		grids.reset();
		for (int i = 0; i < clusters.size(); i++) {
			if (checkPreconditions(clusters.get(i)))
				processCluster(clusters.get(i));
		}
	}

	/**
	 * Checks basic preconditions.
	 * 1) No node may be linked two more than once
	 */
	protected boolean checkPreconditions( List cluster ) {
		for (int i = 0; i < cluster.size(); i++) {
			SquareNode n = cluster.get(i);
			for (int j = 0; j < n.square.size(); j++) {
				SquareEdge e0 = n.edges[j];
				if (e0 == null)
					continue;
				for (int k = j + 1; k < n.square.size(); k++) {
					SquareEdge e1 = n.edges[k];
					if (e1 == null)
						continue;
					if (e0.destination(n) == e1.destination(n)) {
						return false;
					}
				}
			}
		}
		return true;
	}

	/**
	 * Converts the cluster into a grid data structure. If its not a grid then
	 * nothing happens
	 */
	protected void processCluster( List cluster ) {
		invalid = false;
		// handle a special case
		if (cluster.size() == 1) {
			SquareNode n = cluster.get(0);
			if (n.getNumberOfConnections() == 0) {
				SquareGrid grid = grids.grow();
				grid.reset();
				grid.columns = grid.rows = 1;
				grid.nodes.add(n);
				return;
			}
		}

		for (int i = 0; i < cluster.size(); i++) {
			cluster.get(i).graph = SquareNode.RESET_GRAPH;
		}

		SquareNode seed = findSeedNode(cluster);

		if (seed == null)
			return;

		// find the first row
		List firstRow;
		if (seed.getNumberOfConnections() == 1) {
			firstRow = firstRow1(seed);
		} else if (seed.getNumberOfConnections() == 2) {
			firstRow = firstRow2(seed);
		} else {
			throw new RuntimeException("BUG");
		}
		if (invalid || firstRow == null)
			return;

		// Add the next rows to the list, one after another
		List> listRows = new ArrayList<>();// TODO remove memory declaration here
		listRows.add(firstRow);
		while (true) {
			List previous = listRows.get(listRows.size() - 1);
			if (!addNextRow(previous.get(0), listRows)) {
				break;
			}
		}

		if (invalid || listRows.size() < 2)
			return;

		// re-organize into a grid data structure
		SquareGrid grid = assembleGrid(listRows);

		// check the grids connectivity
		if (grid == null || !checkEdgeCount(grid)) {
			grids.removeTail();
		}
	}

	/**
	 * Converts the list of rows into a grid. Since it is a chessboard pattern some of the grid
	 * elements will be null.
	 */
	private @Nullable SquareGrid assembleGrid( List> listRows ) {
		SquareGrid grid = grids.grow();
		grid.reset();

		List row0 = listRows.get(0);
		List row1 = listRows.get(1);

		int offset = row0.get(0).getNumberOfConnections() == 1 ? 0 : 1;

		grid.columns = row0.size() + row1.size();
		grid.rows = listRows.size();

		// initialize grid to null
		for (int i = 0; i < grid.columns*grid.rows; i++) {
			grid.nodes.add(null);
		}

		// fill in the grid
		for (int row = 0; row < listRows.size(); row++) {
			List list = listRows.get(row);
			int startCol = offset - row%2 == 0 ? 0 : 1;
			// make sure there is the expected number of elements in the row
			int adjustedLength = grid.columns - startCol;
			if (adjustedLength - adjustedLength/2 != list.size()) {
				return null;
			}
			int listIndex = 0;
			for (int col = startCol; col < grid.columns; col += 2) {
				grid.set(row, col, list.get(listIndex++));
			}
		}
		return grid;
	}

	/**
	 * Looks at the edge count in each node and sees if it has the expected number
	 */
	private boolean checkEdgeCount( SquareGrid grid ) {

		int left = 0, right = grid.columns - 1;
		int top = 0, bottom = grid.rows - 1;

		for (int row = 0; row < grid.rows; row++) {
			boolean skip = grid.get(row, 0) == null;

			for (int col = 0; col < grid.columns; col++) {
				SquareNode n = grid.get(row, col);
				if (skip) {
					if (n != null)
						return false;
				} else {
					boolean horizontalEdge = col == left || col == right;
					boolean verticalEdge = row == top || row == bottom;

					boolean outer = horizontalEdge || verticalEdge;
					int connections = n.getNumberOfConnections();

					if (outer) {
						if (horizontalEdge && verticalEdge) {
							if (connections != 1)
								return false;
						} else if (connections != 2)
							return false;
					} else {
						if (connections != 4)
							return false;
					}
				}
				skip = !skip;
			}
		}
		return true;
	}

	/**
	 * Adds the first row to the list of rows when the seed element has only one edge
	 */
	@Nullable List firstRow1( SquareNode seed ) {
		for (int i = 0; i < seed.square.size(); i++) {
			if (isOpenEdge(seed, i)) {
				List list = new ArrayList<>();
				seed.graph = 0;

				// Doesn't know which direction it can traverse along. See figure that out
				// by looking at the node its linked to
				int corner = seed.edges[i].destinationSide(seed);
				SquareNode dst = seed.edges[i].destination(seed);
				int l = addOffset(corner, -1, dst.square.size());
				int u = addOffset(corner, 1, dst.square.size());

				if (dst.edges[u] != null) {
					list.add(seed);
					if (!addToRow(seed, i, -1, true, list)) return null;
				} else if (dst.edges[l] != null) {
					List tmp = new ArrayList<>();
					if (!addToRow(seed, i, 1, true, tmp)) return null;
					flipAdd(tmp, list);
					list.add(seed);
				} else {
					// there is only a single node below it
					list.add(seed);
				}

				return list;
			}
		}
		throw new RuntimeException("BUG");
	}

	/**
	 * Adds the first row to the list of rows when the seed element has two edges
	 */
	@Nullable List firstRow2( SquareNode seed ) {
		int indexLower = lowerEdgeIndex(seed);
		int indexUpper = addOffset(indexLower, 1, seed.square.size());

		List listDown = new ArrayList<>();
		List list = new ArrayList<>();

		if (!addToRow(seed, indexUpper, 1, true, listDown)) return null;
		flipAdd(listDown, list);
		list.add(seed);
		seed.graph = 0;
		if (!addToRow(seed, indexLower, -1, true, list)) return null;

		return list;
	}

	/**
	 * Given a node, add all the squares in the row directly below it. They will be ordered from "left" to "right". The
	 * seed node can be anywhere in the row, e.g. middle, start, end.
	 *
	 * @return true if a row was added to grid and false if not
	 */
	boolean addNextRow( SquareNode seed, List> grid ) {
		List row = new ArrayList<>();
		List tmp = new ArrayList<>();

		int numConnections = numberOfOpenEdges(seed);
		if (numConnections == 0) {
			return false;
		} else if (numConnections == 1) {
			for (int i = 0; i < seed.square.size(); i++) {
				SquareEdge edge = seed.edges[i];
				if (edge != null) {
					// see if the edge is one of the open ones
					SquareNode dst = edge.destination(seed);
					if (dst.graph != SquareNode.RESET_GRAPH)
						continue;

					// determine which direction to traverse along
					int corner = edge.destinationSide(seed);
					int l = addOffset(corner, -1, dst.square.size());
					int u = addOffset(corner, 1, dst.square.size());

					// Nodes in the seed's row should all be marked, so any unmarked nodes
					// are ones you don't want to traverse down
					if (isClosedValidEdge(dst, l)) {
						if (!addToRow(seed, i, 1, false, tmp)) return false;
						flipAdd(tmp, row);
					} else if (isClosedValidEdge(dst, u)) {
						if (!addToRow(seed, i, -1, false, row)) return false;
					} else {
						dst.graph = 0;
						row.add(dst);
					}
					break;
				}
			}
		} else if (numConnections == 2) {
			int indexLower = lowerEdgeIndex(seed);
			int indexUpper = addOffset(indexLower, 1, seed.square.size());

			if (!addToRow(seed, indexUpper, 1, false, tmp)) return false;
			flipAdd(tmp, row);
			if (!addToRow(seed, indexLower, -1, false, row)) return false;
		} else {
			return false;
		}
		grid.add(row);
		return true;
	}

	private void flipAdd( List tmp, List row ) {
		for (int i = tmp.size() - 1; i >= 0; i--) {
			row.add(tmp.get(i));
		}
	}

	/**
	 * Returns the open corner index which is first. Assuming that there are two adjacent corners.
	 */
	static int lowerEdgeIndex( SquareNode node ) {
		for (int i = 0; i < node.square.size(); i++) {
			if (isOpenEdge(node, i)) {
				int next = addOffset(i, 1, node.square.size());
				if (isOpenEdge(node, next)) {
					return i;
				}
				if (i == 0) {
					int previous = node.square.size() - 1;
					if (isOpenEdge(node, previous)) {
						return previous;
					}
				}
				return i;
			}
		}

		throw new RuntimeException("BUG!");
	}

	static int numberOfOpenEdges( SquareNode node ) {
		int total = 0;
		for (int i = 0; i < node.square.size(); i++) {
			if (isOpenEdge(node, i))
				total++;
		}
		return total;
	}

	/**
	 * Is the edge open and can be traversed to?  Can't be null and can't have
	 * the marker set to a none RESET_GRAPH value.
	 */
	static boolean isOpenEdge( SquareNode node, int index ) {
		if (node.edges[index] == null)
			return false;
		int marker = node.edges[index].destination(node).graph;
		return marker == SquareNode.RESET_GRAPH;
	}

	/**
	 * Does the edge point some place, but is closed
	 */
	static boolean isClosedValidEdge( SquareNode node, int index ) {
		if (node.edges[index] == null)
			return false;
		int marker = node.edges[index].destination(node).graph;
		return marker != SquareNode.RESET_GRAPH;
	}

	/**
	 * Given a node and the corner to the next node down the line, add to the list every other node until
	 * it hits the end of the row.
	 *
	 * @param n Initial node
	 * @param corner Which corner points to the next node
	 * @param sign Determines the direction it will traverse. -1 or 1
	 * @param skip true = start adding nodes at second, false = start first.
	 * @param row List that the nodes are placed into
	 */
	boolean addToRow( SquareNode n, int corner, int sign, boolean skip,
					  List row ) {
		SquareEdge e;
		while ((e = n.edges[corner]) != null) {
			if (e.a == n) {
				n = e.b;
				corner = e.sideB;
			} else {
				n = e.a;
				corner = e.sideA;
			}
			if (!skip) {
				if (n.graph != SquareNode.RESET_GRAPH) {
					// This should never happen in a valid grid. It can happen if two nodes link to each other multiple
					// times. Other situations as well
					invalid = true;
					return false;
				}
				n.graph = 0;
				row.add(n);
			}
			skip = !skip;
			sign *= -1;
			corner = addOffset(corner, sign, n.square.size());
		}
		return true;
	}

	/**
	 * Finds a seed with 1 or 2 edges.
	 */
	static SquareNode findSeedNode( List cluster ) {
		SquareNode seed = null;

		for (int i = 0; i < cluster.size(); i++) {
			SquareNode n = cluster.get(i);
			int numConnections = n.getNumberOfConnections();
			if (numConnections == 0 || numConnections > 2)
				continue;
			seed = n;
			break;
		}
		return Objects.requireNonNull(seed);
	}

	public DogArray getGrids() {
		return grids;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy