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}