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}