diff --git a/build.gradle b/build.gradle index edd4773dd8..266fe25699 100644 --- a/build.gradle +++ b/build.gradle @@ -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 { diff --git a/main/calibration/build.gradle b/main/calibration/build.gradle index e773a531e6..74d88f365a 100644 --- a/main/calibration/build.gradle +++ b/main/calibration/build.gradle @@ -4,6 +4,7 @@ dependencies { compile project(':main:geo') testCompile project(':main:io') + testCompile project(':main:visualize') } idea { diff --git a/main/calibration/src/boofcv/abst/calib/WrapPlanarChessTarget.java b/main/calibration/src/boofcv/abst/calib/PlanarDetectorChessboard.java similarity index 95% rename from main/calibration/src/boofcv/abst/calib/WrapPlanarChessTarget.java rename to main/calibration/src/boofcv/abst/calib/PlanarDetectorChessboard.java index 03464192af..e45a194806 100644 --- a/main/calibration/src/boofcv/abst/calib/WrapPlanarChessTarget.java +++ b/main/calibration/src/boofcv/abst/calib/PlanarDetectorChessboard.java @@ -30,13 +30,13 @@ * * @author Peter Abeles */ -public class WrapPlanarChessTarget implements PlanarCalibrationDetector { +public class PlanarDetectorChessboard implements PlanarCalibrationDetector { DetectChessCalibrationPoints alg; List layoutPoints; - public WrapPlanarChessTarget(ConfigChessboard config ) { + public PlanarDetectorChessboard(ConfigChessboard config) { alg = new DetectChessCalibrationPoints( config.numCols,config.numRows,config.nonmaxRadius, config.relativeSizeThreshold,ImageFloat32.class); diff --git a/main/calibration/src/boofcv/abst/calib/WrapPlanarSquareGridTarget.java b/main/calibration/src/boofcv/abst/calib/PlanarDetectorSquareGrid.java similarity index 93% rename from main/calibration/src/boofcv/abst/calib/WrapPlanarSquareGridTarget.java rename to main/calibration/src/boofcv/abst/calib/PlanarDetectorSquareGrid.java index 40f3a59961..29eb7c9092 100644 --- a/main/calibration/src/boofcv/abst/calib/WrapPlanarSquareGridTarget.java +++ b/main/calibration/src/boofcv/abst/calib/PlanarDetectorSquareGrid.java @@ -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; @@ -58,7 +62,7 @@ public class WrapPlanarSquareGridTarget implements PlanarCalibrationDetector { ConfigSquareGrid config; List layoutPoints; - public WrapPlanarSquareGridTarget( ConfigSquareGrid config ) { + public PlanarDetectorSquareGrid(ConfigSquareGrid config) { this.config = config; refine = new WrapRefineCornerSegmentFit(); // refine = new WrapRefineCornerCanny(); @@ -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; } diff --git a/main/calibration/src/boofcv/factory/calib/FactoryPlanarCalibrationTarget.java b/main/calibration/src/boofcv/factory/calib/FactoryPlanarCalibrationTarget.java index f1659fbdc8..ac2a76f72f 100644 --- a/main/calibration/src/boofcv/factory/calib/FactoryPlanarCalibrationTarget.java +++ b/main/calibration/src/boofcv/factory/calib/FactoryPlanarCalibrationTarget.java @@ -36,7 +36,7 @@ public class FactoryPlanarCalibrationTarget { public static PlanarCalibrationDetector detectorSquareGrid( ConfigSquareGrid config) { config.checkValidity(); - return new WrapPlanarSquareGridTarget(config); + return new PlanarDetectorSquareGrid(config); } /** @@ -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); } } diff --git a/main/calibration/test/boofcv/abst/calib/GenericPlanarCalibrationDetectorChecks.java b/main/calibration/test/boofcv/abst/calib/GenericPlanarCalibrationDetectorChecks.java new file mode 100644 index 0000000000..c93fbe85c5 --- /dev/null +++ b/main/calibration/test/boofcv/abst/calib/GenericPlanarCalibrationDetectorChecks.java @@ -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 points = new ArrayList(); + + PointTransform_F32 d2o; + PointTransform_F64 o2d; + + + public abstract void renderTarget( ImageFloat32 original , List 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 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 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 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 found , boolean applyTransform ) { + List expected = new ArrayList(); + + 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 associatedPairs = new ArrayList(); + 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); + } +} diff --git a/main/calibration/test/boofcv/abst/calib/TestCalibrateMonoPlanar.java b/main/calibration/test/boofcv/abst/calib/TestCalibrateMonoPlanar.java index 16db673f24..1483f5134f 100644 --- a/main/calibration/test/boofcv/abst/calib/TestCalibrateMonoPlanar.java +++ b/main/calibration/test/boofcv/abst/calib/TestCalibrateMonoPlanar.java @@ -73,7 +73,7 @@ private class FakeDetector implements PlanarCalibrationDetector { List obs; - List layout = WrapPlanarSquareGridTarget.createLayout(3,4,30,30); + List layout = PlanarDetectorSquareGrid.createLayout(3, 4, 30, 30); @Override public boolean process(ImageFloat32 input) { diff --git a/main/calibration/test/boofcv/abst/calib/TestPlanarDetectorChessboard.java b/main/calibration/test/boofcv/abst/calib/TestPlanarDetectorChessboard.java new file mode 100644 index 0000000000..7044aa15a4 --- /dev/null +++ b/main/calibration/test/boofcv/abst/calib/TestPlanarDetectorChessboard.java @@ -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 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 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); + } +} \ No newline at end of file diff --git a/main/calibration/test/boofcv/abst/calib/TestPlanarDetectorSquareGrid.java b/main/calibration/test/boofcv/abst/calib/TestPlanarDetectorSquareGrid.java new file mode 100644 index 0000000000..cab93d09a8 --- /dev/null +++ b/main/calibration/test/boofcv/abst/calib/TestPlanarDetectorSquareGrid.java @@ -0,0 +1,97 @@ +/* + * 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.assertEquals; + +/** + * @author Peter Abeles + */ +public class TestPlanarDetectorSquareGrid extends GenericPlanarCalibrationDetectorChecks { + + private final static ConfigSquareGrid config = new ConfigSquareGrid(3, 5, 30,30); + + { + config.relativeSizeThreshold = 0.6; + } + + @Test + public void createLayout() { + List l = PlanarDetectorSquareGrid.createLayout(3, 5, 0.1, 0.2); + + assertEquals(4*6,l.size()); + + double w = l.get(1).x - l.get(0).x; + double h = l.get(0).y - l.get(4).y ; + + assertEquals(0.1,w,1e-8); + assertEquals(0.1,h,1e-8); + + double s = l.get(2).x - l.get(1).x; + + assertEquals(0.2, s, 1e-8); + } + + @Override + public void renderTarget(ImageFloat32 original, List 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 += 2) { + int y = y0 + i * square; + + for (int j = 0; j < config.numCols; j += 2) { + int x = x0 + j * square; + ImageMiscOps.fillRectangle(original, 0, x, y, square, square); + } + } + + int pointsRow = config.numRows+1; + int pointsCol = config.numCols+1; + + for (int i = 0; i < pointsRow; i++) { + for (int j = 0; j < pointsCol; j++) { + double y = y0 + i*square; + double x = x0 + j*square; + points.add(new Point2D_F64(x, y)); + } + } + + } + + @Override + public PlanarCalibrationDetector createDetector() { + return FactoryPlanarCalibrationTarget.detectorSquareGrid(config); + } +} \ No newline at end of file diff --git a/main/calibration/test/boofcv/abst/calib/TestWrapPlanarChessTarget.java b/main/calibration/test/boofcv/abst/calib/TestWrapPlanarChessTarget.java deleted file mode 100644 index a34a374089..0000000000 --- a/main/calibration/test/boofcv/abst/calib/TestWrapPlanarChessTarget.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 org.junit.Test; - -import static org.junit.Assert.fail; - -/** - * @author Peter Abeles - */ -public class TestWrapPlanarChessTarget { - @Test - public void stuff() { - fail("Implement"); - } - - @Test - public void createLayout() { - fail("Implement"); - } -} \ No newline at end of file diff --git a/main/calibration/test/boofcv/abst/calib/TestWrapPlanarSquareGridTarget.java b/main/calibration/test/boofcv/abst/calib/TestWrapPlanarSquareGridTarget.java deleted file mode 100644 index 59d919f5e1..0000000000 --- a/main/calibration/test/boofcv/abst/calib/TestWrapPlanarSquareGridTarget.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 georegression.struct.point.Point2D_F64; -import org.junit.Test; - -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; - -/** - * @author Peter Abeles - */ -public class TestWrapPlanarSquareGridTarget { - @Test - public void stuff() { - fail("Implement"); - } - - @Test - public void createLayout() { - List l = WrapPlanarSquareGridTarget.createLayout(3,5,0.1,0.2); - - assertEquals(4*6,l.size()); - - double w = l.get(1).x - l.get(0).x; - double h = l.get(0).y - l.get(4).y ; - - assertEquals(0.1,w,1e-8); - assertEquals(0.1,h,1e-8); - - double s = l.get(2).x - l.get(1).x; - - assertEquals(0.2, s, 1e-8); - } -} \ No newline at end of file diff --git a/main/calibration/test/boofcv/alg/geo/calibration/GenericCalibrationGrid.java b/main/calibration/test/boofcv/alg/geo/calibration/GenericCalibrationGrid.java index 75c804123f..90d05fd44a 100644 --- a/main/calibration/test/boofcv/alg/geo/calibration/GenericCalibrationGrid.java +++ b/main/calibration/test/boofcv/alg/geo/calibration/GenericCalibrationGrid.java @@ -20,7 +20,7 @@ import boofcv.abst.calib.ConfigSquareGrid; import boofcv.abst.calib.PlanarCalibrationDetector; -import boofcv.abst.calib.WrapPlanarSquareGridTarget; +import boofcv.abst.calib.PlanarDetectorSquareGrid; import boofcv.factory.calib.FactoryPlanarCalibrationTarget; import georegression.geometry.GeometryMath_F64; import georegression.geometry.RotationMatrixGenerator; @@ -42,7 +42,7 @@ public class GenericCalibrationGrid { public static List standardLayout() { - return WrapPlanarSquareGridTarget.createLayout(4,5,30,30); + return PlanarDetectorSquareGrid.createLayout(4, 5, 30, 30); } public static PlanarCalibrationDetector createStandardConfig() { diff --git a/main/calibration/test/boofcv/alg/geo/calibration/TestZhang99OptimizationJacobian.java b/main/calibration/test/boofcv/alg/geo/calibration/TestZhang99OptimizationJacobian.java index 7fa8fb0a02..c4dcd3f1a4 100644 --- a/main/calibration/test/boofcv/alg/geo/calibration/TestZhang99OptimizationJacobian.java +++ b/main/calibration/test/boofcv/alg/geo/calibration/TestZhang99OptimizationJacobian.java @@ -18,7 +18,7 @@ package boofcv.alg.geo.calibration; -import boofcv.abst.calib.WrapPlanarSquareGridTarget; +import boofcv.abst.calib.PlanarDetectorSquareGrid; import georegression.struct.point.Point2D_F64; import org.ddogleg.optimization.JacobianChecker; import org.junit.Test; @@ -48,7 +48,7 @@ public void compareToNumeric() { private void compareToNumerical(boolean assumeZeroSkew, boolean includeTangential ) { Zhang99ParamAll param = GenericCalibrationGrid.createStandardParam(assumeZeroSkew, 2,includeTangential, 3, rand); - List gridPts = WrapPlanarSquareGridTarget.createLayout(1,1,30,30); + List gridPts = PlanarDetectorSquareGrid.createLayout(1, 1, 30, 30); List> observations = new ArrayList>();