View Javadoc

1   /**
2    * Copyright (c) 2011, The University of Southampton and the individual contributors.
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without modification,
6    * are permitted provided that the following conditions are met:
7    *
8    *   * 	Redistributions of source code must retain the above copyright notice,
9    * 	this list of conditions and the following disclaimer.
10   *
11   *   *	Redistributions in binary form must reproduce the above copyright notice,
12   * 	this list of conditions and the following disclaimer in the documentation
13   * 	and/or other materials provided with the distribution.
14   *
15   *   *	Neither the name of the University of Southampton nor the names of its
16   * 	contributors may be used to endorse or promote products derived from this
17   * 	software without specific prior written permission.
18   *
19   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29   */
30  
31  package org.openimaj.image.processing.edges;
32  
33  import org.openimaj.image.DisplayUtilities;
34  import org.openimaj.image.FImage;
35  import org.openimaj.image.analysis.algorithm.EdgeDirectionCoherenceVector;
36  import org.openimaj.image.processor.SinglebandImageProcessor;
37  
38  /**
39   * This implementation is deprecated and is only kept for backward-compatibility
40   * of old {@link EdgeDirectionCoherenceVector} features. Use the
41   * {@link CannyEdgeDetector} instead.
42   * <p>
43   * This is an implementation of the canny edge detector that was found somewhere
44   * out there on the web with no attribution. If this is your code and you don't
45   * want it in OpenIMAJ, please let us know.
46   *
47   * @author David Dupplaw (dpd@ecs.soton.ac.uk)
48   */
49  @Deprecated
50  public class CannyEdgeDetector2 implements SinglebandImageProcessor<Float, FImage>
51  {
52  	private boolean complete;
53  
54  	/** The threshold */
55  	private int threshold = 128;
56  
57  	/** The first hysteresis threshold */
58  	private int hystThresh1 = 50;
59  
60  	/** The second hysteresis threshold */
61  	private int hystThresh2 = 230;
62  
63  	/** The Guassian kernel size */
64  	private int kernelSize = 15;
65  
66  	final float ORIENT_SCALE = 40F;
67  	private int height;
68  	private int width;
69  	private int picsize;
70  	private float[] data;
71  	private int derivative_mag[];
72  	private float magnitude[];
73  	private float orientation[];
74  	private FImage sourceImage;
75  	private FImage edgeImage;
76  
77  	/**
78  	 * Default constructor
79  	 */
80  	public CannyEdgeDetector2()
81  	{
82  		complete = false;
83  	}
84  
85  	/**
86  	 * @return Returns whether the processing has completed.
87  	 */
88  	public boolean isImageReady()
89  	{
90  		return complete;
91  	}
92  
93  	/**
94  	 * {@inheritDoc}
95  	 *
96  	 * @see org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj.image.Image)
97  	 */
98  	@Override
99  	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 }