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}