/* * 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.shapes; import boofcv.struct.PointIndex_I32; import georegression.geometry.UtilEllipse_F64; import georegression.struct.point.Point2D_F64; import georegression.struct.point.Point2D_I32; import georegression.struct.shapes.EllipseRotated_F64; import georegression.struct.trig.Circle2D_F64; import org.ddogleg.struct.GrowQueue_F64; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.*; /** * @author Peter Abeles */ public class TestShapeFittingOps { /** * Fit a polygon to a simple rectangle, loop assumed */ @Test public void fitPolygon_loop() { List<Point2D_I32> sequence = createRectangle(); List<PointIndex_I32> result = ShapeFittingOps.fitPolygon(sequence,true,0.05,0,100); assertEquals(4, result.size()); checkPolygon(new int[]{5, 0, 5, 9, 0, 9, 0, 0}, new int[]{5, 14, 19, 0}, result); } /** * Fit a polygon to a simple rectangle, not looped */ @Test public void fitPolygon_regular() { List<Point2D_I32> sequence = createRectangle(); List<PointIndex_I32> result = ShapeFittingOps.fitPolygon(sequence,false,0.05,0,100); assertEquals(5, result.size()); checkPolygon(new int[]{0, 0, 5, 0, 5, 9, 0, 9, 0, 1}, new int[]{0, 5, 14, 19, 27}, result); } /** * Checks found polygon in a "shift" independent manor */ public static void checkPolygon( int[] coordinate , int indexes[], List<PointIndex_I32> found ) { assertEquals(indexes.length, found.size()); for (int i = 0; i < found.size(); i++) { boolean matched = true; for (int j = 0; j < found.size(); j++) { int x = coordinate[j*2]; int y = coordinate[j*2+1]; int index = indexes[j]; if( !check(x,y,index,found.get((i+j)%found.size()))) { matched = false; break; } } if( matched ) return; } fail("No match"); } /** * Check the found solution */ @Test public void fitEllipse_F64() { EllipseRotated_F64 rotated = new EllipseRotated_F64(1,2,3,2,-0.05); List<Point2D_F64> points = new ArrayList<>(); for( int i = 0; i < 20; i++ ) { double theta = 2.0*(double)Math.PI*i/20; points.add(UtilEllipse_F64.computePoint(theta, rotated, null)); } EllipseRotated_F64 found = ShapeFittingOps.fitEllipse_F64(points,0,false,null).shape; assertEquals(rotated.center.x,found.center.x,1e-8); assertEquals(rotated.center.y,found.center.y,1e-8); assertEquals(rotated.a,found.a,1e-8); assertEquals(rotated.b,found.b,1e-8); assertEquals(rotated.phi,found.phi,1e-8); // make sure refinement doesn't skew it up found = ShapeFittingOps.fitEllipse_F64(points,20,false,null).shape; assertEquals(rotated.center.x,found.center.x,1e-8); assertEquals(rotated.center.y,found.center.y,1e-8); assertEquals(rotated.a,found.a,1e-8); assertEquals(rotated.b,found.b,1e-8); assertEquals(rotated.phi,found.phi,1e-8); } /** * Request that error be computed */ @Test public void fitEllipse_F64_error() { EllipseRotated_F64 rotated = new EllipseRotated_F64(1,2,3,2,-0.05); List<Point2D_F64> points = new ArrayList<>(); for( int i = 0; i < 20; i++ ) { double theta = 2.0*(double)Math.PI*i/20; points.add(UtilEllipse_F64.computePoint(theta, rotated, null)); } points.get(5).x += 2; // Algebraic solution FitData<EllipseRotated_F64> found = ShapeFittingOps.fitEllipse_F64(points,0,false,null); assertTrue(found.error == 0); // make sure refinement doesn't skew it up found = ShapeFittingOps.fitEllipse_F64(points,0,true,null); assertTrue(found.error > 0); // Refined solution found = ShapeFittingOps.fitEllipse_F64(points,10,false,null); assertTrue(found.error == 0); // make sure refinement doesn't skew it up found = ShapeFittingOps.fitEllipse_F64(points,10,true,null); assertTrue(found.error > 0); } /** * Checks to see if they produce the same solution */ @Test public void fitEllipse_I32() { EllipseRotated_F64 rotated = new EllipseRotated_F64(1,2,3,2,-0.05); List<Point2D_F64> pointsF = new ArrayList<>(); List<Point2D_I32> pointsI = new ArrayList<>(); for( int i = 0; i < 20; i++ ) { double theta = 2.0*(double)Math.PI*i/20; Point2D_F64 p = UtilEllipse_F64.computePoint(theta, rotated, null); Point2D_I32 pi = new Point2D_I32((int)p.x,(int)p.y) ; p.set(pi.x,pi.y); pointsF.add(p); pointsI.add(pi); } EllipseRotated_F64 expected = ShapeFittingOps.fitEllipse_F64(pointsF,0,false,null).shape; EllipseRotated_F64 found = ShapeFittingOps.fitEllipse_I32(pointsI, 0, false, null).shape; assertEquals(expected.center.x, found.center.x,1e-8); assertEquals(expected.center.y, found.center.y,1e-8); assertEquals(expected.a, found.a,1e-8); assertEquals(expected.b, found.b,1e-8); assertEquals(expected.phi, found.phi,1e-8); } @Test public void averageCircle_I32() { List<Point2D_I32> points = new ArrayList<>(); points.add( new Point2D_I32(0,0)); points.add( new Point2D_I32(10,0)); points.add( new Point2D_I32(5,5)); points.add( new Point2D_I32(5,-5)); FitData<Circle2D_F64> found = ShapeFittingOps.averageCircle_I32(points, null, null); assertEquals(5,found.shape.center.x,1e-5); assertEquals(0,found.shape.center.y,1e-5); assertEquals(5,found.shape.radius,1e-5); assertEquals(0, found.error, 1e-5); // Pass in storage and see if it fails found.error = 23; found.shape.center.x = 3; GrowQueue_F64 optional = new GrowQueue_F64(); optional.push(4); ShapeFittingOps.averageCircle_I32(points, optional, found); assertEquals(5,found.shape.center.x,1e-5); assertEquals(0,found.shape.center.y,1e-5); assertEquals(5,found.shape.radius,1e-5); assertEquals(0, found.error, 1e-5); // now make it no longer a perfect fit points.get(0).x = -1; found = ShapeFittingOps.averageCircle_I32(points, null, null); assertTrue( found.error > 0 ); } private static boolean check( int x , int y , int index , PointIndex_I32 found ) { if( x != found.x ) return false; if( y != found.y ) return false; return index == found.index; } @Test public void averageCircle_F64() { List<Point2D_F64> points = new ArrayList<>(); points.add( new Point2D_F64(0,0)); points.add( new Point2D_F64(10,0)); points.add( new Point2D_F64(5,5)); points.add( new Point2D_F64(5,-5)); FitData<Circle2D_F64> found = ShapeFittingOps.averageCircle_F64(points, null, null); assertEquals(5, found.shape.center.x, 1e-5); assertEquals(0,found.shape.center.y,1e-5); assertEquals(5,found.shape.radius,1e-5); assertEquals(0, found.error, 1e-5); // Pass in storage and see if it fails found.error = 23; found.shape.center.x = 3; GrowQueue_F64 optional = new GrowQueue_F64(); optional.push(4); ShapeFittingOps.averageCircle_F64(points, optional, found); assertEquals(5,found.shape.center.x,1e-5); assertEquals(0,found.shape.center.y,1e-5); assertEquals(5,found.shape.radius,1e-5); assertEquals(0, found.error, 1e-5); // now make it no longer a perfect fit points.get(0).x = -1; found = ShapeFittingOps.averageCircle_F64(points, null, null); assertTrue(found.error > 0); } /** * Creates a simple rectangle */ public static List<Point2D_I32> createRectangle() { return createRectangle_I32(6,10,(6+10)*2 - 4); } public static List<Point2D_I32> createRectangle_I32( int width , int height , int numPoints ) { List<Point2D_I32> points = new ArrayList<>(); int length = width*2 + height*2 - 4; for (int i = 0; i < numPoints; i++) { int x = i*length/numPoints; if( x < width ) { points.add( new Point2D_I32(x,0)); } else if( x < width+height-2) { int y = x - width+1; points.add( new Point2D_I32(width-1,y)); } else if( x < width*2+height-2) { int xx = x - width - height+3; points.add( new Point2D_I32(width-xx,height-1)); } else { int y = x - width*2 - height+4; points.add( new Point2D_I32(0,height-y)); } } // for( Point2D_I32 p : points ) { // System.out.println(p); // } // System.out.println("Length = "+points.size()); return points; } public static List<Point2D_F64> createRectangle_F64( int width , int height , int numPoints ) { return ShapeFittingOps.convert_I32_F64(createRectangle_I32(width,height,numPoints)); } public static List<Point2D_F64> createEllipse_F64( EllipseRotated_F64 ellipse , int numPoints ) { List<Point2D_F64> sequence = new ArrayList<>(); for (int i = 0; i < numPoints; i++) { double theta = 2.0*Math.PI*i/numPoints; Point2D_F64 p = new Point2D_F64(); UtilEllipse_F64.computePoint(theta,ellipse,p); sequence.add( p ); } return sequence; } }