/*
* 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.corner;
import boofcv.abst.distort.FDistort;
import boofcv.alg.misc.GImageMiscOps;
import boofcv.core.image.GeneralizedImageOps;
import boofcv.misc.CircularIndex;
import boofcv.struct.image.GrayF32;
import boofcv.struct.image.GrayU8;
import boofcv.struct.image.ImageGray;
import georegression.geometry.UtilLine2D_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.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 TestRefineCornerLinesToImage {
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;
Class imageTypes[] = new Class[]{GrayU8.class,GrayF32.class};
/**
* The shape it is fit to is a rectangle that's aligned to the image axis. No interpolation and a very
* high accuracy shape should be found.
*/
@Test
public void perfectRectangle() {
for( Class imageType : imageTypes ) {
for (int i = 0; i < 2; i++) {
perfectRectangle(i == 0, imageType);
}
}
}
public void perfectRectangle(boolean black, Class imageType) {
setup(null,black,imageType);
RefineCornerLinesToImage alg = new RefineCornerLinesToImage(imageType);
alg.setImage(image);
optimizedCorner(alg,x0,y0,x0,y1,x1,y0);
optimizedCorner(alg,x0,y1,x1,y1,x0,y0);
optimizedCorner(alg,x1,y1,x1,y0,x0,y1);
optimizedCorner(alg,x1,y0,x0,y0,x1,y1);
}
private void optimizedCorner( RefineCornerLinesToImage alg,
int x0 , int y0 , int x1 , int y1 , int x2 , int y2 )
{
double tol = 1e-5;
int r = 2;
// give it initial conditions which are offset from the truth
for (int y = -r; y <= r; y++) {
for (int x = -r; x <= r; x++) {
Point2D_F64 c = new Point2D_F64(x0+x,y0+y);
Point2D_F64 endL = new Point2D_F64(x1,y1);
Point2D_F64 endR = new Point2D_F64(x2,y2);
assertTrue(alg.refine(c, endL, endR));
Point2D_F64 refined = alg.getRefinedCorner();
assertEquals(x0,refined.x,tol);
assertEquals(y0,refined.y,tol);
}
}
}
/**
* Apply different types of distortion to the rectangle and give it a noisy initial estimate
*/
@Test
public void noisyDistorted() {
// distorted and undistorted views
Affine2D_F64 affines[] = new Affine2D_F64[3];
affines[0] = new Affine2D_F64();
affines[1] = new Affine2D_F64();
affines[2] = 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]);
ConvertTransform_F64.convert(new Se2_F64(0, 0, 0.4), affines[1]);
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);
double tol = 0.9;
RefineCornerLinesToImage alg = new RefineCornerLinesToImage(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));
Polygon2D_F64 expected = input.copy();
for (int i = 0; i < 10; i++) {
// add some noise
input.set(expected);
addNoise(input, 2);
alg.setImage(image);
for (int j = 0; j < 4; j++) {
int left = (j+1)%4;
int right = CircularIndex.minusPOffset(j, 1, 4);
assertTrue(alg.refine(input.get(j), input.get(left), input.get(right)));
Point2D_F64 refined = alg.getRefinedCorner();
Point2D_F64 e = expected.get(j);
double difference = refined.distance(e);
assertTrue(i+" "+j+" "+difference,difference <= 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;
}
}
/**
* Basic optimization test. Give it a perfect initial guess and see generates the
* expected solution\
*/
@Test
public void optimize() {
for( Class imageType : imageTypes ) {
for (int i = 0; i < 2; i++) {
optimize(i==0,imageType);
}
}
}
public void optimize(boolean black , Class imageType) {
setup(null, black, imageType);
RefineCornerLinesToImage alg = new RefineCornerLinesToImage(imageType);
alg.setImage(image);
LineGeneral2D_F64 found = new LineGeneral2D_F64();
LineSegment2D_F64 line0 = new LineSegment2D_F64(x0,y0,x1,y0);
LineSegment2D_F64 line1 = new LineSegment2D_F64(x0,y0,x0,y1);
assertTrue(alg.optimize(line0.a,line0.b, found));
checkSolution(line0,found);
assertTrue(alg.optimize(line1.a,line1.b, found));
checkSolution(line1,found);
}
private void checkSolution( 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);
}
private void setup( Affine2D_F64 affine, boolean black , Class imageType ) {
work = GeneralizedImageOps.createSingleBand(imageType, width, height);
image = GeneralizedImageOps.createSingleBand(imageType,width,height);
int bg = black ? white : 0;
int fg = black ? 0 : white;
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();
// }
}
}