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.processing.convolution; 031 032import odk.lang.FastMath; 033 034import org.openimaj.image.FImage; 035import org.openimaj.image.analyser.ImageAnalyser; 036 037/** 038 * Image processor for calculating gradients and orientations using 039 * finite-differences. Both signed (+/- PI) orientations and unsigned (+/- PI/2) 040 * are computable. 041 * 042 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 043 * 044 */ 045public class FImageGradients implements ImageAnalyser<FImage> { 046 /** 047 * Modes of operation for signed and unsigned orientations 048 * 049 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 050 * 051 */ 052 public enum Mode { 053 /** 054 * Unsigned orientations in +/- PI/2 computed using <code>atan</code>. 055 */ 056 Unsigned(-PI_OVER_TWO_FLOAT, PI_OVER_TWO_FLOAT) { 057 @Override 058 void gradientMagnitudesAndOrientations(FImage image, FImage magnitudes, FImage orientations) { 059 FImageGradients.gradientMagnitudesAndUnsignedOrientations(image, magnitudes, orientations); 060 } 061 }, 062 /** 063 * Signed orientations +/- PI computed using <code>atan2</code>. 064 */ 065 Signed(-PI_FLOAT, PI_FLOAT) { 066 @Override 067 void gradientMagnitudesAndOrientations(FImage image, FImage magnitudes, FImage orientations) { 068 FImageGradients.gradientMagnitudesAndOrientations(image, magnitudes, orientations); 069 } 070 }; 071 072 private float min; 073 private float max; 074 075 private Mode(float min, float max) { 076 this.min = min; 077 this.max = max; 078 } 079 080 abstract void gradientMagnitudesAndOrientations(FImage image, FImage magnitudes, FImage orientations); 081 082 /** 083 * Get the minimum angular value (in radians) computed by this mode. 084 * 085 * @return the minimum angular value. 086 */ 087 public float minAngle() { 088 return min; 089 } 090 091 /** 092 * Get the maximum angular value (in radians) computed by this mode. 093 * 094 * @return the maximum angular value. 095 */ 096 public float maxAngle() { 097 return max; 098 } 099 } 100 101 private final static float PI_FLOAT = (float) Math.PI; 102 private final static float PI_OVER_TWO_FLOAT = (float) Math.PI / 2f; 103 private final static float TWO_PI_FLOAT = (float) (Math.PI * 2); 104 105 /** 106 * The gradient magnitudes 107 */ 108 public FImage magnitudes; 109 110 /** 111 * The gradient orientations 112 */ 113 public FImage orientations; 114 115 /** 116 * The orientation mode 117 */ 118 public Mode mode; 119 120 /** 121 * Default constructor using {@link Mode#Signed} mode. 122 */ 123 public FImageGradients() { 124 this.mode = Mode.Signed; 125 } 126 127 /** 128 * Construct using the given {@link Mode}. 129 * 130 * @param mode 131 * the mode 132 */ 133 public FImageGradients(Mode mode) { 134 this.mode = mode; 135 } 136 137 /* 138 * (non-Javadoc) 139 * 140 * @see 141 * org.openimaj.image.analyser.ImageAnalyser#analyseImage(org.openimaj.image 142 * .Image) 143 */ 144 @Override 145 public void analyseImage(FImage image) { 146 if (magnitudes == null || 147 magnitudes.height != image.height || 148 magnitudes.width != image.width) 149 { 150 magnitudes = new FImage(image.width, image.height); 151 orientations = new FImage(image.width, image.height); 152 } 153 154 mode.gradientMagnitudesAndOrientations(image, magnitudes, orientations); 155 } 156 157 /** 158 * Static helper to create a new {@link FImageGradients} and call 159 * {@link FImageGradients#analyseImage(FImage)} with the image. 160 * 161 * @param image 162 * the image 163 * @return a FImageGradients for the image 164 */ 165 public static FImageGradients getGradientMagnitudesAndOrientations(FImage image) { 166 final FImageGradients go = new FImageGradients(); 167 go.analyseImage(image); 168 169 return go; 170 } 171 172 /** 173 * Static helper to create a new {@link FImageGradients} and call 174 * {@link FImageGradients#analyseImage(FImage)} with the image. 175 * 176 * @param image 177 * the image 178 * @param mode 179 * the orientation mode 180 * @return a FImageGradients for the image 181 */ 182 public static FImageGradients getGradientMagnitudesAndOrientations(FImage image, Mode mode) { 183 final FImageGradients go = new FImageGradients(mode); 184 go.analyseImage(image); 185 186 return go; 187 } 188 189 /** 190 * Estimate gradients magnitudes and orientations by calculating pixel 191 * differences. Edges get special treatment. The resultant gradients and 192 * orientations are returned though the gradients and orientations 193 * parameters respectively. The images represented by the gradients and 194 * orientations parameters are assumed to be initialized to the same size as 195 * the input image. Gradients are computed using the <code>atan2</code> 196 * function and will be in the range +/-PI. 197 * 198 * @param image 199 * the input image 200 * @param magnitudes 201 * the output gradient magnitudes 202 * @param orientations 203 * the output gradient orientations 204 */ 205 public static void gradientMagnitudesAndOrientations(FImage image, FImage magnitudes, FImage orientations) 206 { 207 // Note: unrolling this loop to remove the if's doesn't 208 // actually seem to make it faster! 209 for (int r = 0; r < image.height; r++) { 210 for (int c = 0; c < image.width; c++) { 211 float xgrad, ygrad; 212 213 if (c == 0) 214 xgrad = 2.0f * (image.pixels[r][c + 1] - image.pixels[r][c]); 215 else if (c == image.width - 1) 216 xgrad = 2.0f * (image.pixels[r][c] - image.pixels[r][c - 1]); 217 else 218 xgrad = image.pixels[r][c + 1] - image.pixels[r][c - 1]; 219 if (r == 0) 220 ygrad = 2.0f * (image.pixels[r][c] - image.pixels[r + 1][c]); 221 else if (r == image.height - 1) 222 ygrad = 2.0f * (image.pixels[r - 1][c] - image.pixels[r][c]); 223 else 224 ygrad = image.pixels[r - 1][c] - image.pixels[r + 1][c]; 225 226 // magnitudes.pixels[r][c] = (float) Math.sqrt( xgrad * xgrad + 227 // ygrad * ygrad ); 228 // orientations.pixels[r][c] = (float) Math.atan2( ygrad, xgrad 229 // ); 230 231 // JH - my benchmarking shows that (at least on OSX) Math.atan2 232 // is really 233 // slow... FastMath provides an alternative that is much faster 234 magnitudes.pixels[r][c] = (float) Math.sqrt(xgrad * xgrad + ygrad * ygrad); 235 orientations.pixels[r][c] = (float) FastMath.atan2(ygrad, xgrad); 236 } 237 } 238 } 239 240 /** 241 * Estimate gradients magnitudes and orientations by calculating pixel 242 * differences. Edges get special treatment. The resultant gradients and 243 * orientations are returned though the gradients and orientations 244 * parameters respectively. The images represented by the gradients and 245 * orientations parameters are assumed to be initialized to the same size as 246 * the input image. Gradients are computed using the <code>atan</code> 247 * function and will be in the range +/- PI/2. 248 * 249 * @param image 250 * the input image 251 * @param magnitudes 252 * the output gradient magnitudes 253 * @param orientations 254 * the output gradient orientations 255 */ 256 public static void gradientMagnitudesAndUnsignedOrientations(FImage image, FImage magnitudes, FImage orientations) 257 { 258 // Note: unrolling this loop to remove the if's doesn't 259 // actually seem to make it faster! 260 for (int r = 0; r < image.height; r++) { 261 for (int c = 0; c < image.width; c++) { 262 float xgrad, ygrad; 263 264 if (c == 0) 265 xgrad = 2.0f * (image.pixels[r][c + 1] - image.pixels[r][c]); 266 else if (c == image.width - 1) 267 xgrad = 2.0f * (image.pixels[r][c] - image.pixels[r][c - 1]); 268 else 269 xgrad = image.pixels[r][c + 1] - image.pixels[r][c - 1]; 270 if (r == 0) 271 ygrad = 2.0f * (image.pixels[r][c] - image.pixels[r + 1][c]); 272 else if (r == image.height - 1) 273 ygrad = 2.0f * (image.pixels[r - 1][c] - image.pixels[r][c]); 274 else 275 ygrad = image.pixels[r - 1][c] - image.pixels[r + 1][c]; 276 277 magnitudes.pixels[r][c] = (float) Math.sqrt(xgrad * xgrad + ygrad * ygrad); 278 if (magnitudes.pixels[r][c] == 0) 279 orientations.pixels[r][c] = 0; 280 else 281 orientations.pixels[r][c] = (float) FastMath.atan(ygrad / xgrad); 282 } 283 } 284 } 285 286 /** 287 * Estimate gradients magnitudes and orientations by calculating pixel 288 * differences. Edges get special treatment. 289 * <p> 290 * The orientations are quantised into <code>magnitudes.length</code> bins 291 * and the magnitudes are spread to the adjacent bin through linear 292 * interpolation. The magnitudes parameter must be fully allocated as an 293 * array of num orientation bin images, each of the same size as the input 294 * image. 295 * 296 * @param image 297 * @param magnitudes 298 */ 299 public static void gradientMagnitudesAndQuantisedOrientations(FImage image, FImage[] magnitudes) 300 { 301 final int numOriBins = magnitudes.length; 302 303 // Note: unrolling this loop to remove the if's doesn't 304 // actually seem to make it faster! 305 for (int r = 0; r < image.height; r++) { 306 for (int c = 0; c < image.width; c++) { 307 float xgrad, ygrad; 308 309 if (c == 0) 310 xgrad = 2.0f * (image.pixels[r][c + 1] - image.pixels[r][c]); 311 else if (c == image.width - 1) 312 xgrad = 2.0f * (image.pixels[r][c] - image.pixels[r][c - 1]); 313 else 314 xgrad = image.pixels[r][c + 1] - image.pixels[r][c - 1]; 315 if (r == 0) 316 ygrad = 2.0f * (image.pixels[r][c] - image.pixels[r + 1][c]); 317 else if (r == image.height - 1) 318 ygrad = 2.0f * (image.pixels[r - 1][c] - image.pixels[r][c]); 319 else 320 ygrad = image.pixels[r - 1][c] - image.pixels[r + 1][c]; 321 322 // JH - my benchmarking shows that (at least on OSX) Math.atan2 323 // is really 324 // slow... FastMath provides an alternative that is much faster 325 final float mag = (float) Math.sqrt(xgrad * xgrad + ygrad * ygrad); 326 float ori = (float) FastMath.atan2(ygrad, xgrad); 327 328 // adjust range 329 ori = ((ori %= TWO_PI_FLOAT) >= 0 ? ori : (ori + TWO_PI_FLOAT)); 330 331 final float po = numOriBins * ori / TWO_PI_FLOAT; // po is now 332 // 0<=po<oriSize 333 334 final int oi = (int) Math.floor(po); 335 final float of = po - oi; 336 337 // reset 338 for (int i = 0; i < magnitudes.length; i++) 339 magnitudes[i].pixels[r][c] = 0; 340 341 // set 342 magnitudes[oi % numOriBins].pixels[r][c] = (1f - of) * mag; 343 magnitudes[(oi + 1) % numOriBins].pixels[r][c] = of * mag; 344 } 345 } 346 } 347 348 /** 349 * Estimate gradients magnitudes and orientations by calculating pixel 350 * differences. Edges get special treatment. 351 * <p> 352 * The orientations are quantised into <code>magnitudes.length</code> bins. 353 * Magnitudes are optionally spread to the adjacent bin through linear 354 * interpolation. The magnitudes parameter must be fully allocated as an 355 * array of num orientation bin images, each of the same size as the input 356 * image. 357 * 358 * @param image 359 * @param magnitudes 360 * @param interp 361 * @param mode 362 */ 363 public static void gradientMagnitudesAndQuantisedOrientations(FImage image, FImage[] magnitudes, boolean interp, 364 Mode mode) 365 { 366 final int numOriBins = magnitudes.length; 367 368 // Note: unrolling this loop to remove the if's doesn't 369 // actually seem to make it faster! 370 for (int r = 0; r < image.height; r++) { 371 for (int c = 0; c < image.width; c++) { 372 float xgrad, ygrad; 373 374 if (c == 0) 375 xgrad = 2.0f * (image.pixels[r][c + 1] - image.pixels[r][c]); 376 else if (c == image.width - 1) 377 xgrad = 2.0f * (image.pixels[r][c] - image.pixels[r][c - 1]); 378 else 379 xgrad = image.pixels[r][c + 1] - image.pixels[r][c - 1]; 380 if (r == 0) 381 ygrad = 2.0f * (image.pixels[r][c] - image.pixels[r + 1][c]); 382 else if (r == image.height - 1) 383 ygrad = 2.0f * (image.pixels[r - 1][c] - image.pixels[r][c]); 384 else 385 ygrad = image.pixels[r - 1][c] - image.pixels[r + 1][c]; 386 387 // JH - my benchmarking shows that (at least on OSX) Math.atan2 388 // is really 389 // slow... FastMath provides an alternative that is much faster 390 final float mag = (float) Math.sqrt(xgrad * xgrad + ygrad * ygrad); 391 392 float po; 393 if (mode == Mode.Unsigned) { 394 final float ori = mag == 0 ? PI_OVER_TWO_FLOAT : (float) FastMath.atan(ygrad / xgrad) 395 + PI_OVER_TWO_FLOAT; 396 397 po = numOriBins * ori / PI_FLOAT; // po is now 0<=po<oriSize 398 } else { 399 float ori = (float) FastMath.atan2(ygrad, xgrad); 400 401 // adjust range 402 ori = ((ori %= TWO_PI_FLOAT) >= 0 ? ori : (ori + TWO_PI_FLOAT)); 403 404 po = numOriBins * ori / TWO_PI_FLOAT; // po is now 405 // 0<=po<oriSize 406 } 407 408 // reset 409 for (int i = 0; i < magnitudes.length; i++) 410 magnitudes[i].pixels[r][c] = 0; 411 412 int oi = (int) Math.floor(po); 413 final float of = po - oi; 414 415 // set 416 if (interp) { 417 magnitudes[oi % numOriBins].pixels[r][c] = (1f - of) * mag; 418 magnitudes[(oi + 1) % numOriBins].pixels[r][c] = of * mag; 419 } else { 420 // if (of > 0.5) 421 // magnitudes[(oi + 1) % numOriBins].pixels[r][c] = mag; 422 // else 423 if (oi > numOriBins - 1) 424 oi = numOriBins - 1; 425 magnitudes[oi].pixels[r][c] = mag; 426 } 427 } 428 } 429 } 430}