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.image.feature.local.keypoints; 031 032import java.io.DataInput; 033import java.io.DataOutput; 034import java.io.IOException; 035import java.io.PrintWriter; 036import java.io.Serializable; 037import java.util.Arrays; 038import java.util.List; 039import java.util.Scanner; 040import java.util.StringTokenizer; 041 042import org.openimaj.feature.FloatFV; 043import org.openimaj.feature.local.LocalFeature; 044import org.openimaj.feature.local.list.MemoryLocalFeatureList; 045import org.openimaj.io.VariableLength; 046import org.openimaj.math.geometry.point.Point2d; 047import org.openimaj.math.geometry.point.ScaleSpacePoint; 048 049import Jama.Matrix; 050 051/** 052 * A local interest point with a location, scale, orientation and associated 053 * feature. The feature is stored as an array of floats. 054 * 055 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 056 */ 057public class FloatKeypoint 058implements 059Serializable, 060ScaleSpacePoint, 061LocalFeature<KeypointLocation, FloatFV>, 062VariableLength, 063Cloneable 064{ 065 static final long serialVersionUID = 1234554345; 066 067 /** 068 * Default length of standard SIFT features. 069 */ 070 private final static int DEFAULT_LENGTH = 128; 071 072 /** 073 * keypoint feature descriptor (i.e. SIFT) 074 */ 075 public float[] vector; 076 077 /** 078 * dominant orientation of keypoint 079 */ 080 public float ori; 081 082 /** 083 * scale of keypoint 084 */ 085 public float scale; 086 087 /** 088 * x-position of keypoint 089 */ 090 public float x; 091 092 /** 093 * y-position of keypoint 094 */ 095 public float y; 096 097 /** 098 * Construct with the default feature vector length for SIFT (128). 099 */ 100 public FloatKeypoint() { 101 this.vector = new float[DEFAULT_LENGTH]; 102 } 103 104 /** 105 * Construct with the given feature vector length. 106 * 107 * @param length 108 * the length of the feature vector 109 */ 110 public FloatKeypoint(int length) { 111 if (length < 0) 112 length = DEFAULT_LENGTH; 113 this.vector = new float[length]; 114 } 115 116 /** 117 * Construct with the given parameters. 118 * 119 * @param x 120 * the x-ordinate of the keypoint 121 * @param y 122 * the y-ordinate of the keypoint 123 * @param ori 124 * the orientation of the keypoint 125 * @param scale 126 * the scale of the keypoint 127 * @param ivec 128 * the feature vector of the keypoint 129 */ 130 public FloatKeypoint(float x, float y, float ori, float scale, float[] ivec) { 131 this.x = x; 132 this.y = y; 133 this.ori = ori; 134 this.scale = scale; 135 this.vector = ivec; 136 } 137 138 /** 139 * Construct by copying from another {@link FloatKeypoint} 140 * 141 * @param k 142 * the {@link FloatKeypoint} to copy from 143 */ 144 public FloatKeypoint(FloatKeypoint k) { 145 this(k.x, k.y, k.ori, k.scale, Arrays.copyOf(k.vector, k.vector.length)); 146 } 147 148 /** 149 * Construct from a {@link Keypoint}. 150 * 151 * @param k 152 */ 153 public FloatKeypoint(Keypoint k) { 154 this.setLocation(k.getLocation()); 155 this.vector = new float[k.ivec.length]; 156 157 for (int i = 0; i < vector.length; i++) { 158 vector[i] = k.ivec[i] + 128; 159 } 160 } 161 162 @Override 163 public Float getOrdinate(int dimension) { 164 if (dimension == 0) 165 return x; 166 if (dimension == 1) 167 return y; 168 if (dimension == 2) 169 return scale; 170 return null; 171 } 172 173 @Override 174 public int getDimensions() { 175 return 3; 176 } 177 178 @Override 179 public float getX() { 180 return x; 181 } 182 183 @Override 184 public float getY() { 185 return y; 186 } 187 188 @Override 189 public void setX(float x) { 190 this.x = x; 191 } 192 193 @Override 194 public void setY(float y) { 195 this.y = y; 196 } 197 198 @Override 199 public float getScale() { 200 return scale; 201 } 202 203 @Override 204 public void setScale(float scale) { 205 this.scale = scale; 206 } 207 208 @Override 209 public String toString() { 210 return ("Keypoint(" + this.x + ", " + this.y + ", " + this.scale + ", " + this.ori + ")"); 211 } 212 213 /** 214 * Test whether the location of this {@link FloatKeypoint} and another 215 * {@link FloatKeypoint} is the same. 216 * 217 * @param obj 218 * the other keypoint 219 * @return true if the locations match; false otherwise. 220 */ 221 public boolean locationEquals(Object obj) { 222 if (obj instanceof FloatKeypoint) { 223 final FloatKeypoint kobj = (FloatKeypoint) obj; 224 225 if (kobj.x == x && kobj.y == y && kobj.scale == scale) 226 return true; 227 } 228 229 return super.equals(obj); 230 } 231 232 @Override 233 public boolean equals(Object obj) { 234 if (obj instanceof FloatKeypoint) { 235 final FloatKeypoint kobj = (FloatKeypoint) obj; 236 237 if (kobj.x == x && kobj.y == y && kobj.scale == scale && Arrays.equals(vector, kobj.vector)) 238 return true; 239 } 240 241 return super.equals(obj); 242 } 243 244 @Override 245 public int hashCode() { 246 int hash = 1; 247 hash = hash * 31 + Float.floatToIntBits(y); 248 hash = hash * 31 + Float.floatToIntBits(x); 249 hash = hash * 31 + Float.floatToIntBits(scale); 250 return hash; 251 } 252 253 @Override 254 public FloatKeypoint clone() { 255 final FloatKeypoint clone = new FloatKeypoint(); 256 257 clone.x = x; 258 clone.ori = ori; 259 clone.y = y; 260 clone.scale = scale; 261 262 clone.vector = new float[vector.length]; 263 System.arraycopy(vector, 0, clone.vector, 0, vector.length); 264 265 return clone; 266 } 267 268 @Override 269 public void copyFrom(Point2d p) { 270 setX(p.getX()); 271 setY(p.getY()); 272 } 273 274 @Override 275 public void writeBinary(DataOutput out) throws IOException { 276 getLocation().writeBinary(out); 277 out.writeInt(vector.length); 278 for (int i = 0; i < vector.length; i++) 279 out.writeFloat(this.vector[i]); 280 } 281 282 @Override 283 public void writeASCII(PrintWriter out) throws IOException { 284 /* Output data for the keypoint. */ 285 getLocation().writeASCII(out); 286 for (int i = 0; i < vector.length; i++) { 287 if (i > 0 && i % 20 == 0) 288 out.println(); 289 out.print(" " + vector[i]); 290 } 291 out.println(); 292 } 293 294 @Override 295 public void readBinary(DataInput in) throws IOException { 296 final KeypointLocation l = getLocation(); 297 l.readBinary(in); 298 setLocation(l); 299 300 vector = new float[in.readInt()]; 301 for (int i = 0; i < vector.length; i++) 302 vector[i] = in.readFloat(); 303 } 304 305 @Override 306 public void readASCII(Scanner in) throws IOException { 307 final KeypointLocation l = getLocation(); 308 l.readASCII(in); 309 setLocation(l); 310 311 int i = 0; 312 while (i < vector.length) { 313 final String line = in.nextLine(); 314 final StringTokenizer st = new StringTokenizer(line); 315 316 while (st.hasMoreTokens()) { 317 vector[i] = Float.parseFloat(st.nextToken()); 318 i++; 319 } 320 } 321 } 322 323 @Override 324 public byte[] binaryHeader() { 325 return "".getBytes(); 326 } 327 328 @Override 329 public String asciiHeader() { 330 return ""; 331 } 332 333 @Override 334 public FloatFV getFeatureVector() { 335 return new FloatFV(vector); 336 } 337 338 @Override 339 public KeypointLocation getLocation() { 340 return new KeypointLocation(x, y, ori, scale); 341 } 342 343 /** 344 * Set the location of this {@link FloatKeypoint} 345 * 346 * @param location 347 * the location 348 */ 349 public void setLocation(KeypointLocation location) { 350 x = location.x; 351 y = location.y; 352 scale = location.scale; 353 ori = location.orientation; 354 } 355 356 @Override 357 public void translate(float x, float y) { 358 this.x += x; 359 this.y += y; 360 } 361 362 @Override 363 public FloatKeypoint transform(Matrix transform) { 364 float xt = (float) transform.get(0, 0) * getX() + (float) transform.get(0, 1) * getY() 365 + (float) transform.get(0, 2); 366 float yt = (float) transform.get(1, 0) * getX() + (float) transform.get(1, 1) * getY() 367 + (float) transform.get(1, 2); 368 final float zt = (float) transform.get(2, 0) * getX() + (float) transform.get(2, 1) * getY() 369 + (float) transform.get(2, 2); 370 371 xt /= zt; 372 yt /= zt; 373 374 return new FloatKeypoint(xt, yt, this.ori, this.scale, this.vector.clone()); 375 } 376 377 @Override 378 public Point2d minus(Point2d a) { 379 final FloatKeypoint kp = this.clone(); 380 kp.x = this.x - (int) a.getX(); 381 kp.y = this.y - (int) a.getY(); 382 return null; 383 } 384 385 @Override 386 public void translate(Point2d v) { 387 this.translate(v.getX(), v.getY()); 388 } 389 390 @Override 391 public Point2d copy() { 392 return clone(); 393 } 394 395 /** 396 * Convert a list of {@link Keypoint}s to {@link FloatKeypoint}s. 397 * 398 * @param keys 399 * the {@link Keypoint}s to convert. 400 * @return the converted {@link FloatKeypoint}s. 401 */ 402 public static MemoryLocalFeatureList<FloatKeypoint> convert(List<? extends Keypoint> keys) { 403 final MemoryLocalFeatureList<FloatKeypoint> out = new MemoryLocalFeatureList<FloatKeypoint>(); 404 405 for (final Keypoint k : keys) 406 out.add(new FloatKeypoint(k)); 407 408 return out; 409 } 410 411 @Override 412 public void setOrdinate(int dimension, Number value) { 413 if (dimension == 0) 414 x = value.floatValue(); 415 if (dimension == 1) 416 y = value.floatValue(); 417 } 418}