/*
* 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.abst.distort.FDistort;
import boofcv.abst.fiducial.calib.ConfigChessboard;
import boofcv.abst.filter.binary.InputToBinary;
import boofcv.alg.misc.ImageMiscOps;
import boofcv.alg.shapes.polygon.BinaryPolygonDetector;
import boofcv.factory.filter.binary.FactoryThresholdBinary;
import boofcv.factory.shape.FactoryShapeDetector;
import boofcv.struct.image.GrayF32;
import georegression.struct.point.Point2D_F64;
import georegression.struct.se.Se2_F64;
import georegression.transform.se.SePointOps_F64;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* @author Peter Abeles
*/
@SuppressWarnings("unchecked")
public class TestDetectChessboardFiducial {
Random rand = new Random(234);
int squareLength = 30;
int w = 500;
int h = 550;
int offsetX = 15;
int offsetY = 10;
Se2_F64 transform;
@Before
public void setup() {
offsetX = 15;
offsetY = 10;
transform = null;
}
/**
* Give it a simple target and see if it finds the expected number of squares
*/
@Test
public void basicTest() {
for( int numRows = 3; numRows <= 7; numRows++ ) {
for( int numCols = 3; numCols <= 7; numCols++ ) {
// System.out.println("shape "+numCols+" "+numRows);
basicTest(numRows, numCols, true);
}
}
}
public void basicTest(int numRows, int numCols , boolean localThreshold ) {
GrayF32 gray = renderTarget(numRows, numCols);
ImageMiscOps.addGaussian(gray,rand,0.1,0,255);
// ShowImages.showWindow(gray,"Rendered Image");
// try { Thread.sleep(1000); } catch (InterruptedException e) {}
ConfigChessboard configChess = new ConfigChessboard(5, 5, 1);
BinaryPolygonDetector<GrayF32> detectorSquare =
FactoryShapeDetector.polygon(configChess.square, GrayF32.class);
// detectorSquare.setVerbose(true);
InputToBinary<GrayF32> inputToBinary;
if( localThreshold )
inputToBinary = FactoryThresholdBinary.localSquareBlockMinMax(10,0.90,true,10,GrayF32.class);
else
inputToBinary = FactoryThresholdBinary.globalFixed(50,true,GrayF32.class);
DetectChessboardFiducial alg =
new DetectChessboardFiducial(numRows, numCols, 4,detectorSquare,null,null,inputToBinary);
assertTrue(alg.process(gray));
List<Point2D_F64> found = alg.getCalibrationPoints();
List<Point2D_F64> expected = calibrationPoints(numRows, numCols);
assertEquals(expected.size(), found.size());
// check the ordering of the points
for( int i = 0; i < expected.size(); i++ ) {
Point2D_F64 e = expected.get(i);
Point2D_F64 f = found.get(i);
if( transform != null ) {
SePointOps_F64.transform(transform,e,e);
}
assertEquals("i = " + i,e.x,f.x,2);
assertEquals("i = " + i,e.y,f.y,2);
}
}
public GrayF32 renderTarget(int numRows, int numCols) {
GrayF32 gray = new GrayF32(w,h);
ImageMiscOps.fill(gray,80f);
int numCols2 = numCols/2;
int numRows2 = numRows/2;
numCols = numCols/2 + numCols%2;
numRows = numRows/2 + numRows%2;
// create the grid
for( int y = 0; y < numRows; y++) {
for( int x = 0; x < numCols; x++ ) {
int pixelY = 2*y*squareLength+offsetY;
int pixelX = 2*x*squareLength+offsetX;
ImageMiscOps.fillRectangle(gray, 20, pixelX, pixelY, squareLength, squareLength);
}
}
for( int y = 0; y < numRows2; y++) {
for( int x = 0; x < numCols2; x++ ) {
int pixelY = 2*y*squareLength+offsetY+squareLength;
int pixelX = 2*x*squareLength+offsetX+squareLength;
ImageMiscOps.fillRectangle(gray, 20, pixelX, pixelY, squareLength, squareLength);
}
}
if( transform != null ) {
GrayF32 distorted = new GrayF32(gray.width,gray.height);
FDistort f = new FDistort(gray,distorted);
f.border(80f).affine(transform.c,-transform.s,transform.s,transform.c,
transform.T.x,transform.T.y).apply();
gray = distorted;
}
return gray;
}
public List<Point2D_F64> calibrationPoints(int numRows, int numCols) {
List<Point2D_F64> ret = new ArrayList<>();
for( int y = 0; y < numRows-1; y++) {
for( int x = 0; x < numCols-1; x++ ) {
int pixelY = y*squareLength+offsetY+squareLength;
int pixelX = x*squareLength+offsetX+squareLength;
ret.add( new Point2D_F64(pixelX,pixelY));
}
}
return ret;
}
/**
* See if it can detect targets which touch the image border when thresholded using a
* global algorithm.
*
* This doesn't test all possible cases.
*/
@Test
public void touchesBorder_translate() {
for (int i = 0; i < 4; i++) {
if( i%2 == 0 )
offsetX = 0;
else
offsetX = 15;
if( i/2 == 0 )
offsetY = 0;
else
offsetY = 10;
basicTest(3,4, false);
}
}
@Test
public void touchedBorder_rotate() {
List<Se2_F64> transforms = new ArrayList<>();
transforms.add( new Se2_F64(0,0,0.1));
transforms.add( new Se2_F64(w-100,0,0.1));
transforms.add( new Se2_F64(w/2,-5,0.4));
transforms.add( new Se2_F64(w/2,h-105,0.4));
transforms.add( new Se2_F64(w/2,h-100,0.4));
transforms.add( new Se2_F64(35,40,Math.PI/4));
transforms.add( new Se2_F64(50,-5,0.05));
transforms.add( new Se2_F64(50,-25,0.05));
transforms.add( new Se2_F64(50,h-65,-0.05));
transforms.add( new Se2_F64(50,h-90,0.05));
offsetX = 0;
offsetY = 0;
for(Se2_F64 t : transforms ) {
transform = t;
basicTest(3,4, false);
basicTest(3,4, true);
}
}
}