001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.feature.local.matcher; 031 032import java.util.List; 033import java.util.Random; 034 035import org.openimaj.image.FImage; 036import org.openimaj.image.feature.local.keypoints.Keypoint; 037import org.openimaj.util.pair.Pair; 038 039import Jama.Matrix; 040 041/** 042 * Supports the generation of mild affine transforms. This is especially useful for the checking of localfeatures 043 * which should show some resiliance to such transforms 044 * 045 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 046 * @author Sina Samangooei (ss@ecs.soton.ac.uk) 047 * 048 */ 049public class KeypointCorrespondenceTestHelper{ 050 051 /** 052 * Generate an affine transform between some reasonable limits: 053 * - Rotate it between 0 and 360 054 * - transform it between 0 and 10 pixels 055 * - slant it (sheer in x and y) between 0 and 10 degrees 056 * - scale it between 0 and 2.0 times 057 * @param image the image on which to generate the transform 058 * @return a 3x3 transformation matrix 059 */ 060 public static Matrix generateMildTransform(FImage image){return generateMildTransform(image, new Random());} 061 /** 062 * Generate an affine transform between some reasonable limits: 063 * - Rotate it between 0 and 360 064 * - transform it between 0 and 10 pixels 065 * - slant it (sheer in x and y) between 0 and 10 degrees 066 * - scale it between 0 and 2.0 times 067 * @param image the image on which to generate the transform 068 * @param seed the random seed against which to generate the random mild transforms 069 * @return a 3x3 transformation matrix 070 */ 071 public static Matrix generateMildTransform(FImage image,Random seed){ 072 double tenDegrees = Math.PI/2.0 * (1.0/(18.0)); 073// double tenDegrees = 0; 074 return generateRandomTransform( 075 image.getWidth()/2.0,image.getHeight()/2.0, 076 0.0,10.0, 077 0,Math.PI*2, 078 1.0,1.0, 079 tenDegrees*3,tenDegrees*3,seed); 080 } 081 082 private static Matrix generateRandomTransform( 083 double centerX, double centerY, 084 double minTrans, double maxTrans, 085 double minRot, double maxRot, 086 double minScale,double maxScale, 087 double maxSlantX, double maxSlantY,Random seed) { 088 System.out.println("Generating random transform matrix"); 089 double transX = (seed.nextDouble() * (maxTrans - minTrans)) + minTrans; 090 double transY = (seed.nextDouble() * (maxTrans - minTrans)) + minTrans; 091 double rot = (seed.nextDouble() * (maxRot - minRot)) + minRot; 092 double scale = (seed.nextDouble() * (maxScale - minScale)) + minScale; 093 double slantX = (seed.nextDouble() * maxSlantX ); 094 double slantY = (seed.nextDouble() * maxSlantY ); 095 096 slantX = 1.0 - (slantX/(Math.PI/2)); 097 slantY = 1.0 - (slantY/(Math.PI/2)); 098 099 // The center coords take us to the origin 100 // To place the rotated shape back to the center of the image we must find out it's "top left corner" after rotation 101 // Find these points by rotating the unrotated extrema and then calculate the decenterX and Y 102 double[][][] extrema = new double[][][]{ 103 {{0},{0},{1}}, 104 {{0},{centerY*2*scale*slantY},{1}}, 105 {{centerX*2*scale*slantX},{0},{1}}, 106 {{centerX*2*scale*slantX},{centerY*2*scale*slantY},{1}}, 107 }; 108 109 Matrix rotationMatrix = Matrix.constructWithCopy(new double[][]{ 110 {Math.cos(rot),-Math.sin(rot),0}, 111 {Math.sin(rot),Math.cos(rot),0}, 112 {0,0,1}, 113 }); 114 115 116 boolean unset = true; 117 double minX=0,minY=0,maxX=0,maxY=0; 118 for(double[][] ext : extrema){ 119 Matrix tmp = rotationMatrix.times(Matrix.constructWithCopy(ext)); 120 if(unset) 121 { 122 minX = maxX = tmp.get(0, 0); 123 maxY = minY = tmp.get(1, 0); 124 unset = false; 125 } 126 else{ 127 if(tmp.get(0, 0) > maxX) maxX = tmp.get(0, 0); 128 if(tmp.get(1, 0) > maxY) maxY = tmp.get(1, 0); 129 if(tmp.get(0, 0) < minX) minX = tmp.get(0, 0); 130 if(tmp.get(1, 0) < minY) minY = tmp.get(1, 0); 131 } 132 } 133 134 double deCenterX = (maxX - minX)/2.0; 135 double deCenterY = (maxY - minY)/2.0; 136 137 Matrix centerTranslationMatrix = Matrix.constructWithCopy(new double[][]{ 138 {1,0,-centerX*scale*slantX}, 139 {0,1,-centerY*scale*slantY}, 140 {0,0,1}, 141 }); 142 143 Matrix translationMatrix = Matrix.constructWithCopy(new double[][]{ 144 {1,0,transX*scale*slantX}, 145 {0,1,transY*scale*slantY}, 146 {0,0,1}, 147 }); 148 Matrix decenterTranslationMatrix = Matrix.constructWithCopy(new double[][]{ 149 {1,0,deCenterX}, 150 {0,1,deCenterY}, 151 {0,0,1}, 152 }); 153 Matrix scaleMatrix = Matrix.constructWithCopy(new double[][]{ 154 {scale,0,0}, 155 {0,scale,0}, 156 {0,0,1}, 157 }); 158 Matrix slantMatrix = Matrix.constructWithCopy(new double[][]{ 159 {slantX,0,0}, 160 {0,slantY,0}, 161 {0,0,1}, 162 }); 163 164 return Matrix.identity(3, 3) 165 .times(translationMatrix) 166 .times(decenterTranslationMatrix) 167 .times(rotationMatrix) 168 .times(centerTranslationMatrix) 169 .times(slantMatrix) 170 .times(scaleMatrix); 171// return centerTranslationMatrix.times(rotationMatrix).times(scaleMatrix).times(translationMatrix).times(decenterTranslationMatrix); 172 } 173 174 175 /** 176 * Provide simplistic check of correspondence between the keypoints extracted and a given transform. The euclidian 177 * distance between the expected position of a keypoint and it's actual position. Note this should not be used 178 * for consistency matching, please see the {@link LocalFeatureMatcher} for better implementations for that purpose. 179 * @param matches list of keypoint matches 180 * @param transform the supposed transform applied 181 * @return lower numbers are lower euclidian distances 182 */ 183 public static float correspondance(List<Pair<Keypoint>> matches, Matrix transform){ 184 return correspondance(matches,transform,1.0f); 185 } 186 /** 187 * Provide simplistic check of correspondence between the keypoints extracted and a given transform. The euclidian 188 * distance between the expected position of a keypoint and it's actual position. Note this should not be used 189 * for consistency matching, please see the {@link LocalFeatureMatcher} for better implementations for that purpose. 190 * @param matches list of keypoint matches 191 * @param transform the supposed transform applied 192 * @param scaleThreshold The allowable scale shift between two keypoints 193 * @return lower numbers are lower euclidian distances 194 */ 195 public static float correspondance(List<Pair<Keypoint>> matches, Matrix transform, float scaleThreshold){ 196 float total = 0.0f; 197 for(Pair<Keypoint> pair : matches){ 198 199 Keypoint originalKeypoint = pair.firstObject(); 200 float currentScaleThreshold = scaleThreshold * originalKeypoint.scale; 201 Keypoint transformedKeypoint = pair.secondObject(); 202 Matrix originalKPMatrix = keypointToMatrix(originalKeypoint); 203 Matrix transformedKPMatrix = keypointToMatrix(transformedKeypoint ); 204 Matrix originalTransformed = transform.times(originalKPMatrix); 205 206 207 if(distance(originalTransformed,transformedKPMatrix) < currentScaleThreshold){ total +=1f; } 208 } 209 return total; 210 } 211 private static Matrix keypointToMatrix(Keypoint kp) { 212 return Matrix.constructWithCopy( 213 new double[][]{ 214 {kp.x}, 215 {kp.y}, 216 {1.0} 217 } 218 ); 219 } 220 private static float distance(Matrix a,Matrix b) { 221 return (float) a.minus(b).norm2(); 222 } 223}