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.awt.event.KeyEvent; 033import java.awt.event.KeyListener; 034import java.awt.event.MouseEvent; 035import java.awt.event.MouseMotionListener; 036import java.util.ArrayList; 037import java.util.List; 038 039import javax.swing.JFrame; 040 041import org.openimaj.image.DisplayUtilities; 042import org.openimaj.image.Image; 043import org.openimaj.image.feature.local.keypoints.Keypoint; 044import org.openimaj.image.renderer.ImageRenderer; 045import org.openimaj.math.geometry.line.Line2d; 046import org.openimaj.math.geometry.point.Point2d; 047import org.openimaj.math.geometry.point.Point2dImpl; 048import org.openimaj.util.pair.IndependentPair; 049import org.openimaj.util.pair.Pair; 050 051 052/** 053 * Drawing utility useful for drawing two images and the matches between their feature points 054 * 055 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 056 * @author Sina Samangooei (ss@ecs.soton.ac.uk) 057 * 058 */ 059public class MatchingUtilities { 060 /** 061 * Draw matches between two images in the given colour. 062 * Places the images side-by-side and draws a line 063 * for each match. 064 * 065 * @param <T> Pixel type 066 * @param <I> Image type 067 * @param im1 first image 068 * @param im2 second image 069 * @param matches matched features between images 070 * @param col the colour to draw in 071 * @return image drawn on 072 */ 073 public static <T, I extends Image<T,I>> I drawMatches(I im1, I im2, List<? extends Pair<? extends Point2d>> matches, T col) { 074 int newwidth = im1.getWidth() + im2.getWidth(); 075 int newheight = Math.max(im1.getHeight(), im2.getHeight()); 076 077 I out = im1.newInstance(newwidth, newheight); 078 ImageRenderer<T, I> renderer = out.createRenderer(); 079 renderer.drawImage(im1, 0, 0); 080 renderer.drawImage(im2, im1.getWidth(), 0); 081 082 if (matches!=null) { 083 for (Pair<? extends Point2d> p : matches) { 084 renderer.drawLine( (int)p.firstObject().getX() + im1.getWidth(), 085 (int)p.firstObject().getY(), 086 (int)p.secondObject().getX(), 087 (int)p.secondObject().getY(), 088 1,col); 089 } 090 } 091 092 return out; 093 } 094 095 /** 096 * Draw two sets of matches between two images in the given colours. 097 * Places the images side-by-side and draws a line 098 * for each match. 099 * 100 * @param <T> Pixel type 101 * @param <I> Image type 102 * @param im1 first image 103 * @param im2 second image 104 * @param matches first set of matched features between images 105 * @param col the colour to draw first set of matches in 106 * @param matches2 second set of matched features between images 107 * @param col2 the colour to draw second set of matches in 108 * @return image drawn on 109 */ 110 public static <T, I extends Image<T,I>> I drawMatches(I im1, I im2, List<? extends Pair<? extends Point2d>> matches, T col, List<? extends Pair<? extends Point2d>> matches2, T col2) { 111 int newwidth = im1.getWidth() + im2.getWidth(); 112 int newheight = Math.max(im1.getHeight(), im2.getHeight()); 113 114 I out = im1.newInstance(newwidth, newheight); 115 ImageRenderer<T, I> renderer = out.createRenderer(); 116 renderer.drawImage(im1, 0, 0); 117 renderer.drawImage(im2, im1.getWidth(), 0); 118 119 if (matches!=null) { 120 for (Pair<? extends Point2d> p : matches) { 121 renderer.drawLine( (int)p.firstObject().getX() + im1.getWidth(), 122 (int)p.firstObject().getY(), 123 (int)p.secondObject().getX(), 124 (int)p.secondObject().getY(), 125 col); 126 } 127 } 128 129 if (matches2!=null) { 130 for (Pair<? extends Point2d> p : matches2) { 131 renderer.drawLine( (int)p.firstObject().getX() + im1.getWidth(), 132 (int)p.firstObject().getY(), 133 (int)p.secondObject().getX(), 134 (int)p.secondObject().getY(), 135 col2); 136 } 137 } 138 139 return out; 140 } 141 142 /** 143 * Draw matches between two images in the given colour. The 144 * lines representing the matches are drawn on a copy of the 145 * input image. The positions of the matches should represent 146 * valid points in the input image. 147 * 148 * @param <T> Pixel type 149 * @param <I> Image type 150 * @param image first image 151 * @param matches matched features between images 152 * @param col the colour to draw in 153 * @return image drawn on 154 */ 155 public static <T, I extends Image<T,I>> I drawMatches(I image, List<IndependentPair<Point2d, Point2d>> matches, T col) { 156 I out = image.clone(); 157 ImageRenderer<T, I> renderer = out.createRenderer(); 158 159 if (matches!=null) { 160 for (IndependentPair<? extends Point2d, ? extends Point2d> p : matches) { 161 renderer.drawLine( (int)p.firstObject().getX(), 162 (int)p.firstObject().getY(), 163 (int)p.secondObject().getX(), 164 (int)p.secondObject().getY(), 165 col); 166 } 167 } 168 169 return out; 170 } 171 172 static class MouseOverFeatureListener<T, I extends Image<T,I>> implements MouseMotionListener, KeyListener{ 173 private JFrame frame; 174 private List<Pair<Keypoint>> matches; 175 private T colour; 176 private I im1; 177 private I im2; 178 private boolean allMode; 179 180 public MouseOverFeatureListener(I im1,I im2, JFrame frame,List<Pair<Keypoint>> matches, T colour) { 181 this.im1 = im1; 182 this.im2 = im2; 183 this.frame = frame; 184 this.matches = matches; 185 this.colour = colour; 186 this.allMode = false; 187 } 188 189 @Override 190 public void mouseDragged(MouseEvent arg0) { 191 // Do nothing 192 } 193 194 @Override 195 public void mouseMoved(MouseEvent arg0) { 196 List<Pair<Keypoint>> toDisplay = null; 197 if(allMode) { 198 toDisplay = this.matches; 199 } else { 200 Point2d mousePoint = new Point2dImpl(arg0.getX()-im1.getWidth(),arg0.getY()); 201 toDisplay = new ArrayList<Pair<Keypoint>>(); 202 for(Pair<Keypoint> kpair : matches){ 203 Keypoint toCompare = kpair.firstObject(); 204 if(Line2d.distance(mousePoint, toCompare) < 10){ 205 toDisplay.add(kpair); 206 } 207 } 208 } 209 210 I image = MatchingUtilities.drawMatches(im1, im2, toDisplay, this.colour); 211 DisplayUtilities.display(image,frame); 212 } 213 214 @Override 215 public void keyPressed(KeyEvent key) { 216 keyTyped(key); 217 } 218 219 @Override 220 public void keyReleased(KeyEvent arg0) { 221 // Do nothing 222 } 223 224 @Override 225 public void keyTyped(KeyEvent key) { 226 if(key.getKeyCode() == KeyEvent.VK_SPACE) allMode = !allMode; 227 } 228 } 229 230 /** 231 * Create an interactive display of matches between two images. 232 * 233 * @param <T> Pixel type 234 * @param <I> Image type 235 * @param im1 first image 236 * @param im2 second image 237 * @param matches matched features between images 238 * @param col the colour to draw in 239 */ 240 public static <T, I extends Image<T,I>> void displayMouseOverMatches(I im1, I im2,List<Pair<Keypoint>> matches, T col) { 241 int newwidth = im1.getWidth() + im2.getWidth(); 242 int newheight = Math.max(im1.getHeight(), im2.getHeight()); 243 244 I out = im1.newInstance(newwidth, newheight); 245 ImageRenderer<T, I> renderer = out.createRenderer(); 246 renderer.drawImage(im1, 0, 0); 247 renderer.drawImage(im2, im1.getWidth(), 0); 248 249 JFrame frame = DisplayUtilities.display(out); 250 MouseOverFeatureListener<T, I> mofl = new MouseOverFeatureListener<T,I>(im1, im2, frame, matches, col); 251 frame.addKeyListener(mofl); 252 frame.getContentPane().addMouseMotionListener(mofl); 253 } 254}