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

boofcv.alg.fiducial.calib.ecocheck.ECoCheckDetector Maven / Gradle / Ivy

/*
 * 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.ecocheck;

import boofcv.BoofVerbose;
import boofcv.abst.fiducial.calib.ConfigChessboardX;
import boofcv.alg.feature.detect.chess.ChessboardCorner;
import boofcv.alg.feature.detect.chess.DetectChessboardCornersXPyramid;
import boofcv.alg.fiducial.calib.chess.ChessboardCornerClusterFinder;
import boofcv.alg.fiducial.calib.chess.ChessboardCornerClusterToGrid;
import boofcv.alg.fiducial.calib.chess.ChessboardCornerClusterToGrid.GridElement;
import boofcv.alg.fiducial.calib.chess.ChessboardCornerClusterToGrid.GridInfo;
import boofcv.alg.fiducial.calib.chess.ChessboardCornerGraph;
import boofcv.alg.fiducial.qrcode.PackedBits8;
import boofcv.alg.interpolate.InterpolatePixelS;
import boofcv.alg.interpolate.InterpolationType;
import boofcv.alg.misc.ImageMiscOps;
import boofcv.factory.interpolate.FactoryInterpolation;
import boofcv.misc.BoofMiscOps;
import boofcv.struct.GridCoordinate;
import boofcv.struct.border.BorderType;
import boofcv.struct.image.GrayU8;
import boofcv.struct.image.ImageGray;
import boofcv.struct.image.ImageType;
import georegression.struct.line.LineSegment2D_F64;
import georegression.struct.point.Point2D_F64;
import lombok.Getter;
import org.ddogleg.struct.*;
import org.jetbrains.annotations.Nullable;

import java.io.PrintStream;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import static boofcv.alg.fiducial.calib.ecocheck.ECoCheckUtils.rotateObserved;

/**
 * 

Detects chessboard patterns with marker and grid coordinate information encoded inside of the inner white squares. * Multiple unique markers can be detected and damaged/partial targets will work. If no binary patterns are found * in a chessboard, then an "anonymous" pattern is returned. Anonymous chessboards are useful when trying to track * distant targets. Which corners were decoded next to a binary pattern is recorded as those corners are extremely * unlikely to be a false positive. *

* * Processing steps: 1) x-corner detector. 2) find clusters of corners. 3) clusters into grids. 4) detect encoded binary * data inside of grids. 5) align coordinate systems * * @author Peter Abeles */ public class ECoCheckDetector> implements VerbosePrint { // TODO Add back in the ability to encoded every N squares /** Number of times a side is sampled when determining binarization threshold */ final int NUM_SAMPLES_SIDE = 5; // number of samples along each side /** * Number of points along a side it will sample when trying to determine if a border is white. Disable by settings * to zero. */ public int whiteBorderSampleCount = 5; /** If more than this number of points fail the test consider it a failure */ public double maxWhiteBorderFailFraction = 0.3; /** Common utilities for decoding ECoCheck patterns */ @Getter protected ECoCheckUtils utils; /** Chessboard corner detector */ @Getter protected DetectChessboardCornersXPyramid detector; /** Cluster Finder */ @Getter protected ChessboardCornerClusterFinder clusterFinder; /** Cluster to grid */ @Getter protected ChessboardCornerClusterToGrid clusterToGrid = new ChessboardCornerClusterToGrid(); /** Used to sample the input image when "undistorting" the bit pattern */ public InterpolatePixelS interpolate; /** Found chessboard patterns */ @Getter public final DogArray found = new DogArray<>(ECoCheckFound::new, ECoCheckFound::reset); // Binary cells in grid pattern for easy access FastArray gridBinaryCells = new FastArray<>(CellReading.class); // All decoded binary cells DogArray binaryCells = new DogArray<>(CellReading::new, CellReading::reset); // Image pixels that are read when decoding the bit pattern DogArray samplePixels = new DogArray<>(Point2D_F64::new); // Storage for sampled pixel values DogArray_F32 sampleValues = new DogArray_F32(); // The read in bits in a format the codec can understand PackedBits8 bits = new PackedBits8(); // Storage the bits inside an image so that it can be rotated easily GrayU8 bitImage = new GrayU8(1, 1); // Stores intermediate results when rotating GrayU8 workImage = new GrayU8(1, 1); // Used to indicate which corners are around a binary pattern GrayU8 cornersAroundBinary = new GrayU8(1, 1); // Found transform from found to a marker coordinate system DogArray transforms = new DogArray<>(Transform::new, Transform::reset); // Workspace for anonymous chessboards GridInfo anonymousInfo = new GridInfo(); // Storage for a decided binary pattern CellValue decoded = new CellValue(); // Coordinate decoded from cellID GridCoordinate decodedCoordinate = new GridCoordinate(); // Coordinate of the cell observed on the grid taking in account orientation GridCoordinate observedCoordinate = new GridCoordinate(); // workspace for white border test LineSegment2D_F64 lineBlack = new LineSegment2D_F64(); LineSegment2D_F64 lineWhite = new LineSegment2D_F64(); Point2D_F64 pixel = new Point2D_F64(); // Verbose print debugging @Nullable PrintStream verbose; // If true it will print profiling information to verbose out boolean runtimeProfiling; // Processing time for different components in milliseconds @Getter double timeCornerDetectorMS; @Getter double timeClusteringMS; @Getter double timeGridMS; @Getter double timeDecodingMS; @Getter double timeAllMS = 0; /** * Specifies configuration for detector */ public ECoCheckDetector( ECoCheckUtils utils, ConfigChessboardX config, Class imageType ) { this.utils = utils; this.utils.checkFixate(); detector = new DetectChessboardCornersXPyramid<>(ImageType.single(imageType)); clusterFinder = new ChessboardCornerClusterFinder<>(imageType); detector.setPyramidTopSize(config.detPyramidTopSize); detector.getDetector().setNonmaxRadius(config.detNonMaxRadius); detector.getDetector().setNonmaxThresholdRatio((float)config.detNonMaxThresholdRatio); detector.getDetector().setRefinedXCornerThreshold(config.detRefinedXCornerThreshold); clusterFinder.setAmbiguousTol(config.connAmbiguousTol); clusterFinder.setDirectionTol(config.connDirectionTol); clusterFinder.setOrientationTol(config.connOrientationTol); clusterFinder.setMaxNeighbors(config.connMaxNeighbors); clusterFinder.setMaxNeighborDistance(config.connMaxNeighborDistance); clusterFinder.setThresholdEdgeIntensity(config.connEdgeThreshold); interpolate = FactoryInterpolation.createPixelS(0, 255, InterpolationType.NEAREST_NEIGHBOR, BorderType.EXTENDED, imageType); bitImage.reshape(utils.codec.gridBitLength, utils.codec.gridBitLength); } /** * Processes the image and searches for all chessboard patterns. */ public void process( T input ) { // reset / initialize data structures timeCornerDetectorMS = 0; timeClusteringMS = 0; timeGridMS = 0; timeDecodingMS = 0; timeAllMS = 0; found.reset(); interpolate.setImage(input); // Find the chessboard corners long time0 = System.nanoTime(); detector.process(input); long time1 = System.nanoTime(); timeCornerDetectorMS = (time1 - time0)*1e-6; // Find chessboard clusters clusterFinder.process(input, detector.getCorners().toList(), detector.getNumberOfLevels()); DogArray clusters = clusterFinder.getOutputClusters(); long time2 = System.nanoTime(); timeClusteringMS = (time2 - time1)*1e-6; // Convert the clusters into grids for (int clusterIdx = 0; clusterIdx < clusters.size; clusterIdx++) { // Find the chessboard pattern inside the cluster long timeGrid0 = System.nanoTime(); if (!clusterToGrid.clusterToSparse(clusters.get(clusterIdx))) { continue; } // Convert it into a dense grid to make it easier to process clusterToGrid.sparseToDense(); long timeGrid1 = System.nanoTime(); timeGridMS += (timeGrid1 - timeGrid0)*1e-6; // TODO make sure the grid has the // Go through and find all the white squares that could contain data. Attempt to decode decodeBinaryPatterns(); timeDecodingMS += (System.nanoTime() - timeGrid1)*1e-6; // If no data patterns are found, extract the largest grid and return that if (binaryCells.isEmpty()) { createAnonymousTarget(); continue; } // Select the coordinate system that best matches the decoded cells tallyMarkerVotes(); // Sort transform based on the number of votes. The ones with most votes should be first. if (transforms.size > 1) // don't know if line below will allocate memory or not Collections.sort(transforms.toList(), ( a, b ) -> Integer.compare(b.votes, a.votes)); // Two targets could be so close to each other that their chessboards become joined together // Create targets from all hypothesises, unless there's a contradiction for (int transformIdx = 0; transformIdx < transforms.size; transformIdx++) { Transform t = transforms.get(transformIdx); if (!createCorrectedTarget(t, found.grow())) { // conflict was found, abort found.removeTail(); } } } timeAllMS = (System.nanoTime() - time0)*1e-6; if (verbose != null && runtimeProfiling) { verbose.printf("time (ms): all=%.1f corners=%.1f cluster=%.1f grid=%.1f decode=%.1f\n", timeAllMS, timeCornerDetectorMS, timeClusteringMS, timeGridMS, timeDecodingMS); } } /** * Examines all white cells in the found chessboard grid and attempts to decide the binary patterns. */ private void decodeBinaryPatterns() { // Number of rows and columns the cluster algorithm found. These are corners and not squares int rows = clusterToGrid.getSparseRows(); int cols = clusterToGrid.getSparseCols(); // Initialize data structures cornersAroundBinary.reshape(cols, rows); ImageMiscOps.fill(cornersAroundBinary, 0); gridBinaryCells.clear(); gridBinaryCells.resize(rows*cols); binaryCells.reset(); if (verbose != null) { verbose.printf("corner grid: shape=( %d %d ) size=%d\n", rows, cols, clusterToGrid.getSparseGrid().size); } // Go through "squares" in the corner grid for (int row = 1; row < rows; row++) { for (int col = 1; col < cols; col++) { // corners from top-left around the circle GridElement a = clusterToGrid.getDense(row - 1, col - 1); GridElement b = clusterToGrid.getDense(row - 1, col); GridElement c = clusterToGrid.getDense(row, col); GridElement d = clusterToGrid.getDense(row, col - 1); if (a == null || b == null || c == null || d == null) continue; // Grid orientation is currently unknown. We have to assume that any square could be black or white // See if this square could have an encoded value if (!clusterToGrid.isWhiteSquareOrientation(a.node, c.node)) continue; // Compute homography from pixels to data-region coordinates if (!utils.computeGridToImage(a.node.corner, b.node.corner, c.node.corner, d.node.corner)) continue; // This is needed as a backup if isWhiteSquare() is incorrect, which it can be for heavy fisheye/ // Checksum and ECC should catch most errors, but we are concerned about outlier performance. // Verify that the border surrounding data bits is mostly white if (!isBorderWhite(a.node.corner, b.node.corner, c.node.corner, d.node.corner)) { continue; } // Which pixels need to be sampled utils.selectPixelsToSample(samplePixels); samplePixelGray(samplePixels.toList(), sampleValues); // Select a threshold that maximized the variance float threshold = utils.otsuThreshold(sampleValues); // Sample points and compute bit values if (!graySamplesToBits(sampleValues, utils.bitSampleCount, threshold)) continue; boolean success = false; // Try all 4 possible orientations until something works for (int orientation = 0; orientation < 4; orientation++) { convertBitImageToBitArray(); if (!decodeAndSanityCheck()) { // rotate so it can try and see if another orientation works ImageMiscOps.rotateCCW(bitImage, workImage); bitImage.setTo(workImage); continue; } success = true; // mark corners as having a binary code next to them cornersAroundBinary.unsafe_set(col - 1, row - 1, 1); cornersAroundBinary.unsafe_set(col - 1, row, 1); cornersAroundBinary.unsafe_set(col, row - 1, 1); cornersAroundBinary.unsafe_set(col, row, 1); // Save the decoded results into a sparse grid CellReading cell = gridBinaryCells.data[row*cols + col] = binaryCells.grow(); cell.row = row - 1; cell.col = col - 1; cell.orientation = orientation; cell.markerID = decoded.markerID; cell.cellID = decoded.cellID; if (verbose != null) { utils.cellIdToCornerCoordinate(cell.markerID, cell.cellID, decodedCoordinate); verbose.printf("marker=%d id=%d ori=%d code_grid=( %d %d ) obs_grid=( %d %d ) tl=( %.1f %.1f ) tr=( %.1f %.1f )\n", decoded.markerID, decoded.cellID, orientation, decodedCoordinate.row, decodedCoordinate.col, row - 1, col - 1, a.node.corner.x, a.node.corner.y, b.node.corner.x, b.node.corner.y); } break; } if (verbose != null && !success) verbose.printf("Failed to decode. obs_grid=(%d %d) tl=( %.1f %.1f ) thresh=%.1f\n", row - 1, col - 1, a.node.corner.x, a.node.corner.y, threshold); } } } /** * Decode the bits and sanity check the solution to see if it could be correct. */ private boolean decodeAndSanityCheck() { boolean success = false; if (utils.codec.decode(bits, decoded)) { success = true; } // If the marker is out of range it's invalid if (success && decoded.markerID >= utils.markers.size()) { success = false; // This is a rare event. Let's print it just in case something is wrong. if (verbose != null) { verbose.println("Success decoding a cell, but markerID was invalid!"); } } // If the cellID is too larger it's invalid if (success) { int maxCellID = utils.countEncodedSquaresInMarker(decoded.markerID); if (decoded.cellID >= maxCellID) { if (verbose != null) verbose.println("Success decoding a cell, but cellID was invalid!"); success = false; } } return success; } /** * Converts the binary image into a dense bit array that's understood by the codec */ void convertBitImageToBitArray() { bits.resize(bitImage.width*bitImage.height); for (int y = 0, i = 0; y < bitImage.height; y++) { for (int x = 0; x < bitImage.width; x++, i++) { bits.set(utils.bitOrder.get(i), bitImage.data[i]); } } } /** * Samples points offset along the line. The extremes of the line are avoided since those will naturally be * blurry in a chessboard pattern * * @return Average value of tangent points along the side */ float sampleInnerWhite( Point2D_F64 a, Point2D_F64 b ) { double border = utils.dataBorderFraction; double length = 1.0 - 2.0*border; // this specifies how far away from the line it will sample in the tangent direction // We use the border as guide and sample in the middle of it float nx = (float)(0.5*(b.x - a.x)*utils.dataBorderFraction); float ny = (float)(0.5*(b.y - a.y)*utils.dataBorderFraction); float sumWhite = 0.0f; float sumBlack = 0.0f; for (int i = 0; i < NUM_SAMPLES_SIDE; i++) { double f = border + length*i/(double)(NUM_SAMPLES_SIDE - 1); float cx = (float)(a.x + (b.x - a.x)*f); float cy = (float)(a.y + (b.y - a.y)*f); sumWhite += interpolate.get(cx - ny, cy + nx); sumBlack += interpolate.get(cx + ny, cy - nx); } return (sumWhite + sumBlack)/(2.0f*NUM_SAMPLES_SIDE); } /** * RReads the gray scale value at the provided pixel locations * * @param samplePoints (Input) Which pixels to sample. * @param sampleValues (Output) Pixel values */ void samplePixelGray( List samplePoints, DogArray_F32 sampleValues ) { sampleValues.resize(samplePoints.size()); for (int i = 0; i < samplePoints.size(); i++) { Point2D_F64 pixel = samplePoints.get(i); sampleValues.data[i] = interpolate.get((float)pixel.x, (float)pixel.y); } } /** * Samples along the edge making sure the inside is brighter than the outside. */ boolean isBorderWhite( ChessboardCorner a, ChessboardCorner b, ChessboardCorner c, ChessboardCorner d ) { // See if test has been disabled if (whiteBorderSampleCount <= 0) return true; // Scan each side to see if they have the expected brightness pattern int failures = 0; failures += sampleWhiteSide(a, b, 0); failures += sampleWhiteSide(b, c, 1); failures += sampleWhiteSide(c, d, 2); failures += sampleWhiteSide(d, a, 3); boolean success = failures <= 4*whiteBorderSampleCount*maxWhiteBorderFailFraction; if (!success && verbose != null) { verbose.println("FAILED: white border test: " + failures + " / " + (4*whiteBorderSampleCount)); } return success; } /** * Samples the specified side to see if inside points are brighter than outside points. The corner's scale * estimate is used to avoid the corners which tend to have lower contrast * * @return Number of points which failed the brightness check */ int sampleWhiteSide( ChessboardCorner a, ChessboardCorner b, int corner ) { // Use the homography to estimate the center of the white border along this side double r = utils.dataBorderFraction; // set the corners up correctly for each side switch (corner) { case 0 -> { utils.gridToPixel(r, r, lineWhite.a); utils.gridToPixel(1.0 - r, r, lineWhite.b); utils.gridToPixel(r, -r, lineBlack.a); utils.gridToPixel(1.0 - r, -r, lineBlack.b); } case 1 -> { utils.gridToPixel(1.0 - r, r, lineWhite.a); utils.gridToPixel(1.0 - r, 1.0 - r, lineWhite.b); utils.gridToPixel(1.0 + r, r, lineBlack.a); utils.gridToPixel(1.0 + r, 1.0 - r, lineBlack.b); } case 2 -> { utils.gridToPixel(1.0 - r, 1.0 - r, lineWhite.a); utils.gridToPixel(r, 1.0 - r, lineWhite.b); utils.gridToPixel(1.0 - r, 1.0 + r, lineBlack.a); utils.gridToPixel(r, 1.0 + r, lineBlack.b); } case 3 -> { utils.gridToPixel(r, 1.0 - r, lineWhite.a); utils.gridToPixel(r, r, lineWhite.b); utils.gridToPixel(-r, 1.0 - r, lineBlack.a); utils.gridToPixel(-r, r, lineBlack.b); } } // use blur to figure out how much padding is needed to avoid the blurred corner where black/white will // be ambiguous double blurPaddingA = Math.pow(2, a.levelMax); double blurPaddingB = Math.pow(2, b.levelMax); double slopeX = lineWhite.slopeX(); double slopeY = lineWhite.slopeY(); double n = Math.sqrt(slopeX*slopeX + slopeY*slopeY); double fractionStart, fractionEnd; fractionStart = blurPaddingA/n; fractionEnd = 1.0 - blurPaddingB/n; // if the blur is so great that the padding extends beyond the length just sample in the middle if (fractionEnd < fractionStart) { fractionStart = 0.45; fractionEnd = 0.55; } // Sample points along the line int failed = 0; for (int i = 0; i < whiteBorderSampleCount; i++) { double f = (fractionEnd - fractionStart)*i/(whiteBorderSampleCount - 1) + fractionStart; pixel.x = (lineBlack.b.x - lineBlack.a.x)*f + lineBlack.a.x; pixel.y = (lineBlack.b.y - lineBlack.a.y)*f + lineBlack.a.y; float blackValue = interpolate.get((float)pixel.x, (float)pixel.y); pixel.x = (lineWhite.b.x - lineWhite.a.x)*f + lineWhite.a.x; pixel.y = (lineWhite.b.y - lineWhite.a.y)*f + lineWhite.a.y; float whiteValue = interpolate.get((float)pixel.x, (float)pixel.y); // Just check to see if white is brighter than black. Adding a tolerance is problematic since you need // to estimate the tolerance if (whiteValue <= blackValue) failed++; } return failed; } /** * Reads the gray values of data bits inside the square. Votes using the gray threshold. Decides on the bit values * * @param sampleValues (Input) Values of each pixel.. Flattened block array of points in the grid * @param blockSize (Input) How many points are sampled per bit. * @return true if nothing went wrong */ boolean graySamplesToBits( DogArray_F32 sampleValues, int blockSize, float threshold ) { // Sanity check BoofMiscOps.checkEq(bitImage.width*bitImage.height, sampleValues.size()/blockSize); int majority = blockSize/2; int nonWhiteBits = 0; for (int i = 0, bit = 0; i < sampleValues.size(); i += blockSize, bit++) { // Each pixel in the bit's block gets a vote for it's value int vote = 0; for (int blockIdx = 0; blockIdx < blockSize; blockIdx++) { float value = sampleValues.get(i + blockIdx); if (value <= threshold) { vote++; } } int value = vote > majority ? 1 : 0; nonWhiteBits += value; bitImage.data[bit] = (byte)value; } // If every bit is zero then it's a white square reject it return nonWhiteBits != 0; } /** * Find the adjustment from the observed coordinate system to the one encoded and compute the number of matches */ void tallyMarkerVotes() { // Shape of the observed grid int numRows = clusterToGrid.getSparseRows(); int numCols = clusterToGrid.getSparseCols(); transforms.reset(); for (int cellIdx = 0; cellIdx < binaryCells.size; cellIdx++) { CellReading cell = binaryCells.get(cellIdx); // Figure out the encoded coordinate the cell utils.cellIdToCornerCoordinate(cell.markerID, cell.cellID, decodedCoordinate); // rotate the arbitrary observed coordinate system to match the decoded rotateObserved(numRows, numCols, cell.row, cell.col, cell.orientation, observedCoordinate); // After correcting for orientation, the top left corner is in a different location ECoCheckUtils.adjustTopLeft(cell.orientation, observedCoordinate); // Figure out the offset. This should be the same for all encoded cells from the same target int offsetRow = decodedCoordinate.row - observedCoordinate.row; int offsetCol = decodedCoordinate.col - observedCoordinate.col; // Save the coordinate system transform Transform t = findMatching(offsetRow, offsetCol, cell.orientation, cell.markerID); if (t == null) { t = transforms.grow(); t.offsetRow = offsetRow; t.offsetCol = offsetCol; t.marker = cell.markerID; t.orientation = cell.orientation; } // Increment the vote counter t.votes++; } } /** * Finds an existing transform that matches the one just computed */ @Nullable Transform findMatching( int offsetRow, int offsetCol, int orientation, int marker ) { for (int i = 0; i < transforms.size; i++) { Transform t = transforms.get(i); if (t.marker != marker) continue; if (t.offsetRow != offsetRow || t.offsetCol != offsetCol || t.orientation != orientation) continue; return t; } return null; } /** * Applies the transform to all found corners and creates a target ready to be returned. If a corner is impossible * it's assumed to be a false positive and not added. * * @param transform (Input) correction to corner grd coordinate system * @param target (Output) Description of target * @return true if no faults found and it was successful */ boolean createCorrectedTarget( Transform transform, ECoCheckFound target ) { if (verbose != null) verbose.printf("transform: votes=%d marker=%d ori=%d offset={ row=%d col=%d }\n", transform.votes, transform.marker, transform.orientation, transform.offsetRow, transform.offsetCol); target.markerID = transform.marker; // Save the shape of the grid in squares target.squareRows = utils.markers.get(target.markerID).rows; target.squareCols = utils.markers.get(target.markerID).cols; for (int i = 0; i < binaryCells.size; i++) { target.decodedCells.add(binaryCells.get(i).cellID); } // Recycle the variable but give it a better name final GridCoordinate correctedCoordinate = observedCoordinate; // Get shape of the corner grid. int cornerRows = target.squareRows - 1; int cornerCols = target.squareCols - 1; // Go through all the found corners, correct the corner grid coordinate, check if valid, then add to the // found target FastAccess sparseGrid = clusterToGrid.getSparseGrid(); for (int i = 0; i < sparseGrid.size; i++) { GridElement e = sparseGrid.get(i); // Change coordinate system rotateObserved(cornerRows, cornerCols, e.row, e.col, transform.orientation, correctedCoordinate); // Change origin correctedCoordinate.row += transform.offsetRow; correctedCoordinate.col += transform.offsetCol; // Make sure it's inside if (correctedCoordinate.row < 0 || correctedCoordinate.col < 0 || correctedCoordinate.row >= cornerRows || correctedCoordinate.col >= cornerCols) { continue; } // If the row has been marked that means another target already claimed this corner and a false positive // is highly likely if (e.marked) return false; e.marked = true; // The ID is the index from a row-major matrix int cornerID = correctedCoordinate.row*cornerCols + correctedCoordinate.col; // Save the pixel coordinate it was observed at target.addCorner(e.node.corner, cornerID); // Note if this corner touches a binary pattern target.touchBinary.add(cornersAroundBinary.get(e.col, e.row) != 0); } return true; } /** * No binary pattern was found inside the so we don't know which target it is, but this information still might * be useful */ void createAnonymousTarget() { if (!clusterToGrid.sparseToGrid(anonymousInfo)) return; ECoCheckFound target = found.grow(); target.squareRows = anonymousInfo.rows + 1; target.squareCols = anonymousInfo.cols + 1; for (int cornerID = 0; cornerID < anonymousInfo.nodes.size(); cornerID++) { ChessboardCornerGraph.Node n = anonymousInfo.nodes.get(cornerID); target.addCorner(n.corner, cornerID); } } /** * Type of input image it processes */ public ImageType getImageType() { return detector.getImageType(); } @Override public void setVerbose( @Nullable PrintStream out, @Nullable Set configuration ) { this.verbose = BoofMiscOps.addPrefix(this, out); BoofMiscOps.verboseChildren(out, configuration, clusterFinder, clusterToGrid); if (configuration == null) return; runtimeProfiling = configuration.contains(BoofVerbose.RUNTIME); } /** * Records a decoded cell and the location in the observed grid it was decoded at */ static class CellReading { /** Location of cell in the observed coordinate system */ public int row, col; /** Number of times it needed to be rotated before decoded */ public int orientation; /** Decoded marker */ public int markerID; /** Decoded cell */ public int cellID; public void reset() { row = 0; col = 0; orientation = -1; markerID = -1; cellID = -1; } } /** * Transform from the arbitrary grid coordinate system into */ static class Transform { /** Adjustment to grid coordinates after applying the rotation */ public int offsetRow, offsetCol; /** Rotational difference between observed and encoded coordinate system */ public int orientation; /** Marker ID */ public int marker; /** Number of encoded squares that agree with this transform */ public int votes; public void setTo( int offsetRow, int offsetCol, int orientation, int marker, int votes ) { this.offsetRow = offsetRow; this.offsetCol = offsetCol; this.orientation = orientation; this.marker = marker; this.votes = votes; } public void reset() { offsetRow = 0; offsetCol = 0; orientation = 0; votes = 0; marker = -1; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy