/* * 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.edge; import boofcv.abst.distort.FDistort; import boofcv.alg.misc.GImageMiscOps; import boofcv.core.image.GeneralizedImageOps; import boofcv.struct.image.GrayF32; import boofcv.struct.image.GrayU8; import boofcv.struct.image.ImageGray; import boofcv.testing.BoofTesting; import georegression.geometry.UtilLine2D_F64; import georegression.metric.ClosestPoint2D_F64; import georegression.metric.Intersection2D_F64; import georegression.struct.affine.Affine2D_F64; import georegression.struct.line.LineGeneral2D_F64; import georegression.struct.line.LineSegment2D_F64; import georegression.struct.point.Point2D_F64; import georegression.struct.point.Vector2D_F64; import georegression.struct.se.Se2_F64; import georegression.struct.shapes.Polygon2D_F64; import georegression.transform.ConvertTransform_F64; import georegression.transform.affine.AffinePointOps_F64; import org.junit.Test; import java.util.Random; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Peter Abeles */ public class TestSnapToLineEdge { Class imageTypes[] = new Class[]{GrayU8.class, GrayF32.class}; Random rand = new Random(234); int width = 400, height = 500; ImageGray work; // original image before affine has been applied ImageGray image; // image after affine applied int x0 = 200, y0 = 160; int x1 = 260, y1 = 400; // that's exclusive int white = 200; /** * Edges which are aligned with the x and y axises */ @Test public void easy_aligned() { for (Class imageType : imageTypes) { easy_aligned(imageType); } } public void easy_aligned(Class imageType) { setup(null, imageType); SnapToLineEdge alg = new SnapToLineEdge(10,2, imageType); alg.setImage(image); int r = 2; LineSegment2D_F64 bottom = new LineSegment2D_F64(x1-r,y0,x0+r,y0); LineSegment2D_F64 left = new LineSegment2D_F64(x0,y0+r,x0,y1-r); LineSegment2D_F64 top = new LineSegment2D_F64(x0+r,y1,x1-r,y1); LineSegment2D_F64 right = new LineSegment2D_F64(x1,y1-r,x1,y0+r); differentInitial(alg,bottom); differentInitial(alg,left); differentInitial(alg,top); differentInitial(alg,right); } private void differentInitial(SnapToLineEdge alg , LineSegment2D_F64 segment ) { Vector2D_F64 v = new Vector2D_F64(); v.x = -segment.slopeY(); v.y = segment.slopeX(); v.normalize(); LineGeneral2D_F64 found = new LineGeneral2D_F64(); // give it an offset from truth. still simple enough that it should nail the correct solution on the first // try for (int i = -1; i <= 1; i++) { LineSegment2D_F64 work = segment.copy(); work.a.x += i*v.x; work.a.y += i*v.y; work.b.x += i*v.x; work.b.y += i*v.y; assertTrue(alg.refine(work.a, work.b, found)); checkIdentical(segment,found); // do it in the other direction. shouldn't matter assertTrue(alg.refine(work.b, work.a, found)); checkIdentical(segment,found); } } private void checkIdentical( LineSegment2D_F64 expectedLS , LineGeneral2D_F64 found ) { LineGeneral2D_F64 expected = new LineGeneral2D_F64(); UtilLine2D_F64.convert(expectedLS,expected); expected.normalize(); found.normalize(); if( Math.signum(expected.C) != Math.signum(found.C) ) { expected.A *= -1; expected.B *= -1; expected.C *= -1; } assertEquals(expected.A,found.A,1e-8); assertEquals(expected.B,found.B,1e-8); assertEquals(expected.C,found.C,1e-8); } /** * Make sure it can process a sub-image just fine */ @Test public void subimage() { for (Class imageType : imageTypes) { subimage(imageType); } } public void subimage(Class imageType) { setup(null, imageType); SnapToLineEdge alg = new SnapToLineEdge(10,2, imageType); alg.setImage(BoofTesting.createSubImageOf_S(image)); int r = 2; LineSegment2D_F64 bottom = new LineSegment2D_F64(x1-r,y0,x0+r,y0); LineSegment2D_F64 left = new LineSegment2D_F64(x0,y0+r,x0,y1-r); LineSegment2D_F64 top = new LineSegment2D_F64(x0+r,y1,x1-r,y1); LineSegment2D_F64 right = new LineSegment2D_F64(x1,y1-r,x1,y0+r); differentInitial(alg,bottom); differentInitial(alg,left); differentInitial(alg,top); differentInitial(alg,right); } /** * 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) { fit_noisy_affine(a, imageType); } } } public void fit_noisy_affine(Affine2D_F64 affine, Class imageType) { setup(affine, imageType); double tol = 0.5; SnapToLineEdge alg = new SnapToLineEdge(10,2, imageType); Polygon2D_F64 input = new Polygon2D_F64(4); AffinePointOps_F64.transform(affine,new Point2D_F64(x0,y0), input.get(0)); AffinePointOps_F64.transform(affine,new Point2D_F64(x0,y1),input.get(1)); AffinePointOps_F64.transform(affine,new Point2D_F64(x1,y1),input.get(2)); AffinePointOps_F64.transform(affine,new Point2D_F64(x1,y0),input.get(3)); LineGeneral2D_F64 found[] = new LineGeneral2D_F64[input.size()]; for (int i = 0; i < found.length; i++) { found[i] = new LineGeneral2D_F64(); } for (int i = 0; i < 10; i++) { // add some noise Polygon2D_F64 noisy = input.copy(); addNoise(noisy, 2); alg.setImage(image); for (int j = 0; j < noisy.size(); j++) { LineSegment2D_F64 edge = noisy.getLine(j,null); double slopeX = edge.slopeX(); double slopeY = edge.slopeY(); double r = Math.sqrt(slopeX*slopeX + slopeY*slopeY); slopeX /= r; slopeY /= r; // shrink it slightly to avoid weirdness along the corner edge.a.x += 2*slopeX; edge.a.y += 2*slopeY; edge.b.x -= 2*slopeX; edge.b.y -= 2*slopeY; // optimize it a few times to get a good solution for (int k = 0; k < 5; k++) { assertTrue(alg.refine(edge.a,edge.b, found[j])); edge.a.set(ClosestPoint2D_F64.closestPoint(found[j],edge.a,null)); edge.b.set(ClosestPoint2D_F64.closestPoint(found[j],edge.b,null)); } } // compute the corners and see how it did for (int j = 0; j < input.size(); j++) { int k = (j+1)%input.size(); Point2D_F64 c = new Point2D_F64(); Intersection2D_F64.intersection(found[j],found[k],c); double d = c.distance(input.get(k)); assertTrue(d <= tol); } } } 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; } } /** * Simple case where it samples along a line on a perfect rectangle */ @Test public void computePointsAndWeights() { for (Class imageType : imageTypes) { computePointsAndWeights(imageType); } } public void computePointsAndWeights( Class imageType ) { setup(null,imageType); SnapToLineEdge alg = new SnapToLineEdge(10,2, imageType); float H = y1-y0-10; alg.setImage(image); alg.center.set(10, 12); alg.localScale = 2; alg.computePointsAndWeights(0, H, x0, y0 + 5, 1, 0); // sample points on the outside of the line will be zero since the image has no gradient // there. Thus the weight is zero and the point skipped assertEquals(alg.lineSamples,alg.samplePts.size()); for (int i = 0; i < alg.lineSamples; i++) { // only points along the line were saved, the outer ones discarded assertEquals(white,alg.weights.get(i),1e-8); double x = x0 - 10; double y = y0 + 5 + H*i/(alg.lineSamples -1) - 12; x /= alg.localScale; y /= alg.localScale; Point2D_F64 p = (Point2D_F64)alg.samplePts.get(i); assertEquals(x,p.x,1e-4); assertEquals(y,p.y,1e-4); } } /** * Checks to see if it blows up along the image border */ @Test public void computePointsAndWeights_border() { for (Class imageType : imageTypes) { computePointsAndWeights_border(imageType); } } public void computePointsAndWeights_border( Class imageType ) { setup(null,imageType); SnapToLineEdge alg = new SnapToLineEdge(10,2, imageType); alg.setImage(image); alg.computePointsAndWeights(0, image.height-2, 0, 2, 1, 0); assertEquals(0,alg.samplePts.size()); alg.computePointsAndWeights(0, image.height-2, image.width-1, 2, 1, 0); assertEquals(0,alg.samplePts.size()); alg.computePointsAndWeights(image.width-2,0, 1, 0, 0, 1); assertEquals(0,alg.samplePts.size()); alg.computePointsAndWeights(image.width-2,0, 1, image.height-1, 0, 1); assertEquals(0, alg.samplePts.size()); } @Test public void localToGlobal() { LineSegment2D_F64 segment = new LineSegment2D_F64(10,20,50,-10); SnapToLineEdge alg = new SnapToLineEdge(10,2, GrayU8.class); alg.center.set(20,23); alg.localScale = 10; LineSegment2D_F64 local = segment.copy(); toLocal(local.a,alg); toLocal(local.b,alg); LineGeneral2D_F64 expected = new LineGeneral2D_F64(); LineGeneral2D_F64 found = new LineGeneral2D_F64(); UtilLine2D_F64.convert(segment,expected); UtilLine2D_F64.convert(local,found); alg.localToGlobal(found); expected.normalize(); found.normalize(); assertEquals(expected.A,found.A,1e-8); assertEquals(expected.B,found.B,1e-8); assertEquals(expected.C,found.C,1e-8); } private void toLocal( Point2D_F64 p , SnapToLineEdge alg ) { p.x = (p.x-alg.center.x)/alg.localScale; p.y = (p.y-alg.center.y)/alg.localScale; } private void setup( Affine2D_F64 affine, Class imageType ) { work = GeneralizedImageOps.createSingleBand(imageType, width, height); image = GeneralizedImageOps.createSingleBand(imageType,width,height); int bg = white; int fg = 0; GImageMiscOps.fill(work, bg); GImageMiscOps.fillRectangle(work, fg, x0, y0, x1 - x0, y1 - y0); if( affine != null ) { new FDistort(work, image).border(bg).affine(affine).apply(); } else { image.setTo(work); } // BufferedImage out = ConvertBufferedImage.convertTo(image, null, true); // ShowImages.showWindow(out, "Rendered"); // try { // Thread.sleep(3000); // } catch (InterruptedException e) { // e.printStackTrace(); // } } }