Skip to content

Commit

Permalink
- wrote more unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
lessthanoptimal committed May 2, 2015
1 parent 5a617b9 commit 5e9c570
Show file tree
Hide file tree
Showing 13 changed files with 407 additions and 104 deletions.
2 changes: 1 addition & 1 deletion build.gradle
Expand Up @@ -56,7 +56,7 @@ subprojects {

compile group: 'org.georegression', name: 'georegression', version: '0.8-SNAPSHOT'

testCompile group: 'junit', name: 'junit', version: '4.11'
testCompile group: 'junit', name: 'junit', version: '4.12'
}

javadoc {
Expand Down
1 change: 1 addition & 0 deletions main/calibration/build.gradle
Expand Up @@ -4,6 +4,7 @@ dependencies {
compile project(':main:geo')

testCompile project(':main:io')
testCompile project(':main:visualize')
}

idea {
Expand Down
Expand Up @@ -30,13 +30,13 @@
*
* @author Peter Abeles
*/
public class WrapPlanarChessTarget implements PlanarCalibrationDetector {
public class PlanarDetectorChessboard implements PlanarCalibrationDetector {

DetectChessCalibrationPoints<ImageFloat32,ImageFloat32> alg;

List<Point2D_F64> layoutPoints;

public WrapPlanarChessTarget(ConfigChessboard config ) {
public PlanarDetectorChessboard(ConfigChessboard config) {
alg = new DetectChessCalibrationPoints<ImageFloat32, ImageFloat32>(
config.numCols,config.numRows,config.nonmaxRadius,
config.relativeSizeThreshold,ImageFloat32.class);
Expand Down
Expand Up @@ -34,12 +34,16 @@
import java.util.List;

/**
* Implementation of {@link PlanarCalibrationDetector} for square grid target types.
*
* @author Peter Abeles
*/
public class WrapPlanarSquareGridTarget implements PlanarCalibrationDetector {
public class PlanarDetectorSquareGrid implements PlanarCalibrationDetector {

// number of squares in the grid's column
int squareColumns;

// shape of grid in calibration points
int pointColumns;
int pointRows;

Expand All @@ -58,7 +62,7 @@ public class WrapPlanarSquareGridTarget implements PlanarCalibrationDetector {
ConfigSquareGrid config;
List<Point2D_F64> layoutPoints;

public WrapPlanarSquareGridTarget( ConfigSquareGrid config ) {
public PlanarDetectorSquareGrid(ConfigSquareGrid config) {
this.config = config;
refine = new WrapRefineCornerSegmentFit();
// refine = new WrapRefineCornerCanny();
Expand Down Expand Up @@ -115,6 +119,7 @@ public boolean process(ImageFloat32 input) {
orderAlg.getNumRows(),orderAlg.getNumCols(),
pointRows,pointColumns);
} catch( InvalidCalibrationTarget e ) {
System.err.println("Target lost during subpixel estimation. This algorithm needs to be improved");
// e.printStackTrace();
return false;
}
Expand Down
Expand Up @@ -36,7 +36,7 @@ public class FactoryPlanarCalibrationTarget {
public static PlanarCalibrationDetector detectorSquareGrid( ConfigSquareGrid config) {
config.checkValidity();

return new WrapPlanarSquareGridTarget(config);
return new PlanarDetectorSquareGrid(config);
}

/**
Expand All @@ -50,6 +50,6 @@ public static PlanarCalibrationDetector detectorSquareGrid( ConfigSquareGrid con
public static PlanarCalibrationDetector detectorChessboard( ConfigChessboard config ) {
config.checkValidity();

return new WrapPlanarChessTarget(config);
return new PlanarDetectorChessboard(config);
}
}
@@ -0,0 +1,203 @@
/*
* Copyright (c) 2011-2015, 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.abst.calib;

import boofcv.abst.geo.Estimate1ofEpipolar;
import boofcv.alg.distort.DistortImageOps;
import boofcv.alg.distort.PointToPixelTransform_F32;
import boofcv.alg.distort.PointTransformHomography_F32;
import boofcv.alg.distort.PointTransformHomography_F64;
import boofcv.alg.interpolate.TypeInterpolate;
import boofcv.alg.misc.ImageMiscOps;
import boofcv.factory.geo.FactoryMultiView;
import boofcv.gui.image.ShowImages;
import boofcv.io.image.ConvertBufferedImage;
import boofcv.struct.distort.PixelTransform_F32;
import boofcv.struct.distort.PointTransform_F32;
import boofcv.struct.distort.PointTransform_F64;
import boofcv.struct.geo.AssociatedPair;
import boofcv.struct.image.ImageFloat32;
import georegression.struct.point.Point2D_F64;
import org.ejml.data.DenseMatrix64F;
import org.ejml.ops.CommonOps;
import org.junit.Test;

import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
* @author Peter Abeles
*/
public abstract class GenericPlanarCalibrationDetectorChecks {

int width = 300,height= 300;

ImageFloat32 original = new ImageFloat32(width,height);
ImageFloat32 distorted = new ImageFloat32(width, height);
List<Point2D_F64> points = new ArrayList<Point2D_F64>();

PointTransform_F32 d2o;
PointTransform_F64 o2d;


public abstract void renderTarget( ImageFloat32 original , List<Point2D_F64> points );

public abstract PlanarCalibrationDetector createDetector();

public GenericPlanarCalibrationDetectorChecks() {
renderTarget(original,points);
}

/**
* Makes sure origin in the target's physical center. This is done by seeing that most extreme
* points are all equally distant. Can't use the mean since the target might not evenly distributed.
*
* Should this really be a requirement? There is some mathematical justification for it and make sense
* when using it as a fiducial.
*/
@Test
public void targetIsCentered() {
List<Point2D_F64> layout = createDetector().getLayout();

double minX=Double.MAX_VALUE,maxX=-Double.MAX_VALUE;
double minY=Double.MAX_VALUE,maxY=-Double.MAX_VALUE;

for( Point2D_F64 p : layout ) {
if( p.x < minX )
minX = p.x;
if( p.x > maxX )
maxX = p.x;
if( p.y < minY )
minY = p.y;
if( p.y > maxY )
maxY = p.y;
}

assertEquals(Math.abs(minX),Math.abs(maxX),1e-8);
assertEquals(Math.abs(minY),Math.abs(maxY),1e-8);
}

/**
* Easy case with no distortion
*/
@Test
public void undistorted() {
PlanarCalibrationDetector detector = createDetector();

// display(original);

assertTrue(detector.process(original));

List<Point2D_F64> found = detector.getDetectedPoints();

checkList(found, false);
}

/**
* Pinch it a little bit like found with perspective distortion
*/
@Test
public void distorted() {
PlanarCalibrationDetector detector = createDetector();

createTransform(width / 5, height / 5, width * 4 / 5, height / 6, width - 1, height - 1, 0, height - 1);

PixelTransform_F32 pixelTransform = new PointToPixelTransform_F32(d2o);

ImageMiscOps.fill(distorted, 0xff);
DistortImageOps.distortSingle(original, distorted, pixelTransform, true, TypeInterpolate.BILINEAR);

// display(distorted);

assertTrue(detector.process(distorted));

List<Point2D_F64> found = detector.getDetectedPoints();
checkList(found, true);
}

private void display( ImageFloat32 image ) {
BufferedImage visualized = ConvertBufferedImage.convertTo(image, null, true);
ShowImages.showWindow(visualized, "Distorted");

try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public void checkList( List<Point2D_F64> found , boolean applyTransform ) {
List<Point2D_F64> expected = new ArrayList<Point2D_F64>();

if( !applyTransform ) {
expected.addAll(this.points);
} else {
for (int i = 0; i < points.size(); i++) {
Point2D_F64 p = points.get(i).copy();

o2d.compute(p.x, p.y, p);
expected.add(p);
}
}

assertEquals(expected.size(),found.size());
for (int i = 0; i < found.size(); i++) {
Point2D_F64 f = found.get(i);

boolean matched = false;
for (int j = 0; j < expected.size(); j++) {
Point2D_F64 e = expected.get(j);

if( f.distance(e) < 3) {
matched = true;
break;
}
}

assertTrue(matched);
}
}

public void createTransform( double x0 , double y0 , double x1 , double y1 ,
double x2 , double y2 , double x3 , double y3 )
{
// Homography estimation algorithm. Requires a minimum of 4 points
Estimate1ofEpipolar computeHomography = FactoryMultiView.computeHomography(true);

// Specify the pixel coordinates from destination to target
ArrayList<AssociatedPair> associatedPairs = new ArrayList<AssociatedPair>();
associatedPairs.add(new AssociatedPair(x0, y0, 0, 0));
associatedPairs.add(new AssociatedPair(x1, y1, width-1, 0));
associatedPairs.add(new AssociatedPair(x2, y2, width-1, height-1));
associatedPairs.add(new AssociatedPair(x3, y3, 0, height - 1));

// Compute the homography
DenseMatrix64F H = new DenseMatrix64F(3, 3);
computeHomography.process(associatedPairs, H);

// Create the transform for distorting the image
d2o = new PointTransformHomography_F32(H);
CommonOps.invert(H);
o2d = new PointTransformHomography_F64(H);
}
}
Expand Up @@ -73,7 +73,7 @@ private class FakeDetector implements PlanarCalibrationDetector {

List<Point2D_F64> obs;

List<Point2D_F64> layout = WrapPlanarSquareGridTarget.createLayout(3,4,30,30);
List<Point2D_F64> layout = PlanarDetectorSquareGrid.createLayout(3, 4, 30, 30);

@Override
public boolean process(ImageFloat32 input) {
Expand Down
@@ -0,0 +1,89 @@
/*
* Copyright (c) 2011-2015, 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.abst.calib;

import boofcv.alg.misc.ImageMiscOps;
import boofcv.factory.calib.FactoryPlanarCalibrationTarget;
import boofcv.struct.image.ImageFloat32;
import georegression.struct.point.Point2D_F64;
import org.junit.Test;

import java.util.List;

import static org.junit.Assert.assertTrue;

/**
* @author Peter Abeles
*/
public class TestPlanarDetectorChessboard extends GenericPlanarCalibrationDetectorChecks {

private final static ConfigChessboard config = new ConfigChessboard(4,5,30);

@Test
public void createLayout() {
List<Point2D_F64> layout = createDetector().getLayout();

// first control points should be the top left corner then work it's way down in a
// grid pattern
assertTrue(layout.get(0).y == layout.get(2).y);
assertTrue(layout.get(0).x < layout.get(2).x);
assertTrue(layout.get(0).y > layout.get(3).y);

}

@Override
public void renderTarget(ImageFloat32 original, List<Point2D_F64> points) {

ImageMiscOps.fill(original, 255);

int square = original.getWidth()/(Math.max(config.numCols,config.numRows)+4);

int targetWidth = square * config.numCols;
int targetHeight = square * config.numRows;

int x0 = (original.width - targetWidth) / 2;
int y0 = (original.height- targetHeight) / 2;

for (int i = 0; i < config.numRows; i++) {
int y = y0 + i*square;

int startJ = i%2 == 0 ? 0 : 1;
for (int j = startJ; j < config.numCols; j += 2) {
int x = x0 + j * square;
ImageMiscOps.fillRectangle(original,0,x,y,square,square);
}
}

int pointsRow = 2*(config.numRows/2) - (1 - config.numRows % 2);
int pointsCol = 2*(config.numCols/2) - (1 - config.numCols % 2);

for (int i = 0; i < pointsRow; i++) {
for (int j = 0; j < pointsCol; j++) {
double y = y0+(i+1)*square;
double x = x0+(j+1)*square;
points.add( new Point2D_F64(x,y));
}
}
}

@Override
public PlanarCalibrationDetector createDetector() {
return FactoryPlanarCalibrationTarget.detectorChessboard(config);
}
}

0 comments on commit 5e9c570

Please sign in to comment.