/* * 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.distort; import boofcv.alg.geo.PerspectiveOps; import boofcv.struct.calib.CameraPinholeRadial; import boofcv.struct.distort.Point2Transform2_F32; import boofcv.struct.distort.Point2Transform2_F64; import georegression.struct.affine.Affine2D_F32; import georegression.struct.affine.Affine2D_F64; import georegression.struct.point.Point2D_F32; import georegression.struct.point.Point2D_F64; import georegression.struct.shapes.RectangleLength2D_F32; import georegression.struct.shapes.RectangleLength2D_F64; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Peter Abeles */ public class TestLensDistortionOps { Point2D_F32 pf = new Point2D_F32(); Point2D_F64 pd = new Point2D_F64(); int width = 300; int height = 350; /** * Checks the border of the returned transform. Makes sure that the entire original image is visible. * Also makes sure that the requested inverse transform is actually the inverse. */ @Test public void transform_F32_fullView() { CameraPinholeRadial param = new CameraPinholeRadial(). fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(0.1, 0.05); Point2Transform2_F32 adjToDist = LensDistortionOps.transform_F32(AdjustmentType.FULL_VIEW, param, null, true); Point2Transform2_F32 distToAdj = LensDistortionOps.transform_F32(AdjustmentType.FULL_VIEW, param, null, false); checkBorderOutside(adjToDist,distToAdj); param = new CameraPinholeRadial(). fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(-0.1,-0.05); adjToDist = LensDistortionOps.transform_F32(AdjustmentType.FULL_VIEW, param, null, true); distToAdj = LensDistortionOps.transform_F32(AdjustmentType.FULL_VIEW, param, null, false); checkBorderOutside(adjToDist,distToAdj); } @Test public void transform_F64_fullView() { CameraPinholeRadial param = new CameraPinholeRadial(). fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(0.1, 0.05); Point2Transform2_F64 adjToDist = LensDistortionOps.transform_F64(AdjustmentType.FULL_VIEW, param, null, true); Point2Transform2_F64 distToAdj = LensDistortionOps.transform_F64(AdjustmentType.FULL_VIEW, param, null, false); checkBorderOutside(adjToDist,distToAdj); param = new CameraPinholeRadial(). fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(-0.1,-0.05); adjToDist = LensDistortionOps.transform_F64(AdjustmentType.FULL_VIEW, param, null, true); distToAdj = LensDistortionOps.transform_F64(AdjustmentType.FULL_VIEW, param, null, false); checkBorderOutside(adjToDist,distToAdj); } private void checkBorderOutside(Point2Transform2_F32 tran, Point2Transform2_F32 tranInv) { for( int y = 0; y < height; y++ ) { checkBorderOutside(0, y, tran, tranInv); checkBorderOutside(width - 1, y, tran, tranInv); } for( int x = 0; x < width; x++ ) { checkBorderOutside(x, 0, tran, tranInv); checkBorderOutside(x, height - 1, tran, tranInv); } } private void checkBorderOutside(Point2Transform2_F64 distToUndist, Point2Transform2_F64 undistToDist) { for( int y = 0; y < height; y++ ) { checkBorderOutside(0, y, distToUndist, undistToDist); checkBorderOutside(width - 1, y, distToUndist, undistToDist); } for( int x = 0; x < width; x++ ) { checkBorderOutside(x, 0, distToUndist, undistToDist); checkBorderOutside(x, height - 1, distToUndist, undistToDist); } } private void checkBorderOutside(int x, int y, Point2Transform2_F32 tran, Point2Transform2_F32 tranInv) { tran.compute(x, y, pf); float tol = 0.1f; String s = x+" "+y+" -> "+ pf.x+" "+ pf.y; assertTrue(s, pf.x <= 1 + tol || pf.x >= width - 1 - tol || pf.y <= 1 + tol || pf.y >= height - 1 - tol); // check the inverse tranInv.compute(pf.x, pf.y, pf); assertEquals(pf.x,x, 0.01f); assertEquals(pf.y,y, 0.01f); } private void checkBorderOutside(int x, int y, Point2Transform2_F64 tran, Point2Transform2_F64 tranInv) { tran.compute(x, y, pd); double tol = 0.1; String s = x+" "+y+" -> "+ pd.x+" "+ pd.y; assertTrue(s, pd.x <= 1 + tol || pd.x >= width - 1 - tol || pd.y <= 1 + tol || pd.y >= height - 1 - tol); // check the inverse tranInv.compute(pd.x, pd.y, pd); assertEquals(pd.x,x, 0.001); assertEquals(pd.y,y, 0.001); } /** * Sees if the adjusted intrinsic parameters is correct */ @Test public void transform_F32_fullView_intrinsic() { // distorted pixel in original image float pixelX = 12.5f,pixelY = height-3; CameraPinholeRadial orig = new CameraPinholeRadial(). fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(0.1, 0.05); Point2Transform2_F32 distToNorm = LensDistortionOps.transformPoint(orig).undistort_F32(true, false); Point2D_F32 norm = new Point2D_F32(); distToNorm.compute(pixelX, pixelY, norm); CameraPinholeRadial adjusted = new CameraPinholeRadial(); Point2Transform2_F32 distToAdj = LensDistortionOps. transform_F32(AdjustmentType.FULL_VIEW, orig, adjusted, false); Point2D_F32 adjPixel = new Point2D_F32(); Point2D_F32 normFound = new Point2D_F32(); distToAdj.compute(pixelX,pixelY,adjPixel); PerspectiveOps.convertPixelToNorm(adjusted, adjPixel, normFound); // see if the normalized image coordinates are the same assertEquals(norm.x, normFound.x,1e-3); assertEquals(norm.y, normFound.y, 1e-3); } @Test public void transform_F64_fullView_intrinsic() { // distorted pixel in original image double pixelX = 12.5,pixelY = height-3; CameraPinholeRadial orig = new CameraPinholeRadial(). fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(0.1, 0.05); Point2Transform2_F64 distToNorm = LensDistortionOps.transformPoint(orig).undistort_F64(true, false); Point2D_F64 norm = new Point2D_F64(); distToNorm.compute(pixelX, pixelY, norm); CameraPinholeRadial adjusted = new CameraPinholeRadial(); Point2Transform2_F64 distToAdj = LensDistortionOps.transform_F64(AdjustmentType.FULL_VIEW, orig, adjusted, false); Point2D_F64 adjPixel = new Point2D_F64(); Point2D_F64 normFound = new Point2D_F64(); distToAdj.compute(pixelX,pixelY,adjPixel); PerspectiveOps.convertPixelToNorm(adjusted, adjPixel, normFound); // see if the normalized image coordinates are the same assertEquals(norm.x, normFound.x, 1e-6); assertEquals(norm.y, normFound.y, 1e-6); } /** * Checks the border of the returned transform. Makes sure that no none-visible portion is visible. * Also makes sure that the requested inverse transform is actually the inverse. */ @Test public void transform_F32_shrink() { CameraPinholeRadial param = new CameraPinholeRadial().fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(0.1, 1e-4); Point2Transform2_F32 adjToDist = LensDistortionOps.transform_F32(AdjustmentType.EXPAND, param, null, true); Point2Transform2_F32 distToAdj = LensDistortionOps.transform_F32(AdjustmentType.EXPAND, param, null, false); checkInside(adjToDist, distToAdj); // distort it in the other direction param = new CameraPinholeRadial().fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(-0.1,-1e-4); adjToDist = LensDistortionOps.transform_F32(AdjustmentType.EXPAND, param, null, true); distToAdj = LensDistortionOps.transform_F32(AdjustmentType.EXPAND, param, null, false); checkInside(adjToDist, distToAdj); } @Test public void transform_F64_shrink() { CameraPinholeRadial param = new CameraPinholeRadial().fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(0.1, 1e-4); Point2Transform2_F64 adjToDist = LensDistortionOps.transform_F64(AdjustmentType.EXPAND, param, null, true); Point2Transform2_F64 distToAdj = LensDistortionOps.transform_F64(AdjustmentType.EXPAND, param, null, false); checkInside(adjToDist, distToAdj); // distort it in the other direction param = new CameraPinholeRadial().fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(-0.1,-1e-4); adjToDist = LensDistortionOps.transform_F64(AdjustmentType.EXPAND, param, null, true); distToAdj = LensDistortionOps.transform_F64(AdjustmentType.EXPAND, param, null, false); checkInside(adjToDist, distToAdj); } private void checkInside(Point2Transform2_F32 tran, Point2Transform2_F32 tranInv ) { double closestT = Double.MAX_VALUE; double closestB = Double.MAX_VALUE; for( int y = 0; y < height; y++ ) { checkInside(0,y,tran,tranInv); checkInside(width-1,y,tran,tranInv); closestT = Math.min(closestT,distanceEdge(0,y,tran)); closestB = Math.min(closestB,distanceEdge(width-1,y,tran)); } // should be close to the edge at some point assertTrue( closestT < 1 ); assertTrue( closestB < 1 ); closestT = closestB = Double.MAX_VALUE; for( int x = 0; x < width; x++ ) { checkInside(x,0,tran,tranInv); checkInside(x,height-1,tran,tranInv); closestT = Math.min(closestT,distanceEdge(x,0,tran)); closestB = Math.min(closestB,distanceEdge(x,height-1,tran)); } // should be close to the edge at some point assertTrue(closestT < 1); assertTrue(closestB < 1); } private void checkInside(Point2Transform2_F64 tran, Point2Transform2_F64 tranInv ) { double closestT = Double.MAX_VALUE; double closestB = Double.MAX_VALUE; for( int y = 0; y < height; y++ ) { checkInside(0,y,tran,tranInv); checkInside(width-1,y,tran,tranInv); closestT = Math.min(closestT,distanceEdge(0,y,tran)); closestB = Math.min(closestB,distanceEdge(width-1,y,tran)); } // should be close to the edge at some point assertTrue( closestT < 1 ); assertTrue( closestB < 1 ); closestT = closestB = Double.MAX_VALUE; for( int x = 0; x < width; x++ ) { checkInside(x,0,tran,tranInv); checkInside(x,height-1,tran,tranInv); closestT = Math.min(closestT,distanceEdge(x,0,tran)); closestB = Math.min(closestB,distanceEdge(x,height-1,tran)); } // should be close to the edge at some point assertTrue(closestT < 1); assertTrue(closestB < 1); } private void checkInside(int x , int y , Point2Transform2_F32 tran , Point2Transform2_F32 tranInv ) { tran.compute(x, y, pf); float tol = 0.1f; String s = x+" "+y+" -> "+ pf.x+" "+ pf.y; assertTrue(s, pf.x >= -tol && pf.x < width + tol); assertTrue(s, pf.y >= -tol && pf.y < height + tol); // check the inverse tranInv.compute(pf.x, pf.y, pf); assertEquals(pf.x, x, 0.01f); assertEquals(pf.x, x, 0.01f); } private void checkInside(int x , int y , Point2Transform2_F64 tran , Point2Transform2_F64 tranInv ) { tran.compute(x, y, pd); double tol = 0.1f; String s = x+" "+y+" -> "+ pd.x+" "+ pd.y; assertTrue(s, pd.x >= -tol && pd.x < width + tol); assertTrue(s, pd.y >= -tol && pd.y < height + tol); // check the inverse tranInv.compute(pd.x, pd.y, pd); assertEquals(pd.x, x, 0.01); assertEquals(pd.x, x, 0.01); } private double distanceEdge( int x , int y , Point2Transform2_F32 tran ) { tran.compute(x, y, pf); double min = Double.MAX_VALUE; if( x < min ) min = x; if( y < min ) min = y; if( width-x-1 < min ) min = width-x-1; if( height-y-1 < min ) min = height-y-1; return min; } private double distanceEdge( int x , int y , Point2Transform2_F64 tran ) { tran.compute(x, y, pd); double min = Double.MAX_VALUE; if( x < min ) min = x; if( y < min ) min = y; if( width-x-1 < min ) min = width-x-1; if( height-y-1 < min ) min = height-y-1; return min; } /** * Sees if the adjusted intrinsic parameters is correct but computing normalized image coordinates first * with the original distorted image and then with the adjusted undistorted image. */ @Test public void transform_F32_shrink_intrinsic() { // distorted pixel in original image float pixelX = 12.5f,pixelY = height-3; CameraPinholeRadial orig = new CameraPinholeRadial(). fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(0.1, 0.05); Point2Transform2_F32 distToNorm = LensDistortionOps.transformPoint(orig).undistort_F32(true, false); Point2D_F32 norm = new Point2D_F32(); distToNorm.compute(pixelX, pixelY, norm); CameraPinholeRadial adjusted = new CameraPinholeRadial(); Point2Transform2_F32 distToAdj = LensDistortionOps.transform_F32(AdjustmentType.EXPAND, orig, adjusted, false); Point2D_F32 adjPixel = new Point2D_F32(); Point2D_F32 normFound = new Point2D_F32(); distToAdj.compute(pixelX,pixelY,adjPixel); PerspectiveOps.convertPixelToNorm(adjusted, adjPixel, normFound); // see if the normalized image coordinates are the same assertEquals(norm.x, normFound.x, 1e-3); assertEquals(norm.y, normFound.y, 1e-3); } /** * Sees if the adjusted intrinsic parameters is correct but computing normalized image coordinates first * with the original distorted image and then with the adjusted undistorted image. */ @Test public void transform_F64_shrink_intrinsic() { // distorted pixel in original image double pixelX = 12.5,pixelY = height-3; CameraPinholeRadial orig = new CameraPinholeRadial(). fsetK(300, 320, 0, 150, 130, width, height).fsetRadial(0.1, 0.05); Point2Transform2_F64 distToNorm = LensDistortionOps.transformPoint(orig).undistort_F64(true, false); Point2D_F64 norm = new Point2D_F64(); distToNorm.compute(pixelX, pixelY, norm); CameraPinholeRadial adjusted = new CameraPinholeRadial(); Point2Transform2_F64 distToAdj = LensDistortionOps.transform_F64(AdjustmentType.EXPAND, orig, adjusted, false); Point2D_F64 adjPixel = new Point2D_F64(); Point2D_F64 normFound = new Point2D_F64(); distToAdj.compute(pixelX,pixelY,adjPixel); PerspectiveOps.convertPixelToNorm(adjusted, adjPixel, normFound); // see if the normalized image coordinates are the same assertEquals(norm.x, normFound.x, 1e-6); assertEquals(norm.y, normFound.y, 1e-6); } @Test public void boundBoxInside_F32() { // basic sanity check Affine2D_F32 affine = new Affine2D_F32(1,1,0,1,1,2); PixelTransformAffine_F32 transform = new PixelTransformAffine_F32(affine); RectangleLength2D_F32 found = LensDistortionOps.boundBoxInside(20, 10, transform); assertEquals(10,found.x0,1e-4); assertEquals(2 ,found.y0,1e-4); assertEquals(20-9,found.width,1e-4); assertEquals(10, found.height,1e-4); } @Test public void boundBoxInside_F64() { // basic sanity check Affine2D_F64 affine = new Affine2D_F64(1,1,0,1,1,2); PixelTransformAffine_F64 transform = new PixelTransformAffine_F64(affine); RectangleLength2D_F64 found = LensDistortionOps.boundBoxInside(20, 10, transform); assertEquals(10,found.x0,1e-8); assertEquals(2 ,found.y0,1e-8); assertEquals(20-9,found.width,1e-8); assertEquals(10, found.height,1e-8); } }