// Catalano Imaging Library
// The Catalano Framework
//
// Copyright © Diego Catalano, 2012-2016
// diego.catalano at live.com
//
// Copyright © Andrew Kirillov, 2007-2008
// andrew.kirillov at gmail.com
//
//    This library is free software; you can redistribute it and/or
//    modify it under the terms of the GNU Lesser General Public
//    License as published by the Free Software Foundation; either
//    version 2.1 of the License, or (at your option) any later version.
//
//    This library is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//    Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public
//    License along with this library; if not, write to the Free Software
//    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

package Catalano.Imaging.Filters;

import Catalano.Imaging.FastBitmap;
import Catalano.Imaging.IApplyInPlace;

/**
 * Canny edge detector.
 * The filter searches for objects' edges by applying Canny edge detector. The implementation follows Bill Green's Canny edge detection tutorial.
 * 
 * 

The implemented canny edge detector has one difference with the above linked algorithm. * The difference is in hysteresis step, which is a bit simplified (getting faster as a result). * On the hysteresis step each pixel is compared with two threshold values: HighThreshold and LowThreshold. * If pixel's value is greater or equal to HighThreshold, then it is kept as edge pixel. * If pixel's value is greater or equal to LowThreshold, then it is kept as edge pixel only if there is at least one neighbouring pixel (8 neighbours are checked) * which has value greater or equal to HighThreshold; otherwise it is none edge pixel. * In the case if pixel's value is less than LowThreshold, then it is marked as none edge immediately.

* *

  • Supported types: Grayscale. *
  • Coordinate System: Matrix. * * @author Diego Catalano */ public class CannyEdgeDetector implements IApplyInPlace{ private double sigma = 1.4D; private int size = 1; private int lowThreshold = 20; private int highThreshold = 100; /** * Get Low threshold. * Used for Hysteresis. * @return Low threshold. */ public int getLowThreshold() { return lowThreshold; } /** * Set Low threshold. * @param lowThreshold Threshold value. */ public void setLowThreshold(int lowThreshold) { this.lowThreshold = lowThreshold; } /** * Get High threshold. * Used for Hysteresis. * @return Threshold value. */ public int getHighThreshold() { return highThreshold; } /** * Set High threshold. * @param highThreshold Threshold value. */ public void setHighThreshold(int highThreshold) { this.highThreshold = highThreshold; } /** * Get Gaussian sigma. * @return Gaussian sigma. */ public double getSigma() { return sigma; } /** * Set Gaussian sigma. * @param sigma Gaussian sigma. */ public void setSigma(double sigma) { this.sigma = sigma; } /** * Get Gaussian kernel size. * @return Gaussian kernel size. */ public int getSize() { return size; } /** * Set Gaussian kernel size. * @param size Gaussian kernel size. */ public void setSize(int size) { this.size = size; } /** * Initialize a new instance of the CannyEdgeDetector class. */ public CannyEdgeDetector() {} /** * Initialize a new instance of the CannyEdgeDetector class. * @param lowThreshold Low threshold. (Used for hysteresis). * @param highThreshold High Threshold. (Used for hysteresis). */ public CannyEdgeDetector(int lowThreshold, int highThreshold){ this.lowThreshold = lowThreshold; this.highThreshold = highThreshold; } /** * Initialize a new instance of the CannyEdgeDetector class. * @param lowThreshold Low threshold. (Used for hysteresis). * @param highThreshold High Threshold. (Used for hysteresis). * @param sigma Gaussian sigma. */ public CannyEdgeDetector(int lowThreshold, int highThreshold, double sigma){ this.lowThreshold = lowThreshold; this.highThreshold = highThreshold; this.sigma = sigma; } /** * Initialize a new instance of the CannyEdgeDetector class. * @param lowThreshold Low threshold. (Used for hysteresis). * @param highThreshold High Threshold. (Used for hysteresis). * @param sigma Gaussian sigma. * @param size Size of gaussian kernel. */ public CannyEdgeDetector(int lowThreshold, int highThreshold, double sigma, int size){ this.lowThreshold = lowThreshold; this.highThreshold = highThreshold; this.sigma = sigma; this.size = size; } @Override public void applyInPlace(FastBitmap fastBitmap) { if (fastBitmap.isGrayscale()){ int width = fastBitmap.getWidth(); int height = fastBitmap.getHeight(); int gx, gy; double orientation, toAngle = 180.0 / Math.PI; float leftPixel = 0, rightPixel = 0; // STEP 1 - Apply Gaussian Blur FastBitmap blurredImage = new FastBitmap(fastBitmap); GaussianBlur g = new GaussianBlur(sigma, size); g.applyInPlace(blurredImage); int[] orients = new int[width * height]; float[][] gradients = new float[width][height]; float maxGradient = Float.NEGATIVE_INFINITY; // STEP 2 - calculate magnitude and edge orientation int p = 0; for (int x = 1; x < height - 1; x++) { for (int y = 1; y < width - 1; y++, p++) { int p1 = blurredImage.getGray(x - 1, y + 1); int p2 = blurredImage.getGray(x + 1, y + 1); int p3 = blurredImage.getGray(x - 1, y - 1); int p4 = blurredImage.getGray(x + 1, y - 1); int p5 = blurredImage.getGray(x, y + 1); int p6 = blurredImage.getGray(x, y - 1); int p7 = blurredImage.getGray(x - 1, y); int p8 = blurredImage.getGray(x + 1, y); gx = p1 + p2 - p3 - p4 + 2 * (p5 - p6); gy = p3 + p1 - p4 - p2 + 2 * (p7 - p8); // get gradient value gradients[y][x] = (float) Math.sqrt( gx * gx + gy * gy ); if ( gradients[y][x] > maxGradient ) maxGradient = gradients[y][x]; // --- get orientation if ( gx == 0 ) { // can not divide by zero orientation = ( gy == 0 ) ? 0 : 90; } else { double div = (double) gy / gx; // handle angles of the 2nd and 4th quads if ( div < 0 ) { orientation = 180 - Math.atan( -div ) * toAngle; } // handle angles of the 1st and 3rd quads else { orientation = Math.atan( div ) * toAngle; } // get closest angle from 0, 45, 90, 135 set if ( orientation < 22.5 ) orientation = 0; else if ( orientation < 67.5 ) orientation = 45; else if ( orientation < 112.5 ) orientation = 90; else if ( orientation < 157.5 ) orientation = 135; else orientation = 0; } // save orientation orients[p] = (int)orientation; } } p = 0; // STEP 3 - suppress non maximums for (int x = 1; x < height - 1; x++) { for (int y = 1; y < width - 1; y++, p++) { // get two adjacent pixels switch ( orients[p] ) { case 0: leftPixel = gradients[y - 1][x]; rightPixel = gradients[y + 1][x]; break; case 45: leftPixel = gradients[y - 1][x + 1]; rightPixel = gradients[y + 1][x - 1]; break; case 90: leftPixel = gradients[y][x + 1]; rightPixel = gradients[y][x - 1]; break; case 135: leftPixel = gradients[y + 1][x + 1]; rightPixel = gradients[y - 1][x - 1]; break; } // compare current pixels value with adjacent pixels if ( ( gradients[y][x] < leftPixel ) || ( gradients[y][x] < rightPixel ) ) { fastBitmap.setGray(x, y, 0); } else { fastBitmap.setGray(x, y, (int)( gradients[y][x] / maxGradient * 255 )); } } } // STEP 4 - Hysteresis Threshold HysteresisThreshold threshold = new HysteresisThreshold(lowThreshold, highThreshold); threshold.applyInPlace(fastBitmap); } else{ throw new IllegalArgumentException("CannyEdgeDetector only works in grayscale images."); } } }
  • Ads help maintain this website.