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/**
031 * 
032 */
033package org.openimaj.image.processing.edges;
034
035import gnu.trove.list.array.TFloatArrayList;
036
037import org.openimaj.citation.annotation.Reference;
038import org.openimaj.citation.annotation.ReferenceType;
039import org.openimaj.image.FImage;
040import org.openimaj.image.processor.SinglebandImageProcessor;
041
042/**
043 * Implementations of the SUSAN edge detection algorithm. The default processor
044 * uses the simple version; there are static methods for the other versions
045 * which are pretty slow. However, the circular version allows you to detect
046 * "fat" lines which would otherwise be detected as two separate lines.
047 * 
048 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
049 * 
050 * @created 18 Jun 2012
051 */
052@Reference(
053                author = { "S. M. Smith" },
054                title = "A new class of corner finder",
055                type = ReferenceType.Article,
056                url = "http://users.fmrib.ox.ac.uk/~steve/susan/susan/node4.html",
057                year = "1992",
058                booktitle = "Proc. 3rd British Machine Vision Conference",
059                pages = "139-148")
060public class SUSANEdgeDetector implements
061                SinglebandImageProcessor<Float, FImage>
062{
063        /**
064         * An enumerator of different SUSAN edge detector types
065         * 
066         * @author David Dupplaw (dpd@ecs.soton.ac.uk)
067         * 
068         * @created 18 Jun 2012
069         */
070        private enum SUSANDetector
071        {
072                /**
073                 * The simple, fast SUSAN detector
074                 */
075                SIMPLE
076                {
077                        @Override
078                        public FImage process(FImage img)
079                        {
080                                return SUSANEdgeDetector.simpleSusan(img, threshold, nmax);
081                        }
082                },
083                /**
084                 * The smooth SUSAN detector
085                 */
086                SMOOTH
087                {
088                        @Override
089                        public FImage process(FImage img)
090                        {
091                                return SUSANEdgeDetector.smoothSusan(img, threshold, nmax);
092                        }
093                },
094                /**
095                 * The smooth, circular detector
096                 */
097                CIRCULAR
098                {
099                        @Override
100                        public FImage process(FImage img)
101                        {
102                                return SUSANEdgeDetector.smoothCircularSusan(img, threshold, nmax, radius);
103                        }
104                };
105
106                protected double threshold = 0.08;
107                protected double nmax = 9;
108                protected double radius = 3.4;
109
110                public abstract FImage process(FImage img);
111        }
112
113        /** The SUSAN detector in use */
114        private SUSANDetector susan = SUSANDetector.SIMPLE;
115
116        /**
117         * Default constructor that instantiates a simple SUSAN edge detector with
118         * threshold 0.08 and global threshold weighting 9.
119         */
120        public SUSANEdgeDetector()
121        {
122                this.susan = SUSANDetector.SIMPLE;
123        }
124
125        /**
126         * @param s
127         *            The susan detector to use
128         * @param threshold
129         *            The threshold to use
130         * @param nmax
131         *            The global threshold weighting
132         */
133        public SUSANEdgeDetector(SUSANDetector s, double threshold,
134                        double nmax)
135        {
136                this.susan = s;
137                susan.threshold = threshold;
138                susan.nmax = nmax;
139        }
140
141        /**
142         * @param s
143         *            The susan detector to use
144         * @param threshold
145         *            The threshold to use
146         * @param nmax
147         *            The global threshold weighting
148         * @param radius
149         *            The radius of the circular susan
150         */
151        public SUSANEdgeDetector(SUSANDetector s, double threshold,
152                        double nmax, double radius)
153        {
154                this.susan = s;
155                susan.threshold = threshold;
156                susan.nmax = nmax;
157                susan.radius = radius;
158        }
159
160        /**
161         * {@inheritDoc}
162         * 
163         * @see org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj.image.Image)
164         */
165        @Override
166        public void processImage(FImage image)
167        {
168                image.internalAssign(susan.process(image));
169        }
170
171        /**
172         * Performs the simple SUSAN edge detection.
173         * 
174         * @param img
175         *            The image to find edges in
176         * @param thresh
177         *            The threshold
178         * @param nmax
179         *            The global threshold weighting
180         * @return Edge image
181         */
182        public static FImage simpleSusan(FImage img, double thresh, double nmax)
183        {
184                final FImage area = new FImage(img.getWidth(), img.getHeight());
185
186                final double globalThresh = (3.0 * nmax) / 4.0;
187
188                for (int y = 1; y < img.getHeight() - 1; y++)
189                {
190                        for (int x = 1; x < img.getWidth() - 1; x++)
191                        {
192                                double a = 0;
193                                for (int x1 = x - 1; x1 < x + 2; x1++)
194                                {
195                                        for (int y1 = y - 1; y1 < y + 2; y1++)
196                                        {
197                                                if (Math.abs(img.getPixel(x1, y1) - img.getPixel(x, y)) < thresh)
198                                                        a++;
199                                        }
200                                }
201
202                                if (a < globalThresh)
203                                        area.setPixel(x, y, (float) (globalThresh - a));
204                        }
205                }
206
207                return area;
208        }
209
210        /**
211         * Performs the simple SUSAN edge detection.
212         * 
213         * @param img
214         *            The image to find edges in
215         * @param thresh
216         *            The threshold
217         * @param nmax
218         *            The global threshold weighting
219         * @return Edge image
220         */
221        public static FImage smoothSusan(FImage img, double thresh, double nmax)
222        {
223                final FImage area = new FImage(img.getWidth(), img.getHeight());
224
225                final double globalThresh = (3.0 * nmax) / 4.0;
226
227                for (int y = 1; y < img.getHeight() - 1; y++)
228                {
229                        for (int x = 1; x < img.getWidth() - 1; x++)
230                        {
231                                double a = 0;
232                                for (int x1 = x - 1; x1 < x + 2; x1++)
233                                {
234                                        for (int y1 = y - 1; y1 < y + 2; y1++)
235                                        {
236                                                a += Math.exp(
237                                                                -Math.pow(
238                                                                                Math.abs(img.getPixel(x1, y1) -
239                                                                                                img.getPixel(x, y))
240                                                                                                / thresh, 6));
241                                        }
242                                }
243
244                                if (a < globalThresh)
245                                        area.setPixel(x, y, (float) (globalThresh - a));
246                        }
247                }
248
249                return area;
250        }
251
252        /**
253         * Performs the simple SUSAN edge detection.
254         * 
255         * @param img
256         *            The image to find edges in
257         * @param thresh
258         *            The threshold
259         * @param nmax
260         *            The global threshold weighting
261         * @param radius
262         *            The radius of the circle (try 3.4)
263         * @return Edge image
264         */
265        public static FImage smoothCircularSusan(FImage img, double thresh, double nmax, double radius)
266        {
267                final FImage area = new FImage(img.getWidth(), img.getHeight());
268                final double globalThresh = (3.0 * nmax) / 4.0;
269
270                final int r = (int) Math.ceil(radius);
271                for (int y = r; y < img.getHeight() - r; y++)
272                {
273                        for (int x = r; x < img.getWidth() - r; x++)
274                        {
275                                final float[] pixelValues = getPixelsInCircle(x, y, radius, img);
276                                double a = 0;
277                                for (final float f : pixelValues)
278                                        a += Math.exp(
279                                                        -Math.pow(
280                                                                        Math.abs(f -
281                                                                                        img.getPixel(x, y))
282                                                                                        / thresh, 6));
283
284                                if (a < globalThresh)
285                                        area.setPixel(x, y, (float) (globalThresh - a));
286                        }
287                }
288
289                return area;
290        }
291
292        /**
293         * Returns the values of pixels within a circle centres at cx, cy in the
294         * image img, with a radius r.
295         * 
296         * @param cx
297         *            The centre of the circle's x coordinate
298         * @param cy
299         *            The centre of the circle's y coordinate
300         * @param r
301         *            The radius of the circle
302         * @param img
303         *            The image from which to take pixels
304         * @return A list of pixel values
305         */
306        private static float[] getPixelsInCircle(int cx, int cy, double r, FImage img)
307        {
308                final TFloatArrayList f = new TFloatArrayList();
309                for (int i = (int) Math.ceil(cx - r); i < (int) Math.ceil(cx + r); i++)
310                {
311                        final double ri = Math.sqrt(r * r - (i - cx) * (i - cx));
312                        for (int j = (int) Math.ceil(cy - ri); j < (int) Math.ceil(cy + ri); j++)
313                        {
314                                f.add(img.getPixel(i, j));
315                        }
316                }
317                return f.toArray();
318        }
319}