import java.applet.*; import java.awt.*; import java.awt.image.*; import java.net.*; import java.util.*; import java.io.*; import java.lang.Math.*; import java.util.Stack.*; /** *Canny is an algorithm to apply the canny edge detector to an image *@author:Timothy Sharman *@see code.iface.canny */ public class Canny extends Thread{ //the Gaussian smoothing operator used as part of the process. GaussianSmooth gaussiansmooth; //The Contrast stretching operator used as part of the process // ContrastStretch contraststretch = new ContrastStretch(); //the width and height of the output image private int d_w; private int d_h; private int[] dest_1d; /** *Applies the canny edge detector to the input image *@param src_1d The source image as a pixel array *@param width width of the destination image in pixels *@param height height of the destination image in pixels *@param size The size of the kernel used in the smoothing *@param theta The gaussian smoothing standard deviation *@param lowthresh The low threshold for the tracking *@param highthresh The high threshold for the tracking *@param scale The amount of scaling to be applied to the image *@param offset The amount to be added to each result pixel *@return A pixel array containing edges in the image */ //Tim's Canny Edge Detection Algorithm //Based on algorithm in Machine Vision (pg 169) /*a) assume the image is grey level (hence RR=GG=BB) b) use value &0x000000ff to get the BB value c) gaussian smooth image d) work out gradient magnitude e) apply nonmaxima suppression f) threshold and detect edges */ public int [] apply_canny(int [] src_1d, int width, int height, int size, float theta, int lowthresh, int highthresh, float scale, int offset) { d_w = width; d_h = height; //Setup local variables dest_1d = new int[d_w*d_h]; int [] tmp_1d = new int[d_w*d_h]; int [][] tmp_2d = new int[d_w][d_h]; int [][] p_2d = new int[d_w][d_h]; int [][] q_2d = new int[d_w][d_h]; int [][] m_2d = new int[d_w][d_h]; double [][] theta_2d = new double[d_w][d_h]; int [][] nms = new int[d_w][d_h]; int [][] delta = new int[d_w][d_h]; int [][] tracked = new int[d_w][d_h]; int result; int tmp = 0; int [] tmp2_1d; //Set up the output array for(int i = 0; i < dest_1d.length; i++){ dest_1d[i] = 0xff000000; } //Smooth the initial image tmp_1d = gaussiansmooth. smooth_image(src_1d, width, height, size, theta); //Mask off so that we work with values between 0 and 255 for(int i = 0; i < tmp_1d.length; i++){ tmp_1d[i] = tmp_1d[i] & 0x000000ff; } //Convert 1_d to 2_d for ease of processing in next stages for(int i = 0; i < d_w; i++){ for(int j = 0; j < d_h; j++){ tmp_2d[i][j] = tmp_1d[i+(j*d_w)]; } } //Apply the gradient detection for(int i = 0; i < (d_w-1); i++){ for(int j = 0; j < (d_h-1); j++){ p_2d[i][j] = (tmp_2d[i][j+1]-tmp_2d[i][j]+ tmp_2d[i+1][j+1]-tmp_2d[i+1][j])/2; q_2d[i][j] = (tmp_2d[i][j]-tmp_2d[i+1][j]+ tmp_2d[i][j+1]-tmp_2d[i+1][j+1])/2; m_2d[i][j] = (int)Math.sqrt(Math.pow((double)p_2d[i][j],2)+ Math.pow((double)q_2d[i][j],2)); theta_2d[i][j] = Math.atan2((double)(q_2d[i][j]),(double)(p_2d[i][j])); } } //Resize image d_w--; d_h--; //Apply the nonmaxima suppression //First calculate which sector each line appears in for(int i = 0; i < d_w; i++){ for(int j = 0; j < d_h; j++){ delta[i][j] = sector(theta_2d[i][j]); } } //Then apply non maximal suppression for(int i = 0; i < (d_w-1); i++){ nms[i][0] = 0; nms[i][d_h-1] = 0; } for(int j = 0; j < (d_h-1); j++){ nms[0][j] = 0; nms[d_w-1][j] = 0; } for(int i = 1; i < (d_w-1); i++){ for(int j = 1; j < (d_h-1); j++){ nms[i][j] = suppress(m_2d, delta[i][j], i, j,lowthresh); } } //Resize again! d_w = d_w - 2; d_h = d_h - 2; //Track the image tracked = apply_track(nms, d_w, d_h, lowthresh, highthresh); //Calculate the output array for(int i = 0; i < d_w; i++){ for(int j = 0; j < d_h; j++){ result = tracked[i][j]; result = (int) (result * scale); result = result + offset; if(result > 255){result = 255;} if(result < 0){result = 0;} dest_1d[(i+(j*(d_w+3)))] = 0xff000000 | result << 16 | result << 8 | result; } } //Change the sizes back d_w = d_w + 3; d_h = d_h + 3; return dest_1d; } //Function to check which sector the line is in (see Machine Vision pg 171) private int sector(double theta){ //Converting into degrees from radians, and moving to lie between 0 and 360 theta = Math.toDegrees(theta); theta = theta + 270 ; theta = theta % 360; if((theta >= 337.5) || (theta < 22.5) || ((theta >= 157.5) && (theta < 202.5))){ return 0; } if(((theta >= 22.5) && (theta < 67.5)) || ((theta >=202.5) && (theta < 247.5))){ return 1; } if(((theta >= 67.5) && (theta < 112.5)) || ((theta >=247.5) && (theta < 292.5))){ return 2; } if(((theta >= 112.5) && (theta < 157.5)) || ((theta >= 292.5) && (theta < 337.5))){ return 3; } return 0; } // Function to apply non maxima suppression to the image array private int suppress(int[][] m_2d, int sector, int i, int j, int lowthresh){ int tmp = m_2d[i][j]; if (tmp < lowthresh) return 0; //if (318 < i && i < 322 && 113 < j && j < 117)System.out.println("ij("+i+","+j+") sector: "+sector+" neigh: "+m_2d[i-1][j-1]+" "+m_2d[i-1][j]+" "+m_2d[i-1][j+1]+" "+m_2d[i][j-1]+" "+m_2d[i][j]+" "+m_2d[i][j+1]+" "+m_2d[i+1][j-1]+" "+m_2d[i+1][j]+" "+m_2d[i+1][j+1]); if(sector == 0){ if((m_2d[i+1][j] >= tmp) || (m_2d[i-1][j] > tmp)){ return 0; } else { return tmp; } } if(sector == 1){ if((m_2d[i+1][j+1] >= tmp) || (m_2d[i-1][j-1] > tmp)){ return 0; } else { return tmp; } } if(sector == 2){ if((m_2d[i][j+1] >= tmp) || (m_2d[i][j-1] > tmp)){ return 0; } else { return tmp; } } if(sector == 3){ if((m_2d[i+1][j-1] >= tmp) || (m_2d[i-1][j+1] > tmp)){ return 0; } else { return tmp; } } System.out.println("Canny - Unidentified sector "+sector+" at ij: "+i+" "+j); return 0; } /*The function apply_track is used to track the image for suitable lines. It *does this by first finding points above the highthreshold. When it finds *such a point it then finds surrounding point which are above the low threshold *and tracks along them, continually finding points above the low threshold. This *is done until the tracker explores all paths from the original point. It then *finds the next starting point and starts tracking again. */ private int [][] apply_track(int [][] input, int width, int height, int lowthresh,int highthresh) { d_w = width; d_h = height; int [][] marked = new int[d_w][d_h]; int [][] tracked = new int[d_w][d_h]; Stack to_track = new Stack(); //Initialise the marked array for(int i = 0; i < d_w; i++){ for(int j = 0; j < d_h; j++){ marked[i][j] = 0; } } //Now find all the starting points for the tracking for(int i = 0; i < d_w; i++){ for(int j = 0; j < d_h; j++){ //If the point is unmarked and above high threshold then track if((input[i][j] > highthresh) && (marked[i][j] == 0)){ marked = track(input, marked, to_track, lowthresh, i, j); } } } //Now clear all the pixels in the input which are unmarked for(int i = 0; i < d_w; i++){ for(int j = 0; j < d_h; j++){ if(marked[i][j] == 0){ tracked[i][j] = 0; } else { tracked[i][j] = input[i][j]; } } } return tracked; } /*The function track is called once a starting point for tracking has been *found. When this happens, this function follows all possible paths above *the threshold by placing unsearched paths on the stack. Each time a path *is looked at it's pixels are marked. This continues until the stack is *empty, at which point the new array of marked paths is returned. */ private int [][] track(int [][] input, int [][] marked, Stack to_track, int thresh, int i, int j){ //empty represents when the stack is empty boolean empty = false; int a; int b; //Create a point to represent where to start the tracking from Point current = new Point(i,j); //Push the initial point onto the stack to_track. push(current); while(!empty){ try{ //Take the top pixel from the stack current = (Point)to_track. pop(); //Find it's co-ordinates a = current.x; b = current.y; //Now check neighbourhood and add to stack anything above thresh //Only done if pixel is currently unmarked if(marked[a][b] == 0){ //Try and track from each neighbouring point if(a > 0 && b > 0){ if(input[a-1][b-1] > thresh){ current = new Point((a-1), (b-1)); to_track. push(current); } } if(b > 0){ if(input[a][b-1] > thresh){ current = new Point((a), (b-1)); to_track. push(current); } } if(a < (d_w-1) && b > 0){ if(input[a+1][b-1] > thresh){ current = new Point((a+1), (b-1)); to_track. push(current); } } if(a > 0){ if(input[a-1][b] > thresh){ current = new Point((a-1), (b)); to_track. push(current); } } if(a < (d_w-1)){ if(input[a+1][b] > thresh){ current = new Point((a+1), (b)); to_track. push(current); } } if(a > 0 && b < (d_h-1)){ if(input[a-1][b+1] > thresh){ current = new Point((a-1), (b+1)); to_track. push(current); } } if( b < (d_h-1)){ if(input[a][b+1] > thresh){ current = new Point((a), (b+1)); to_track. push(current); } } if(a < (d_w-1) && b < (d_h-1)){ if(input[a+1][b+1] > thresh){ current = new Point((a+1), (b+1)); to_track. push(current); } } //Mark this pixel as having been tracked from marked[a][b] = 1; } } //If stack empty, then set the empty flag to true catch(EmptyStackException e){ empty = true; } } return marked; } }