/* * 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.alg.shapes.polygon; import boofcv.alg.distort.PixelTransformAffine_F32; import boofcv.testing.BoofTesting; import georegression.geometry.UtilPolygons2D_F64; import georegression.metric.Distance2D_F64; import georegression.struct.affine.Affine2D_F64; import georegression.struct.line.LineGeneral2D_F64; import georegression.struct.point.Point2D_F64; import georegression.struct.se.Se2_F64; import georegression.struct.shapes.Polygon2D_F64; import georegression.struct.shapes.Quadrilateral_F64; import georegression.transform.ConvertTransform_F64; import org.junit.Test; import static java.lang.Math.max; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * @author Peter Abeles */ @SuppressWarnings("unchecked") public class TestRefinePolygonLineToImage extends BaseFitPolygon { /** * Give it a shape which is too small and see if it fails */ @Test public void fit_tooSmall() { final boolean black = true; Polygon2D_F64 input = new Polygon2D_F64(5,5, 5,6, 6,6, 6,5); for (Class imageType : imageTypes) { setup(new Affine2D_F64(), black, imageType); RefinePolygonLineToImage alg = createAlg(input.size(),imageType); Polygon2D_F64 output = new Polygon2D_F64(input.size()); alg.setImage(image); assertFalse(alg.refine(input,null,null, output)); } } /** * Makes sure it can handle sub-images */ @Test public void fit_subimage() { final boolean black = true; Polygon2D_F64 input = new Polygon2D_F64(x0,y0 , x0,y1, x1,y1, x1,y0); for (Class imageType : imageTypes) { setup(new Affine2D_F64(), black, imageType); RefinePolygonLineToImage alg = createAlg(input.size(),imageType); Polygon2D_F64 output = new Polygon2D_F64(4); alg.setImage(image); assertTrue(alg.refine(input,null,null, output)); // do it again with a sub-image Polygon2D_F64 output2 = new Polygon2D_F64(4); image = BoofTesting.createSubImageOf_S(image); alg.setImage(image); assertTrue(alg.refine(input,null,null, output2)); assertTrue(UtilPolygons2D_F64.isIdentical(output, output2, 1e-8)); } } /** * Fit a square which is alligned to the image axis. It should get a nearly perfect fit. * Initial conditions are be a bit off. */ @Test public void alignedSquare() { Polygon2D_F64 original = new Polygon2D_F64(x0,y0 , x0,y1, x1,y1, x1,y0); for (Class imageType : imageTypes) { for (int i = 0; i < 2; i++) { boolean black = i == 0; setup(new Affine2D_F64(), black, imageType); RefinePolygonLineToImage alg = createAlg(original.size(),imageType); for (int j = 0; j < 20; j++) { Polygon2D_F64 input = original.copy(); addNoise(input,2); Polygon2D_F64 output = new Polygon2D_F64(original.size()); alg.setImage(image); assertTrue(alg.refine(input,null,null, output)); assertTrue(original.isIdentical(output, 0.01)); } } } } /** * See if it handles lines along the image border correctly */ @Test public void fitWithEdgeOnBorder() { for (Class imageType : imageTypes) { x0 = 0; x1 = 100; y0 = 100; y1 = 200; setup(null, true, imageType); RefinePolygonLineToImage alg = createAlg(4,imageType); Polygon2D_F64 input = createFromSquare(null); input.get(0).set(x0,y0+1.1); input.get(1).set(x0,y1-1.1); Polygon2D_F64 found = new Polygon2D_F64(4); alg.setImage(image); assertTrue(alg.refine(input,null,null,found)); Polygon2D_F64 expected = createFromSquare(null); assertTrue(expected.isIdentical(found, 0.01)); } } /** * Perfect initial guess. */ @Test public void fit_perfect_affine() { // distorted and undistorted views Affine2D_F64 affines[] = new Affine2D_F64[2]; affines[0] = new Affine2D_F64(); affines[1] = new Affine2D_F64(1.3,0.05,-0.15,0.87,0.1,0.6); ConvertTransform_F64.convert(new Se2_F64(0,0,0.2),affines[0]); for (Class imageType : imageTypes) { for (Affine2D_F64 a : affines) { // System.out.println(imageType+" "+a); fit_perfect_affine(true, a, imageType); fit_perfect_affine(false, a, imageType); } } } public void fit_perfect_affine(boolean black, Affine2D_F64 affine, Class imageType) { setup(affine, black, imageType); RefinePolygonLineToImage alg = createAlg(4,imageType); Polygon2D_F64 input = createFromSquare(affine); Polygon2D_F64 expected = input.copy(); Polygon2D_F64 found = new Polygon2D_F64(4); alg.setImage(image); assertTrue(alg.refine(input,null,null, found)); // input shouldn't be modified assertTrue(expected.isIdentical(input,0)); // should be close to the expected assertTrue(expected.isIdentical(found,0.27)); // do it again with a sub-image to see if it handles that image = BoofTesting.createSubImageOf_S(image); alg.setImage(image); assertTrue(alg.refine(input,null,null, found)); assertTrue(expected.isIdentical(input,0)); assertTrue(expected.isIdentical(found,0.27)); } /** * Perfect initial guess. But provide a transform which will undo the affine transform */ @Test public void fit_perfect_transform() { // distorted and undistorted views Affine2D_F64 affines[] = new Affine2D_F64[1]; affines[0] = new Affine2D_F64(0.8,0,0,0.8,0,0); for (Class imageType : imageTypes) { for (Affine2D_F64 a : affines) { // System.out.println(imageType+" "+a); fit_perfect_transform(true, a, imageType); fit_perfect_transform(false, a, imageType); } } } public void fit_perfect_transform(boolean black, Affine2D_F64 regToDist, Class imageType) { setup(regToDist, black, imageType); RefinePolygonLineToImage alg = createAlg(4,imageType); Polygon2D_F64 input = createFromSquare(null); Polygon2D_F64 expected = input.copy(); Polygon2D_F64 found = new Polygon2D_F64(4); alg.setImage(image); // fail without the transform assertFalse(alg.refine(input,null,null, found)); // work when the transform is applied PixelTransformAffine_F32 transform = new PixelTransformAffine_F32(); transform.set(regToDist); alg.setTransform(transform); alg.setImage(image); assertTrue(alg.refine(input,null,null, found)); // should be close to the expected assertTrue(expected.isIdentical(found,0.3)); } /** * Fit the quad with a noisy initial guess */ @Test public void fit_noisy_affine() { // distorted and undistorted views Affine2D_F64 affines[] = new Affine2D_F64[2]; affines[0] = new Affine2D_F64(); affines[1] = new Affine2D_F64(1.3,0.05,-0.15,0.87,0.1,0.6); ConvertTransform_F64.convert(new Se2_F64(0,0,0.2),affines[0]); for (Class imageType : imageTypes) { for (Affine2D_F64 a : affines) { // System.out.println(imageType+" "+a); fit_noisy_affine(true, a, imageType); fit_noisy_affine(false, a, imageType); } } } public void fit_noisy_affine(boolean black, Affine2D_F64 affine, Class imageType) { setup(affine, black, imageType); RefinePolygonLineToImage alg = createAlg(4,imageType); Polygon2D_F64 input = createFromSquare(affine); Polygon2D_F64 expected = input.copy(); Polygon2D_F64 found = new Polygon2D_F64(4); for (int i = 0; i < 10; i++) { // add some noise input.set(expected); addNoise(input, 2); alg.setImage(image); assertTrue(alg.refine(input,null,null, found)); // should be close to the expected double before = computeMaxDistance(input,expected); double after = computeMaxDistance(found,expected); assertTrue(after < before); assertTrue(expected.isIdentical(found, 0.5)); //----- Reverse the order and it should still work input.flip(); assertTrue(alg.refine(input,null,null, found)); found.flip(); after = computeMaxDistance(found, expected); assertTrue(after<before); assertTrue(expected.isIdentical(found, 0.5)); } } private void addNoise(Polygon2D_F64 input, double spread) { for (int i = 0; i < input.size(); i++) { Point2D_F64 v = input.get(i); v.x += rand.nextDouble()*spread - spread/2.0; v.y += rand.nextDouble()*spread - spread/2.0; } } private RefinePolygonLineToImage createAlg( int numSides , Class imageType) { return new RefinePolygonLineToImage(numSides,imageType); } /** * Optimize a line with a perfect initial guess */ @Test public void optimize_line_perfect() { for (Class imageType : imageTypes) { optimize_line_perfect(true, imageType); optimize_line_perfect(false, imageType); } } public void optimize_line_perfect(boolean black, Class imageType) { setup(null, black, imageType); RefinePolygonLineToImage alg = createAlg(4,imageType); Quadrilateral_F64 input = new Quadrilateral_F64(x0,y0,x0,y1,x1,y1,x1,y0); LineGeneral2D_F64 found = new LineGeneral2D_F64(); alg.setImage(image); assertTrue(alg.optimize(input.a, input.b, found)); assertTrue(Distance2D_F64.distance(found, input.a) <= 1e-4); assertTrue(Distance2D_F64.distance(found, input.b) <= 1e-4); } private double computeMaxDistance(Polygon2D_F64 expected, Polygon2D_F64 found ) { double a = 0; for (int i = 0; i < expected.size(); i++) { a = max(a,expected.get(i).distance(found.get(i))); } return a; } }