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 */ 030 031package org.openimaj.image.processing.edges; 032 033import org.openimaj.image.DisplayUtilities; 034import org.openimaj.image.FImage; 035import org.openimaj.image.analysis.algorithm.EdgeDirectionCoherenceVector; 036import org.openimaj.image.processor.SinglebandImageProcessor; 037 038/** 039 * This implementation is deprecated and is only kept for backward-compatibility 040 * of old {@link EdgeDirectionCoherenceVector} features. Use the 041 * {@link CannyEdgeDetector} instead. 042 * <p> 043 * This is an implementation of the canny edge detector that was found somewhere 044 * out there on the web with no attribution. If this is your code and you don't 045 * want it in OpenIMAJ, please let us know. 046 * 047 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 048 */ 049@Deprecated 050public class CannyEdgeDetector2 implements SinglebandImageProcessor<Float, FImage> 051{ 052 private boolean complete; 053 054 /** The threshold */ 055 private int threshold = 128; 056 057 /** The first hysteresis threshold */ 058 private int hystThresh1 = 50; 059 060 /** The second hysteresis threshold */ 061 private int hystThresh2 = 230; 062 063 /** The Guassian kernel size */ 064 private int kernelSize = 15; 065 066 final float ORIENT_SCALE = 40F; 067 private int height; 068 private int width; 069 private int picsize; 070 private float[] data; 071 private int derivative_mag[]; 072 private float magnitude[]; 073 private float orientation[]; 074 private FImage sourceImage; 075 private FImage edgeImage; 076 077 /** 078 * Default constructor 079 */ 080 public CannyEdgeDetector2() 081 { 082 complete = false; 083 } 084 085 /** 086 * @return Returns whether the processing has completed. 087 */ 088 public boolean isImageReady() 089 { 090 return complete; 091 } 092 093 /** 094 * {@inheritDoc} 095 * 096 * @see org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj.image.Image) 097 */ 098 @Override 099 public void processImage(FImage image) 100 { 101 complete = false; 102 103 final int widGaussianKernel = kernelSize; 104 final int threshold = this.threshold; 105 final int threshold1 = hystThresh1; 106 final int threshold2 = hystThresh2; 107 108 if (threshold < 0 || threshold > 255) 109 { 110 throw new IllegalArgumentException("The value of the threshold " + 111 "is out of its valid range."); 112 } 113 114 if (widGaussianKernel < 3 || widGaussianKernel > 40) 115 { 116 throw new IllegalArgumentException("The value of the widGaussianKernel " + 117 "is out of its valid range."); 118 } 119 120 width = image.getWidth(); 121 height = image.getHeight(); 122 picsize = width * height; 123 sourceImage = image; 124 125 data = new float[picsize]; 126 magnitude = new float[picsize]; 127 orientation = new float[picsize]; 128 129 final float f = 1.0F; 130 canny_core(f, widGaussianKernel); 131 thresholding_tracker(threshold1, threshold2); 132 133 for (int i = 0; i < picsize; i++) 134 if (data[i] > threshold) 135 data[i] = 1; 136 else 137 data[i] = -1; 138 139 edgeImage = new FImage(data, width, height).normalise(); 140 data = null; 141 142 complete = true; 143 144 image.internalAssign(edgeImage); 145 } 146 147 /** 148 * Assumes the input is a one-dimensional representation of an image. 149 * Displays the image. 150 * 151 * @param data 152 * A one-dimensional representation of an image. 153 */ 154 protected void display(int[] data) 155 { 156 final FImage tmp = new FImage(width, height); 157 for (int r = 0; r < height; r++) 158 for (int c = 0; c < width; c++) 159 tmp.pixels[r][c] = data[c + r * width] / 255f; 160 DisplayUtilities.display(tmp); 161 } 162 163 /** 164 * Assumes the input is a one-dimensional representation of an image. 165 * Displays the image. 166 * 167 * @param data 168 * A one-dimensional representation of an image. 169 */ 170 protected void display(float[] data) 171 { 172 final FImage tmp = new FImage(width, height); 173 for (int r = 0; r < height; r++) 174 for (int c = 0; c < width; c++) 175 tmp.pixels[r][c] = data[c + r * width] / 255f; 176 DisplayUtilities.display(tmp); 177 } 178 179 /** 180 * @param f 181 * @param i 182 */ 183 private void canny_core(float f, int i) 184 { 185 derivative_mag = new int[picsize]; 186 final float af4[] = new float[i]; 187 final float af5[] = new float[i]; 188 final float af6[] = new float[i]; 189 // data = image2pixels( sourceImage ); 190 data = sourceImage.clone().multiply(255.0f).getFloatPixelVector(); 191 int k4 = 0; 192 do { 193 if (k4 >= i) 194 break; 195 final float f1 = gaussian(k4, f); 196 if (f1 <= 0.005F && k4 >= 2) 197 break; 198 final float f2 = gaussian(k4 - 0.5F, f); 199 final float f3 = gaussian(k4 + 0.5F, f); 200 final float f4 = gaussian(k4, f * 0.5F); 201 af4[k4] = (f1 + f2 + f3) / 3F / (6.283185F * f * f); 202 af5[k4] = f3 - f2; 203 af6[k4] = 1.6F * f4 - f1; 204 k4++; 205 } while (true); 206 207 final int j = k4; 208 float af[] = new float[picsize]; 209 float af1[] = new float[picsize]; 210 int j1 = width - (j - 1); 211 int l = width * (j - 1); 212 int i1 = width * (height - (j - 1)); 213 for (int l4 = j - 1; l4 < j1; l4++) { 214 for (int l5 = l; l5 < i1; l5 += width) { 215 final int k1 = l4 + l5; 216 float f8 = data[k1] * af4[0]; 217 float f10 = f8; 218 int l6 = 1; 219 int k7 = k1 - width; 220 for (int i8 = k1 + width; l6 < j; i8 += width) { 221 f8 += af4[l6] * (data[k7] + data[i8]); 222 f10 += af4[l6] * (data[k1 - l6] + data[k1 + l6]); 223 l6++; 224 k7 -= width; 225 } 226 227 af[k1] = f8; 228 af1[k1] = f10; 229 } 230 231 } 232 233 float af2[] = new float[picsize]; 234 for (int i5 = j - 1; i5 < j1; i5++) { 235 for (int i6 = l; i6 < i1; i6 += width) { 236 float f9 = 0.0F; 237 final int l1 = i5 + i6; 238 for (int i7 = 1; i7 < j; i7++) 239 f9 += af5[i7] * (af[l1 - i7] - af[l1 + i7]); 240 241 af2[l1] = f9; 242 } 243 244 } 245 246 af = null; 247 float af3[] = new float[picsize]; 248 for (int j5 = k4; j5 < width - k4; j5++) { 249 for (int j6 = l; j6 < i1; j6 += width) { 250 float f11 = 0.0F; 251 final int i2 = j5 + j6; 252 int j7 = 1; 253 for (int l7 = width; j7 < j; l7 += width) { 254 f11 += af5[j7] * (af1[i2 - l7] - af1[i2 + l7]); 255 j7++; 256 } 257 258 af3[i2] = f11; 259 } 260 261 } 262 263 // display(af3); 264 265 af1 = null; 266 j1 = width - j; 267 l = width * j; 268 i1 = width * (height - j); 269 for (int k5 = j; k5 < j1; k5++) { 270 for (int k6 = l; k6 < i1; k6 += width) { 271 final int j2 = k5 + k6; 272 final int k2 = j2 - width; 273 final int l2 = j2 + width; 274 final int i3 = j2 - 1; 275 final int j3 = j2 + 1; 276 final int k3 = k2 - 1; 277 final int l3 = k2 + 1; 278 final int i4 = l2 - 1; 279 final int j4 = l2 + 1; 280 final float f6 = af2[j2]; 281 final float f7 = af3[j2]; 282 final float f12 = hypotenuse(f6, f7); 283 final int k = (int) (f12 * 20D); 284 derivative_mag[j2] = k >= 256 ? 255 : k; 285 final float f13 = hypotenuse(af2[k2], af3[k2]); 286 final float f14 = hypotenuse(af2[l2], af3[l2]); 287 final float f15 = hypotenuse(af2[i3], af3[i3]); 288 final float f16 = hypotenuse(af2[j3], af3[j3]); 289 final float f18 = hypotenuse(af2[l3], af3[l3]); 290 final float f20 = hypotenuse(af2[j4], af3[j4]); 291 final float f19 = hypotenuse(af2[i4], af3[i4]); 292 final float f17 = hypotenuse(af2[k3], af3[k3]); 293 float f5; 294 if (f6 * f7 <= 0 295 ? Math.abs(f6) >= Math.abs(f7) 296 ? (f5 = Math.abs(f6 * f12)) 297 >= Math.abs(f7 * f18 - (f6 + f7) * f16) 298 && f5 299 > Math.abs(f7 * f19 - (f6 + f7) * f15) : ( 300 f5 = Math.abs(f7 * f12)) 301 >= Math.abs(f6 * f18 - (f7 + f6) * f13) 302 && f5 303 > Math.abs(f6 * f19 - (f7 + f6) * f14) : Math.abs(f6) 304 >= Math.abs(f7) 305 ? (f5 = Math.abs(f6 * f12)) 306 >= Math.abs(f7 * f20 + (f6 - f7) * f16) 307 && f5 308 > Math.abs(f7 * f17 + (f6 - f7) * f15) : ( 309 f5 = Math.abs(f7 * f12)) 310 >= Math.abs(f6 * f20 + (f7 - f6) * f14) 311 && f5 > Math.abs(f6 * f17 + (f7 - f6) * f13)) 312 { 313 magnitude[j2] = derivative_mag[j2]; 314 orientation[j2] = (float) Math.toDegrees( 315 Math.atan2(f7, f6)); 316 } 317 } 318 319 } 320 321 derivative_mag = null; 322 af2 = null; 323 af3 = null; 324 } 325 326 /** 327 * If <code>f</code> and <code>f1</code> are the shorter sides of a 328 * triangle, calculates the hypotenuse of the triangle. 329 * 330 * @param f 331 * short side of a triangle 332 * @param f1 333 * short side of a triangle 334 * @return The length of the hypotenuse. 335 */ 336 private float hypotenuse(float f, float f1) 337 { 338 if (f == 0.0F && f1 == 0.0F) 339 return 0.0F; 340 else 341 return (float) Math.sqrt(f * f + f1 * f1); 342 } 343 344 private float gaussian(float f, float f1) { 345 return (float) Math.exp((-f * f) / (2 * f1 * f1)); 346 } 347 348 private void thresholding_tracker(int i, int j) { 349 for (int k = 0; k < picsize; k++) 350 data[k] = 0; 351 352 for (int l = 0; l < width; l++) { 353 for (int i1 = 0; i1 < height; i1++) 354 if (magnitude[l + width * i1] >= i) 355 follow(l, i1, j); 356 357 } 358 359 } 360 361 /** 362 * @param i 363 * @param j 364 * @param k 365 * @return 366 */ 367 private boolean follow(int i, int j, int k) 368 { 369 int j1 = i + 1; 370 int k1 = i - 1; 371 int l1 = j + 1; 372 int i2 = j - 1; 373 final int j2 = i + j * width; 374 if (l1 >= height) 375 l1 = height - 1; 376 if (i2 < 0) 377 i2 = 0; 378 if (j1 >= width) 379 j1 = width - 1; 380 if (k1 < 0) 381 k1 = 0; 382 if (data[j2] == 0) { 383 data[j2] = magnitude[j2]; 384 boolean flag = false; 385 int l = k1; 386 do { 387 if (l > j1) 388 break; 389 int i1 = i2; 390 do { 391 if (i1 > l1) 392 break; 393 final int k2 = l + i1 * width; 394 if ((i1 != j || l != i) 395 && magnitude[k2] >= k 396 && follow(l, i1, k)) 397 { 398 flag = true; 399 break; 400 } 401 i1++; 402 } while (true); 403 if (!flag) 404 break; 405 l++; 406 } while (true); 407 return true; 408 } else { 409 return false; 410 } 411 } 412 413 /** 414 * @param image 415 */ 416 public void setSourceImage(FImage image) 417 { 418 sourceImage = image; 419 } 420 421 /** 422 * @return edgeImage 423 */ 424 public FImage getEdgeImage() 425 { 426 return edgeImage; 427 } 428 429 /** 430 * @return magnitude 431 */ 432 public float[] getMagnitude() 433 { 434 return magnitude; 435 } 436 437 /** 438 * @return orientation 439 */ 440 public float[] getOrientation() 441 { 442 return orientation; 443 } 444 445 /** 446 * Get the threshold above which an edge pixel will be considered an edge. 447 * 448 * @return the threshold above which edge pixels will be considered edges. 449 */ 450 public int getThreshold() 451 { 452 return threshold; 453 } 454 455 /** 456 * Get the threshold above which an edge pixel will be considered an edge. 457 * 458 * @param threshold 459 * the threshold above which an edge pixel will be considered an 460 * edge. 461 */ 462 public void setThreshold(int threshold) 463 { 464 this.threshold = threshold; 465 } 466 467 /** 468 * Get the first hysteresis threshold. 469 * 470 * @return the first hysteresis threshold. 471 */ 472 public int getHystThresh1() 473 { 474 return hystThresh1; 475 } 476 477 /** 478 * Set the fist hysteresis threshold. 479 * 480 * @param hystThresh1 481 * the threshold value 482 */ 483 public void setHystThresh1(int hystThresh1) 484 { 485 this.hystThresh1 = hystThresh1; 486 } 487 488 /** 489 * Get the second hysteresis threshold. 490 * 491 * @return the second hysteresis threshold. 492 */ 493 public int getHystThresh2() 494 { 495 return hystThresh2; 496 } 497 498 /** 499 * Set the second hysteresis threshold. 500 * 501 * @param hystThresh2 502 * the threshold value 503 */ 504 public void setHystThresh2(int hystThresh2) 505 { 506 this.hystThresh2 = hystThresh2; 507 } 508 509 /** 510 * Get the kernel size being used. 511 * 512 * @return the kernel size being used for blurring 513 */ 514 public int getKernelSize() 515 { 516 return kernelSize; 517 } 518 519 /** 520 * Set the kernel size to use. 521 * 522 * @param kernelSize 523 * the size of the kernel to use for blurring. 524 */ 525 public void setKernelSize(int kernelSize) 526 { 527 this.kernelSize = kernelSize; 528 } 529}