/******************************************************************************* * PROGRAM: canny_edge * PURPOSE: This program implements a "Canny" edge detector. The processing * steps are as follows: * * 1) Convolve the image with a separable gaussian filter. * 2) Take the dx and dy the first derivatives using [-1,0,1] and [1,0,-1]'. * 3) Compute the magnitude: sqrt(dx*dx+dy*dy). * 4) Perform non-maximal suppression. * 5) Perform hysteresis. * * The user must input three parameters. These are as follows: * * sigma = The standard deviation of the gaussian smoothing filter. * tlow = Specifies the low value to use in hysteresis. This is a * fraction (0-1) of the computed high threshold edge strength value. * thigh = Specifies the high value to use in hysteresis. This fraction (0-1) * specifies the percentage point in a histogram of the gradient of * the magnitude. Magnitude values of zero are not counted in the * histogram. * * NAME: Mike Heath * Computer Vision Laboratory * University of South Floeida * heath@csee.usf.edu * * DATE: 2/15/96 * * Modified: 5/17/96 - To write out a floating point RAW headerless file of * the edge gradient "up the edge" where the angle is * defined in radians counterclockwise from the x direction. * * COMPILE: Three 'C' source code files are needed to compile and run this edge * detection: "canny_edge.c", "hysteresis.c" and "pgm_io.c". They should * all be available in the same directory. To compile, type: * gcc -o canny_edge canny_edge.c hysteresis.c pgm_io.c -lm * (Note: You can also use optimization such as -O3) * USAGE: canny_edge image_filename sigma tlow thigh * where tlow < thigh are both fractions between 0.0 and 1.0. * Example: canny_edge panda.pgm 2 0.5 0.7 * The resulting edge image is "panda.pgm_s_2.00_l_0.50_h_0.70.pgm" *******************************************************************************/ #include #include #include #define VERBOSE 1 #define BOOSTBLURFACTOR 90.0 int read_pgm_image(char *infilename, unsigned char **image, int *rows, int *cols); int write_pgm_image(char *outfilename, unsigned char *image, int rows, int cols, char *comment, int maxval); void canny(unsigned char *image, int rows, int cols, float sigma, float tlow, float thigh, unsigned char **edge, char *fname); void gaussian_smooth(unsigned char *image, int rows, int cols, float sigma, short int **smoothedim); void make_gaussian_kernel(float sigma, float **kernel, int *windowsize); void derrivative_x_y(short int *smoothedim, int rows, int cols, short int **delta_x, short int **delta_y); void magnitude_x_y(short int *delta_x, short int *delta_y, int rows, int cols, short int **magnitude); void apply_hysteresis(short int *mag, unsigned char *nms, int rows, int cols, float tlow, float thigh, unsigned char *edge); void radian_direction(short int *delta_x, short int *delta_y, int rows, int cols, float **dir_radians, int xdirtag, int ydirtag); double angle_radians(double x, double y); main(int argc, char *argv[]) { char *infilename = NULL; /* Name of the input image */ char *dirfilename = NULL; /* Name of the output gradient direction image */ char outfilename[128]; /* Name of the output "edge" image */ char composedfname[128]; /* Name of the output "direction" image */ unsigned char *image; /* The input image */ unsigned char *edge; /* The output edge image */ int rows, cols; /* The dimensions of the image. */ float sigma, /* Standard deviation of the gaussian kernel. */ tlow, /* Fraction of the high threshold in hysteresis. */ thigh; /* High hysteresis threshold control. The actual threshold is the (100 * thigh) percentage point in the histogram of the magnitude of the gradient image that passes non-maximal suppression. */ /**************************************************************************** * Get the command line arguments. ****************************************************************************/ if(argc < 5){ fprintf(stderr,"\n %s image sigma tlow thigh [writedirim]\n",argv[0]); fprintf(stderr,"\n image: An image to process. Must be in "); fprintf(stderr,"PGM format.\n"); fprintf(stderr," sigma: Standard deviation of the gaussian"); fprintf(stderr," blur kernel.\n"); fprintf(stderr," tlow: Fraction (0.0-1.0) of the high "); fprintf(stderr,"edge strength threshold.\n"); fprintf(stderr," thigh: Fraction (0.0-1.0) of the distribution"); fprintf(stderr," of non-zero edge\n strengths for "); fprintf(stderr,"hysteresis. The fraction is used to compute\n"); fprintf(stderr," the high edge strength threshold.\n"); fprintf(stderr," writedirim: Optional argument to output "); fprintf(stderr,"a floating point"); fprintf(stderr," direction image.\n\n"); exit(1); } infilename = argv[1]; sigma = atof(argv[2]); tlow = atof(argv[3]); thigh = atof(argv[4]); if(argc == 6) dirfilename = infilename; else dirfilename = NULL; /**************************************************************************** * Read in the image. This read function allocates memory for the image. ****************************************************************************/ if (VERBOSE) printf("Reading the image %s.\n", infilename); if(read_pgm_image(infilename, &image, &rows, &cols) == 0){ fprintf(stderr, "Error reading the input image, %s.\n", infilename); exit(1); } /**************************************************************************** * Perform the edge detection. All of the work takes place here. ****************************************************************************/ if (VERBOSE) printf("Starting Canny edge detection.\n"); if(dirfilename != NULL){ sprintf(composedfname, "%s_s_%3.2f_l_%3.2f_h_%3.2f.fim", infilename, sigma, tlow, thigh); dirfilename = composedfname; } canny(image, rows, cols, sigma, tlow, thigh, &edge, dirfilename); /**************************************************************************** * Write out the edge image to a file. ****************************************************************************/ sprintf(outfilename, "%s_s_%3.2f_l_%3.2f_h_%3.2f.pgm", infilename, sigma, tlow, thigh); if (VERBOSE) printf("Writing the edge iname in the file %s.\n", outfilename); if(write_pgm_image(outfilename, edge, rows, cols, "", 255) == 0){ fprintf(stderr, "Error writing the edge image, %s.\n", outfilename); exit(1); } } /******************************************************************************* * PROCEDURE: canny * PURPOSE: To perform canny edge detection. * NAME: Mike Heath * DATE: 2/15/96 *******************************************************************************/ void canny(unsigned char *image, int rows, int cols, float sigma, float tlow, float thigh, unsigned char **edge, char *fname) { FILE *fpdir=NULL; /* File to write the gradient image to. */ unsigned char *nms; /* Points that are local maximal magnitude. */ short int *smoothedim, /* The image after gaussian smoothing. */ *delta_x, /* The first devivative image, x-direction. */ *delta_y, /* The first derivative image, y-direction. */ *magnitude; /* The magnitude of the gadient image. */ int r, c, pos; float *dir_radians=NULL; /* Gradient direction image. */ /**************************************************************************** * Perform gaussian smoothing on the image using the input standard * deviation. ****************************************************************************/ if (VERBOSE) printf("Smoothing the image using a gaussian kernel.\n"); gaussian_smooth(image, rows, cols, sigma, &smoothedim); /**************************************************************************** * Compute the first derivative in the x and y directions. ****************************************************************************/ if (VERBOSE) printf("Computing the X and Y first derivatives.\n"); derrivative_x_y(smoothedim, rows, cols, &delta_x, &delta_y); /**************************************************************************** * This option to write out the direction of the edge gradient was added * to make the information available for computing an edge quality figure * of merit. ****************************************************************************/ if(fname != NULL){ /************************************************************************* * Compute the direction up the gradient, in radians that are * specified counteclockwise from the positive x-axis. *************************************************************************/ radian_direction(delta_x, delta_y, rows, cols, &dir_radians, -1, -1); /************************************************************************* * Write the gradient direction image out to a file. *************************************************************************/ if((fpdir = fopen(fname, "wb")) == NULL){ fprintf(stderr, "Error opening the file %s for writing.\n", fname); exit(1); } fwrite(dir_radians, sizeof(float), rows*cols, fpdir); fclose(fpdir); free(dir_radians); } /**************************************************************************** * Compute the magnitude of the gradient. ****************************************************************************/ if (VERBOSE) printf("Computing the magnitude of the gradient.\n"); magnitude_x_y(delta_x, delta_y, rows, cols, &magnitude); /**************************************************************************** * Perform non-maximal suppression. ****************************************************************************/ if (VERBOSE) printf("Doing the non-maximal suppression.\n"); if((nms = (unsigned char *) calloc(rows*cols,sizeof(unsigned char)))==NULL){ fprintf(stderr, "Error allocating the nms image.\n"); exit(1); } non_max_supp(magnitude, delta_x, delta_y, rows, cols, nms); /**************************************************************************** * Use hysteresis to mark the edge pixels. ****************************************************************************/ if (VERBOSE) printf("Doing hysteresis thresholding.\n"); if((*edge=(unsigned char *)calloc(rows*cols,sizeof(unsigned char))) ==NULL){ fprintf(stderr, "Error allocating the edge image.\n"); exit(1); } apply_hysteresis(magnitude, nms, rows, cols, tlow, thigh, *edge); /**************************************************************************** * Free all of the memory that we allocated except for the edge image that * is still being used to store out result. ****************************************************************************/ free(smoothedim); free(delta_x); free(delta_y); free(magnitude); free(nms); } /******************************************************************************* * Procedure: radian_direction * Purpose: To compute a direction of the gradient image from component dx and * dy images. Because not all derriviatives are computed in the same way, this * code allows for dx or dy to have been calculated in different ways. * * FOR X: xdirtag = -1 for [-1 0 1] * xdirtag = 1 for [ 1 0 -1] * * FOR Y: ydirtag = -1 for [-1 0 1]' * ydirtag = 1 for [ 1 0 -1]' * * The resulting angle is in radians measured counterclockwise from the * xdirection. The angle points "up the gradient". *******************************************************************************/ void radian_direction(short int *delta_x, short int *delta_y, int rows, int cols, float **dir_radians, int xdirtag, int ydirtag) { int r, c, pos; float *dirim=NULL; double dx, dy; /**************************************************************************** * Allocate an image to store the direction of the gradient. ****************************************************************************/ if((dirim = (float *) calloc(rows*cols, sizeof(float))) == NULL){ fprintf(stderr, "Error allocating the gradient direction image.\n"); exit(1); } *dir_radians = dirim; for(r=0,pos=0;r= 0){ if(y >= 0) return(ang); else return(2*M_PI - ang); } else{ if(y >= 0) return(M_PI - ang); else return(M_PI + ang); } } /******************************************************************************* * PROCEDURE: magnitude_x_y * PURPOSE: Compute the magnitude of the gradient. This is the square root of * the sum of the squared derivative values. * NAME: Mike Heath * DATE: 2/15/96 *******************************************************************************/ void magnitude_x_y(short int *delta_x, short int *delta_y, int rows, int cols, short int **magnitude) { int r, c, pos, sq1, sq2; /**************************************************************************** * Allocate an image to store the magnitude of the gradient. ****************************************************************************/ if((*magnitude = (short *) calloc(rows*cols, sizeof(short))) == NULL){ fprintf(stderr, "Error allocating the magnitude image.\n"); exit(1); } for(r=0,pos=0;r= 0) && ((c+cc) < cols)){ dot += (float)image[r*cols+(c+cc)] * kernel[center+cc]; sum += kernel[center+cc]; } } tempim[r*cols+c] = dot/sum; } } /**************************************************************************** * Blur in the y - direction. ****************************************************************************/ if (VERBOSE) printf(" Bluring the image in the Y-direction.\n"); for(c=0;c= 0) && ((r+rr) < rows)){ dot += tempim[(r+rr)*cols+c] * kernel[center+rr]; sum += kernel[center+rr]; } } (*smoothedim)[r*cols+c] = (short int)(dot*BOOSTBLURFACTOR/sum + 0.5); } } free(tempim); free(kernel); } /******************************************************************************* * PROCEDURE: make_gaussian_kernel * PURPOSE: Create a one dimensional gaussian kernel. * NAME: Mike Heath * DATE: 2/15/96 *******************************************************************************/ void make_gaussian_kernel(float sigma, float **kernel, int *windowsize) { int i, center; float x, fx, sum=0.0; *windowsize = 1 + 2 * ceil(2.5 * sigma); center = (*windowsize) / 2; if (VERBOSE) printf(" The kernel has %d elements.\n", *windowsize); if((*kernel = (float *) calloc((*windowsize), sizeof(float))) == NULL){ fprintf(stderr, "Error callocing the gaussian kernel array.\n"); exit(1); } for(i=0;i<(*windowsize);i++){ x = (float)(i - center); fx = pow(2.71828, -0.5*x*x/(sigma*sigma)) / (sigma * sqrt(6.2831853)); (*kernel)[i] = fx; sum += fx; } for(i=0;i<(*windowsize);i++) (*kernel)[i] /= sum; if (VERBOSE){ printf("The filter coefficients are:\n"); for(i=0;i<(*windowsize);i++) printf("kernel[%d] = %f\n", i, (*kernel)[i]); } }