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   * 
32   */
33  package org.openimaj.image.processing.edges;
34  
35  import gnu.trove.list.array.TFloatArrayList;
36  
37  import org.openimaj.citation.annotation.Reference;
38  import org.openimaj.citation.annotation.ReferenceType;
39  import org.openimaj.image.FImage;
40  import org.openimaj.image.processor.SinglebandImageProcessor;
41  
42  /**
43   * Implementations of the SUSAN edge detection algorithm. The default processor
44   * uses the simple version; there are static methods for the other versions
45   * which are pretty slow. However, the circular version allows you to detect
46   * "fat" lines which would otherwise be detected as two separate lines.
47   * 
48   * @author David Dupplaw (dpd@ecs.soton.ac.uk)
49   * 
50   * @created 18 Jun 2012
51   */
52  @Reference(
53  		author = { "S. M. Smith" },
54  		title = "A new class of corner finder",
55  		type = ReferenceType.Article,
56  		url = "http://users.fmrib.ox.ac.uk/~steve/susan/susan/node4.html",
57  		year = "1992",
58  		booktitle = "Proc. 3rd British Machine Vision Conference",
59  		pages = "139-148")
60  public class SUSANEdgeDetector implements
61  		SinglebandImageProcessor<Float, FImage>
62  {
63  	/**
64  	 * An enumerator of different SUSAN edge detector types
65  	 * 
66  	 * @author David Dupplaw (dpd@ecs.soton.ac.uk)
67  	 * 
68  	 * @created 18 Jun 2012
69  	 */
70  	private enum SUSANDetector
71  	{
72  		/**
73  		 * The simple, fast SUSAN detector
74  		 */
75  		SIMPLE
76  		{
77  			@Override
78  			public FImage process(FImage img)
79  			{
80  				return SUSANEdgeDetector.simpleSusan(img, threshold, nmax);
81  			}
82  		},
83  		/**
84  		 * The smooth SUSAN detector
85  		 */
86  		SMOOTH
87  		{
88  			@Override
89  			public FImage process(FImage img)
90  			{
91  				return SUSANEdgeDetector.smoothSusan(img, threshold, nmax);
92  			}
93  		},
94  		/**
95  		 * The smooth, circular detector
96  		 */
97  		CIRCULAR
98  		{
99  			@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 }