Canny edge detector

From Rosetta Code
Revision as of 17:16, 5 March 2012 by 87.224.129.185 (talk) (Created page with "{{task}}'''Task:''' Write a program that performs so called [http://en.wikipedia.org/wiki/Canny_edge_detector Canny edge detection] on an image. The algorithm consists of the...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Task
Canny edge detector
You are encouraged to solve this task according to the task description, using any language you may know.

Task: Write a program that performs so called Canny edge detection on an image.

The algorithm consists of the following steps:

1) Noise reduction. May be performed by Gaussian filter.

2) Compute intensity gradient (matrices and ) and its magnitude . May be performed by convolution of an image with Sobel operators.

3) Non-maximum suppression.

For each pixel compute the orientation of intensity gradient vector: . Transform angle to one of four directions: 0, 45, 90, 135 degrees. Compute new array : if

,

where - current pixel, - two neighbour pixels in the direction of gradient, then , otherwise . Nonzero pixels in resulting array correspond to local maxima of in direction .

4) Tracing edges with hysteresis.

At this stage two thresholds for the values of are introduced: and . Starting from pixels with find all paths of pixels with and put them to the resulting image.

C

The following program reads an 8 bits per pixel grayscale BMP file and saves the result to `out.bmp'. Compile with `-lm'. <lang c>

  1. include <stdint.h>
  2. include <stdio.h>
  3. include <stdlib.h>
  4. include <float.h>
  5. include <math.h>
  6. include <string.h>
  1. define MAX_BRIGHTNESS 255

/*

* Loading part taken from
* http://www.vbforums.com/showthread.php?t=261522
* BMP info:
* http://en.wikipedia.org/wiki/BMP_file_format
*
*/

/* Note: the magic number has been removed from the bmpfile_header structure

  since it causes alignment problems
    struct bmpfile_magic should be written/read first
  followed by the
    struct bmpfile_header
  [this avoids compiler-specific alignment pragmas etc.]
  • /

struct bmpfile_magic {

 unsigned char magic[2];

};

struct bmpfile_header {

 uint32_t filesz;
 uint16_t creator1;
 uint16_t creator2;
 uint32_t bmp_offset;

};

typedef struct {

 uint32_t header_sz;
 int32_t width;
 int32_t height;
 uint16_t nplanes;
 uint16_t bitspp;
 uint32_t compress_type;
 uint32_t bmp_bytesz;
 int32_t hres;
 int32_t vres;
 uint32_t ncolors;
 uint32_t nimpcolors;

} BITMAPINFOHEADER;

typedef struct { uint8_t r; uint8_t g; uint8_t b; uint8_t null; } rgb_t;

BITMAPINFOHEADER ih;

// Use int instead `unsigned char' so that // we can store negative values. typedef int pixel_t;

pixel_t *load_bmp(char *filename, BITMAPINFOHEADER *bitmapInfoHeader) { FILE *filePtr; //our file pointer struct bmpfile_magic mag; struct bmpfile_header bitmapFileHeader; //our bitmap file header pixel_t *bitmapImage; //store image data

filePtr = fopen(filename,"r"); if (filePtr == NULL) { perror("fopen()"); exit(1); }

fread(&mag, sizeof(struct bmpfile_magic),1,filePtr); //verify that this is a bmp file by check bitmap id if ( *((uint16_t*) mag.magic) != 0x4D42 ) { fprintf(stderr, "Not a BMP file: magic=%c%c\n", mag.magic[0], mag.magic[1]); fclose(filePtr); return NULL; } //read the bitmap file header fread(&bitmapFileHeader, sizeof(struct bmpfile_header), 1, filePtr);

//read the bitmap info header fread(bitmapInfoHeader, sizeof(BITMAPINFOHEADER), 1, filePtr);

if( bitmapInfoHeader->compress_type != 0) fprintf(stderr, "Warning, compression is not supported.\n");

//move file point to the beginning of bitmap data fseek(filePtr, bitmapFileHeader.bmp_offset, SEEK_SET);

//allocate enough memory for the bitmap image data bitmapImage = (pixel_t *)malloc(bitmapInfoHeader->bmp_bytesz*(sizeof(pixel_t)));

//verify memory allocation if (!bitmapImage) { free(bitmapImage); fclose(filePtr); return NULL; }

//read in the bitmap image data int i; unsigned char c; for(i=0; i<bitmapInfoHeader->bmp_bytesz; i++){ fread(&c, sizeof(unsigned char), 1, filePtr); bitmapImage[i] = (int) c; }

// If we were using unsigned char as pixel_t, then: //fread(bitmapImage, 1, bitmapInfoHeader->bmp_bytesz, filePtr);

//close file and return bitmap image data fclose(filePtr); return bitmapImage; }

// Return: nonzero on error. int save_bmp(char *filename, BITMAPINFOHEADER *bmp_ih, pixel_t *data) { unsigned int offset = sizeof(struct bmpfile_magic) + sizeof(struct bmpfile_header) + sizeof(BITMAPINFOHEADER) + (1<<bmp_ih->bitspp)*4;

struct bmpfile_header bmp_fh = { .filesz = offset + bmp_ih->bmp_bytesz, .creator1 = 0, .creator2 = 0, .bmp_offset = offset };

FILE* fp = fopen(filename,"w"); if (fp == NULL) return 1; struct bmpfile_magic mag = Template:0x42, 0x4d;

fwrite(&mag, 1, sizeof(struct bmpfile_magic), fp); fwrite(&bmp_fh, 1, sizeof(struct bmpfile_header), fp); fwrite(bmp_ih, 1, sizeof(BITMAPINFOHEADER), fp);

// Palette rgb_t color = {0, 0, 0, 0}; int i; for(i=0; i < (1<<bmp_ih->bitspp); i++) { color.r = i; color.g = i; color.b = i; fwrite(&color, 1, sizeof(rgb_t), fp); }

// We use int instead of uchar, so we can't write img in 1 call any more. //fwrite(data, 1, bmp_ih->bmp_bytesz, fp); unsigned char c; for(i=0; i<bmp_ih->bmp_bytesz; i++){ c = (unsigned char) data[i]; fwrite(&c, sizeof(unsigned char), 1, fp); }

fclose(fp); return 0; }

// if norm==1, map pixels to range 0..MAX_BRIGHTNESS void convolution(pixel_t *in, pixel_t *out, float *kernel, int nx, int ny, int kn, int norm) { int i, j, m, n, c; int khalf = floor(kn/2.); float pixel, min=DBL_MAX, max=DBL_MIN;

if(norm) for(m=khalf; m<nx-khalf; m++) for(n=khalf; n<ny-khalf; n++) { pixel = 0; c = 0; for(j=-khalf; j<=khalf; j++) for(i=-khalf; i<=khalf; i++) pixel += in[(n-j)*nx + m-i]*kernel[c++]; if(pixel < min) min = pixel; if(pixel > max) max = pixel; }

for(m=khalf; m<nx-khalf; m++) for(n=khalf; n<ny-khalf; n++) { pixel = 0; c = 0; for(j=-khalf; j<=khalf; j++) for(i=-khalf; i<=khalf; i++) pixel += in[(n-j)*nx + m-i]*kernel[c++];

if(norm) pixel = MAX_BRIGHTNESS*(pixel-min)/(max-min); out[n*nx + m] = (pixel_t) pixel; } }

// http://www.songho.ca/dsp/cannyedge/cannyedge.html // determine size of kernel (odd #) // 0.0 <= sigma < 0.5 : 3 // 0.5 <= sigma < 1.0 : 5 // 1.0 <= sigma < 1.5 : 7 // 1.5 <= sigma < 2.0 : 9 // 2.0 <= sigma < 2.5 : 11 // 2.5 <= sigma < 3.0 : 13 ... //kernelSize = 2 * int(2*sigma) + 3;

void gaussian_filter(pixel_t *in, pixel_t *out, int nx, int ny, float sigma) { int i, j, c=0; const int n = 2*(int)(2*sigma) + 3; float mean = floor(n/2.); float kernel[n*n]; fprintf(stderr, "gaussian_filter: kernel size %d, sigma=%g\n", n, sigma); for(i=0; i<n; i++) for(j=0; j<n; j++) kernel[c++]=exp(-0.5*( pow((i-mean)/sigma,2.0) +pow((j-mean)/sigma,2.0))) /(2*M_PI*sigma*sigma); convolution(in, out, kernel, nx, ny, n, 1); }

/*

* Links:
* http://en.wikipedia.org/wiki/Canny_edge_detector
* http://www.tomgibara.com/computer-vision/CannyEdgeDetector.java
* http://fourier.eng.hmc.edu/e161/lectures/canny/node1.html
* http://www.songho.ca/dsp/cannyedge/cannyedge.html
*
* Note: T1 and T2 are lower and upper thresholds.
*/

void canny_edge_detection(pixel_t *in, pixel_t *out, int nx, int ny, int tmin, int tmax, float sigma) { int i, j, c, Gmax; int counter = 0; float dir; float Gx[]={ -1,0,1, -2,0,2, -1,0,1}; float Gy[]={ 1,2,1, 0,0,0, -1,-2,-1};

pixel_t *G = calloc(nx*ny*sizeof(pixel_t), 1), *after_Gx = calloc(nx*ny*sizeof(pixel_t), 1), *after_Gy = calloc(nx*ny*sizeof(pixel_t), 1), *nms = calloc(nx*ny*sizeof(pixel_t), 1);

gaussian_filter(in, out, nx, ny, sigma);

convolution(out, after_Gx, Gx, nx, ny, 3, 0); convolution(out, after_Gy, Gy, nx, ny, 3, 0);

for(i=1; i<nx-1; i++) for(j=1; j<ny-1; j++) { c = i + nx*j; //G[c] = abs(after_Gx[c]) + abs(after_Gy[c]); G[c] = hypot(after_Gx[c], after_Gy[c]); if (G[c]>Gmax ) Gmax = G[c]; }

int nn,ss,ww,ee,nw,ne,sw,se; // Non-maximum suppression, straightforward implementation. for(i=1; i<nx-1; i++) for(j=1; j<ny-1; j++) { c = i + nx*j; nn = c - nx; ss = c + nx; ww = c + 1; ee = c - 1; nw = nn + 1; ne = nn - 1; sw = ss + 1; se = ss - 1;

dir = atan2(after_Gy[c], after_Gx[c]) * 8.0/M_PI;

if( (( fabs(dir)<=1 || fabs(fabs(dir)-8)<=1 ) && G[c] > G[ee] && G[c] > G[ww]) /* 0 deg */ || (( fabs(dir-2)<=1 || fabs(dir+6)<=1 ) && G[c] > G[nw] && G[c] > G[se]) /* 45 deg */ || (( fabs(fabs(fabs(dir)-4))<=1 ) && G[c] > G[nn] && G[c] > G[ss]) /* 90 deg */ || (( fabs(dir-6)<=1 || fabs(dir+2)<=1 ) && G[c] > G[ne] && G[c] > G[sw]) /* 135 deg */ ) nms[c] = G[c]; else nms[c] = 0; } // Reuse array pixel_t *edges = after_Gy; // used as a stack memset(out, 0, sizeof(pixel_t)*nx*ny); memset(edges, 0, sizeof(pixel_t)*nx*ny); int nedges, k, t; int nbs[8]; // neighbours

counter = 0; // Tracing edges with hysteresis . Non-recursive implementation. for(c=1, j=1; j<ny-1; j++) for(i=1; i<nx-1; i++) { if( nms[c] >= tmax && out[c] == 0 ) // trace edges { out[c] = MAX_BRIGHTNESS; nedges = 1; edges[0] = c;

do{ nedges--; t = edges[ nedges ];

nbs[0] = t - nx; // nn nbs[1] = t + nx; // ss nbs[2] = t + 1; // ww nbs[3] = t - 1; // ee nbs[4] = nbs[0] + 1; // nw nbs[5] = nbs[0] - 1; // ne nbs[6] = nbs[1] + 1; // sw nbs[7] = nbs[1] - 1; // se

for(k=0; k<8; k++) if( nms[ nbs[k] ] >= tmin && out[ nbs[k] ] == 0 ) { out[ nbs[k] ] = MAX_BRIGHTNESS; edges[nedges++] = nbs[k]; } } while( nedges > 0 ); } c++; } free(after_Gx); free(after_Gy); free(G); free(nms); }

int main(int argc, char **argv) { if(argc < 2){ printf("Usage: %s image.bmp\n", argv[0]); exit(1); } pixel_t *bitmap_data, *temp_image;

bitmap_data = load_bmp(argv[1], &ih); temp_image = (pixel_t *) malloc(ih.bmp_bytesz*sizeof(pixel_t));

printf("Info: %d x %d x %d\n", ih.width, ih.height, ih.bitspp);

canny_edge_detection(bitmap_data, temp_image, ih.width, ih.height, 45, 50, 1);

save_bmp("out.bmp", &ih, temp_image); free(bitmap_data); free(temp_image); return 0; } </lang>