Canny edge detector: Difference between revisions

From Rosetta Code
Content added Content deleted
(Fixed links in C entry)
(The C entry already uses C99 features like variable length array, so now uses C99 better)
Line 6: Line 6:
# '''Tracing edges with hysteresis.''' At this stage two thresholds for the values of <math>G</math> are introduced: <math>T_{min}</math> and <math>T_{max}</math>. Starting from pixels with <math>N(p) \geqslant T_{max}</math> find all paths of pixels with <math>N(p) \geqslant T_{min}</math> and put them to the resulting image.
# '''Tracing edges with hysteresis.''' At this stage two thresholds for the values of <math>G</math> are introduced: <math>T_{min}</math> and <math>T_{max}</math>. Starting from pixels with <math>N(p) \geqslant T_{max}</math> find all paths of pixels with <math>N(p) \geqslant T_{min}</math> and put them to the resulting image.


=={{header|C}}==
=={{header|C99}}==
The following program reads an 8 bits per pixel grayscale [[wp:BMP file format|BMP]] file and saves the result to `out.bmp'. Compile with `-lm'.
The following program reads an 8 bits per pixel grayscale [[wp:BMP file format|BMP]] file and saves the result to `out.bmp'. Compile with `-lm'.
<lang c>#include <stdint.h>
<lang c>#include <stdint.h>
Line 14: Line 14:
#include <math.h>
#include <math.h>
#include <string.h>
#include <string.h>
#include <stdbool.h>


#define MAX_BRIGHTNESS 255
#define MAX_BRIGHTNESS 255
#define M_PI 3.14159265358979323846264338327


/*
/*
Line 23: Line 25:
* http://en.wikipedia.org/wiki/BMP_file_format
* http://en.wikipedia.org/wiki/BMP_file_format
*
*
* Note: the magic number has been removed from the bmpfile_header_t structure
* Note: the magic number has been removed from the bmpfile_header_t
* since it causes alignment problems
* structure since it causes alignment problems
* bmpfile_magic_t should be written/read first
* bmpfile_magic_t should be written/read first
* followed by the
* followed by the
Line 32: Line 34:


typedef struct {
typedef struct {
unsigned char magic[2];
uint8_t magic[2];
} bmpfile_magic_t;
} bmpfile_magic_t;


Line 63: Line 65:
} rgb_t;
} rgb_t;


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


pixel_t *load_bmp(const char *filename, bitmap_info_header_t *bitmapInfoHeader)
pixel_t *load_bmp(const char *filename,
bitmap_info_header_t *bitmapInfoHeader)
{
{
FILE *filePtr; // our file pointer
FILE *filePtr = fopen(filename, "rb");
bmpfile_magic_t mag;
bmpfile_header_t bitmapFileHeader; // our bitmap file header
pixel_t *bitmapImage; // store image data
size_t i;
unsigned char c;

filePtr = fopen(filename, "rb");
if (filePtr == NULL) {
if (filePtr == NULL) {
perror("fopen()");
perror("fopen()");
Line 81: Line 78:
}
}


bmpfile_magic_t mag;
if (fread(&mag, sizeof(bmpfile_magic_t), 1, filePtr) != 1) {
if (fread(&mag, sizeof(bmpfile_magic_t), 1, filePtr) != 1) {
fclose(filePtr);
fclose(filePtr);
Line 90: Line 88:
// strict-aliasing rules [-Wstrict-aliasing]
// strict-aliasing rules [-Wstrict-aliasing]
if (*((uint16_t*)mag.magic) != 0x4D42) {
if (*((uint16_t*)mag.magic) != 0x4D42) {
fprintf(stderr, "Not a BMP file: magic=%c%c\n", mag.magic[0], mag.magic[1]);
fprintf(stderr, "Not a BMP file: magic=%c%c\n",
mag.magic[0], mag.magic[1]);
fclose(filePtr);
fclose(filePtr);
return NULL;
return NULL;
}
}



bmpfile_header_t bitmapFileHeader; // our bitmap file header
// read the bitmap file header
// read the bitmap file header
if (fread(&bitmapFileHeader, sizeof(bmpfile_header_t), 1, filePtr) != 1) {
if (fread(&bitmapFileHeader, sizeof(bmpfile_header_t),
1, filePtr) != 1) {
fclose(filePtr);
fclose(filePtr);
return NULL;
return NULL;
Line 102: Line 104:


// read the bitmap info header
// read the bitmap info header
if (fread(bitmapInfoHeader, sizeof(bitmap_info_header_t), 1, filePtr) != 1) {
if (fread(bitmapInfoHeader, sizeof(bitmap_info_header_t),
1, filePtr) != 1) {
fclose(filePtr);
fclose(filePtr);
return NULL;
return NULL;
}
}



if (bitmapInfoHeader->compress_type != 0)
if (bitmapInfoHeader->compress_type != 0)
Line 115: Line 117:


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


// verify memory allocation
// verify memory allocation
Line 124: Line 127:


// read in the bitmap image data
// read in the bitmap image data
for (i = 0; i < bitmapInfoHeader->bmp_bytesz; i++) {
for (size_t i = 0; i < bitmapInfoHeader->bmp_bytesz; i++) {
unsigned char c;
if (fread(&c, sizeof(unsigned char), 1, filePtr) != 1) {
if (fread(&c, sizeof(unsigned char), 1, filePtr) != 1) {
fclose(filePtr);
fclose(filePtr);
Line 144: Line 148:
const pixel_t *data)
const pixel_t *data)
{
{
FILE* fp = fopen(filename, "wb");
uint32_t offset = sizeof(bmpfile_magic_t) +
if (fp == NULL)
sizeof(bmpfile_header_t) +
return 1;
sizeof(bitmap_info_header_t) +

((1U << bmp_ih->bitspp) * 4);
bmpfile_magic_t mag = {{0x42, 0x4d}};
fwrite(&mag, 1, sizeof(bmpfile_magic_t), fp);

const uint32_t offset = sizeof(bmpfile_magic_t) +
sizeof(bmpfile_header_t) +
sizeof(bitmap_info_header_t) +
((1U << bmp_ih->bitspp) * 4);


bmpfile_header_t bmp_fh = {
const bmpfile_header_t bmp_fh = {
.filesz = offset + bmp_ih->bmp_bytesz,
.filesz = offset + bmp_ih->bmp_bytesz,
.creator1 = 0,
.creator1 = 0,
Line 156: Line 167:
};
};


bmpfile_magic_t mag = {{0x42, 0x4d}};
rgb_t color = {0, 0, 0, 0};
size_t i;
FILE* fp = fopen(filename, "wb");

if (fp == NULL)
return 1;

fwrite(&mag, 1, sizeof(bmpfile_magic_t), fp);
fwrite(&bmp_fh, 1, sizeof(bmpfile_header_t), fp);
fwrite(&bmp_fh, 1, sizeof(bmpfile_header_t), fp);
fwrite(bmp_ih, 1, sizeof(bitmap_info_header_t), fp);
fwrite(bmp_ih, 1, sizeof(bitmap_info_header_t), fp);


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


// We use int instead of uchar, so we can't write img in 1 call any more.
// 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);
// fwrite(data, 1, bmp_ih->bmp_bytesz, fp);
for (i = 0; i < bmp_ih->bmp_bytesz; i++) {
for (size_t i = 0; i < bmp_ih->bmp_bytesz; i++) {
unsigned char c = (unsigned char)data[i];
unsigned char c = (unsigned char)data[i];
fwrite(&c, sizeof(unsigned char), 1, fp);
fwrite(&c, sizeof(unsigned char), 1, fp);
Line 189: Line 190:
// if norm==1, map pixels to range 0..MAX_BRIGHTNESS
// if norm==1, map pixels to range 0..MAX_BRIGHTNESS
void convolution(const pixel_t *in, pixel_t *out, const float *kernel,
void convolution(const pixel_t *in, pixel_t *out, const float *kernel,
const int nx, const int ny, const int kn, const int norm)
const int nx, const int ny, const int kn,
const bool norm)
{
{
int i, j, m, n, c;
const int khalf = (int)floor(kn / 2.0);
const int khalf = (int)floor(kn / 2.0);
float pixel, min = FLT_MAX, max = FLT_MIN;
float min = FLT_MAX, max = FLT_MIN;


if (norm)
if (norm)
for (m = khalf; m < nx - khalf; m++)
for (int m = khalf; m < nx - khalf; m++)
for (n = khalf; n < ny - khalf; n++) {
for (int n = khalf; n < ny - khalf; n++) {
pixel = 0.0;
float pixel = 0.0;
c = 0;
int c = 0;
for (j = -khalf; j <= khalf; j++)
for (int j = -khalf; j <= khalf; j++)
for (i = -khalf; i <= khalf; i++)
for (int i = -khalf; i <= khalf; i++) {
pixel += in[(n - j) * nx + m - i] * kernel[c++];
pixel += in[(n - j) * nx + m - i] * kernel[c];
c++;
}
if (pixel < min)
if (pixel < min)
min = pixel;
min = pixel;
Line 209: Line 212:
}
}


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


if (norm)
if (norm)
Line 236: Line 241:
const int nx, const int ny, const float sigma)
const int nx, const int ny, const float sigma)
{
{
int i, j, c = 0;
const int n = 2 * (int)(2 * sigma) + 3;
const int n = 2 * (int)(2 * sigma) + 3;
float mean = (float)floor(n / 2.0);
const float mean = (float)floor(n / 2.0);
float kernel[n * n]; // variable length array
float kernel[n * n]; // variable length array


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

convolution(in, out, kernel, nx, ny, n, true);
}
}


Line 258: Line 268:
* Note: T1 and T2 are lower and upper thresholds.
* Note: T1 and T2 are lower and upper thresholds.
*/
*/
pixel_t *canny_edge_detection(const pixel_t *in, const bitmap_info_header_t *bmp_ih,
pixel_t *canny_edge_detection(const pixel_t *in,
const int tmin, const int tmax, const float sigma)
const bitmap_info_header_t *bmp_ih,
const int tmin, const int tmax,
const float sigma)
{
{
int i, j, c, Gmax;

const float Gx[] = {-1, 0, 1,
-2, 0, 2,
-1, 0, 1};

const float Gy[] = { 1, 2, 1,
0, 0, 0,
-1,-2,-1};

const int nx = bmp_ih->width;
const int nx = bmp_ih->width;
const int ny = bmp_ih->height;
const int ny = bmp_ih->height;
Line 279: Line 281:
pixel_t *nms = calloc(nx * ny * sizeof(pixel_t), 1);
pixel_t *nms = calloc(nx * ny * sizeof(pixel_t), 1);
pixel_t *out = malloc(bmp_ih->bmp_bytesz * sizeof(pixel_t));
pixel_t *out = malloc(bmp_ih->bmp_bytesz * sizeof(pixel_t));
pixel_t *edges;
int nedges, k, t;
int nbs[8]; // neighbours


if (!G || !after_Gx || !after_Gy || !nms || !out)
if (!G || !after_Gx || !after_Gy || !nms || !out)
Line 287: Line 286:


gaussian_filter(in, out, nx, ny, sigma);
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);


Gmax = 0;
const float Gx[] = {-1, 0, 1,
for (i = 1; i < nx - 1; i++)
-2, 0, 2,
for (j = 1; j < ny - 1; j++) {
-1, 0, 1};

c = i + nx * j;
const float Gy[] = { 1, 2, 1,
0, 0, 0,
-1,-2,-1};

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

int Gmax = 0;
for (int i = 1; i < nx - 1; i++)
for (int j = 1; j < ny - 1; j++) {
const int c = i + nx * j;
// G[c] = abs(after_Gx[c]) + abs(after_Gy[c]);
// G[c] = abs(after_Gx[c]) + abs(after_Gy[c]);
G[c] = (pixel_t)hypot(after_Gx[c], after_Gy[c]);
G[c] = (pixel_t)hypot(after_Gx[c], after_Gy[c]);
Line 301: Line 309:


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

c = i + nx * j;
const int ww = c + 1;
nn = c - nx;
const int ee = c - 1;
ss = c + nx;
const int nw = nn + 1;
ww = c + 1;
const int ne = nn - 1;
ee = c - 1;
const int sw = ss + 1;
nw = nn + 1;
const int se = ss - 1;
ne = nn - 1;
sw = ss + 1;
se = ss - 1;


dir = (float)(fmod(atan2(after_Gy[c], after_Gx[c]) + M_PI, M_PI) / M_PI) * 8;
const float dir = (float)(fmod(atan2(after_Gy[c],
after_Gx[c]) + M_PI,
M_PI) / M_PI) * 8;


if (((dir <= 1 || dir > 7) && G[c] > G[ee] && G[c] > G[ww]) || // 0 deg
if (((dir <= 1 || dir > 7) && G[c] > G[ee] &&
((dir > 1 && dir <= 3) && G[c] > G[nw] && G[c] > G[se]) || // 45 deg
G[c] > G[ww]) || // 0 deg
((dir > 3 && dir <= 5) && G[c] > G[nn] && G[c] > G[ss]) || // 90 deg
((dir > 1 && dir <= 3) && G[c] > G[nw] &&
((dir > 5 && dir <= 7) && G[c] > G[ne] && G[c] > G[sw])) // 135 deg
G[c] > G[se]) || // 45 deg
((dir > 3 && dir <= 5) && G[c] > G[nn] &&
G[c] > G[ss]) || // 90 deg
((dir > 5 && dir <= 7) && G[c] > G[ne] &&
G[c] > G[sw])) // 135 deg
nms[c] = G[c];
nms[c] = G[c];
else
else
Line 328: Line 339:


// Reuse array
// Reuse array
edges = after_Gy; // used as a stack
pixel_t *edges = after_Gy; // used as a stack
memset(out, 0, sizeof(pixel_t) * nx * ny);
memset(out, 0, sizeof(pixel_t) * nx * ny);
memset(edges, 0, sizeof(pixel_t) * nx * ny);
memset(edges, 0, sizeof(pixel_t) * nx * ny);


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


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


int nbs[8]; // neighbours
nbs[0] = t - nx; // nn
nbs[0] = t - nx; // nn
nbs[1] = t + nx; // ss
nbs[1] = t + nx; // ss
Line 353: Line 366:
nbs[7] = nbs[1] - 1; // se
nbs[7] = nbs[1] - 1; // se


for (k = 0; k < 8; k++)
for (int k = 0; k < 8; k++)
if (nms[nbs[k]] >= tmin && out[nbs[k]] == 0) {
if (nms[nbs[k]] >= tmin && out[nbs[k]] == 0) {
out[nbs[k]] = MAX_BRIGHTNESS;
out[nbs[k]] = MAX_BRIGHTNESS;
edges[nedges++] = nbs[k];
edges[nedges] = nbs[k];
nedges++;
}
}
} while (nedges > 0);
} while (nedges > 0);
Line 373: Line 387:
int main(const int argc, const char ** const argv)
int main(const int argc, const char ** const argv)
{
{
pixel_t *in_bitmap_data, *out_bitmap_data;
static bitmap_info_header_t ih;

if (argc < 2) {
if (argc < 2) {
printf("Usage: %s image.bmp\n", argv[0]);
printf("Usage: %s image.bmp\n", argv[0]);
Line 381: Line 392:
}
}


static bitmap_info_header_t ih;
in_bitmap_data = load_bmp(argv[1], &ih);
const pixel_t *in_bitmap_data = load_bmp(argv[1], &ih);
if (in_bitmap_data == NULL)
if (in_bitmap_data == NULL)
return 1;
return 1;
Line 387: Line 399:
printf("Info: %d x %d x %d\n", ih.width, ih.height, ih.bitspp);
printf("Info: %d x %d x %d\n", ih.width, ih.height, ih.bitspp);


out_bitmap_data = canny_edge_detection(in_bitmap_data, &ih, 45, 50, 1.0f);
const pixel_t *out_bitmap_data =
canny_edge_detection(in_bitmap_data, &ih, 45, 50, 1.0f);
if (out_bitmap_data == NULL)
if (out_bitmap_data == NULL)
return 1;
return 1;
Line 394: Line 407:
return 1;
return 1;


free(in_bitmap_data);
free((pixel_t*)in_bitmap_data);
free(out_bitmap_data);
free((pixel_t*)out_bitmap_data);

return 0;
return 0;
}</lang>
}</lang>

Revision as of 17:19, 10 March 2012

Canny edge detector is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

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 is the current pixel, and are the 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.

C99

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

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

/*

* 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_t
* structure since it causes alignment problems
*     bmpfile_magic_t should be written/read first
* followed by the
*     bmpfile_header_t
* [this avoids compiler-specific alignment pragmas etc.]
*/

typedef struct {

   uint8_t magic[2];

} bmpfile_magic_t;

typedef struct {

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

} bmpfile_header_t;

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;

} bitmap_info_header_t;

typedef struct {

   uint8_t r;
   uint8_t g;
   uint8_t b;
   uint8_t nothing;

} rgb_t;

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

pixel_t *load_bmp(const char *filename,

                 bitmap_info_header_t *bitmapInfoHeader)

{

   FILE *filePtr = fopen(filename, "rb");
   if (filePtr == NULL) {
       perror("fopen()");
       return NULL;
   }
   bmpfile_magic_t mag;
   if (fread(&mag, sizeof(bmpfile_magic_t), 1, filePtr) != 1) {
       fclose(filePtr);
       return NULL;
   }
   // verify that this is a bmp file by check bitmap id
   // warning: dereferencing type-punned pointer will break
   // strict-aliasing rules [-Wstrict-aliasing]
   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;
   }


   bmpfile_header_t bitmapFileHeader; // our bitmap file header
   // read the bitmap file header
   if (fread(&bitmapFileHeader, sizeof(bmpfile_header_t),
             1, filePtr) != 1) {
       fclose(filePtr);
       return NULL;
   }
   // read the bitmap info header
   if (fread(bitmapInfoHeader, sizeof(bitmap_info_header_t),
             1, filePtr) != 1) {
       fclose(filePtr);
       return NULL;
   }
   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
   pixel_t *bitmapImage = (pixel_t*)malloc(bitmapInfoHeader->bmp_bytesz *
                                           sizeof(pixel_t));
   // verify memory allocation
   if (bitmapImage == NULL) {
       fclose(filePtr);
       return NULL;
   }
   // read in the bitmap image data
   for (size_t i = 0; i < bitmapInfoHeader->bmp_bytesz; i++) {
       unsigned char c;
       if (fread(&c, sizeof(unsigned char), 1, filePtr) != 1) {
           fclose(filePtr);
           return NULL;
       }
       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(const char *filename, const bitmap_info_header_t *bmp_ih,

            const pixel_t *data)

{

   FILE* fp = fopen(filename, "wb");
   if (fp == NULL)
       return 1;
   bmpfile_magic_t mag = Template:0x42, 0x4d;
   fwrite(&mag, 1, sizeof(bmpfile_magic_t), fp);
   const uint32_t offset = sizeof(bmpfile_magic_t) +
                           sizeof(bmpfile_header_t) +
                           sizeof(bitmap_info_header_t) +
                           ((1U << bmp_ih->bitspp) * 4);
   const bmpfile_header_t bmp_fh = {
       .filesz = offset + bmp_ih->bmp_bytesz,
       .creator1 = 0,
       .creator2 = 0,
       .bmp_offset = offset
   };
   fwrite(&bmp_fh, 1, sizeof(bmpfile_header_t), fp);
   fwrite(bmp_ih, 1, sizeof(bitmap_info_header_t), fp);
   // Palette
   for (size_t i = 0; i < (1U << bmp_ih->bitspp); i++) {
       const rgb_t color = {(uint8_t)i, (uint8_t)i, (uint8_t)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);
   for (size_t i = 0; i < bmp_ih->bmp_bytesz; i++) {
       unsigned char 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(const pixel_t *in, pixel_t *out, const float *kernel,

                const int nx, const int ny, const int kn,
                const bool norm)

{

   const int khalf = (int)floor(kn / 2.0);
   float min = FLT_MAX, max = FLT_MIN;
   if (norm)
       for (int m = khalf; m < nx - khalf; m++)
           for (int n = khalf; n < ny - khalf; n++) {
               float pixel = 0.0;
               int c = 0;
               for (int j = -khalf; j <= khalf; j++)
                   for (int i = -khalf; i <= khalf; i++) {
                       pixel += in[(n - j) * nx + m - i] * kernel[c];
                       c++;
                   }
               if (pixel < min)
                   min = pixel;
               if (pixel > max)
                   max = pixel;
               }
   for (int m = khalf; m < nx - khalf; m++)
       for (int n = khalf; n < ny - khalf; n++) {
           float pixel = 0.0;
           int c = 0;
           for (int j = -khalf; j <= khalf; j++)
               for (int i = -khalf; i <= khalf; i++) {
                   pixel += in[(n - j) * nx + m - i] * kernel[c];
                   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(const pixel_t *in, pixel_t *out,

                    const int nx, const int ny, const float sigma)

{

   const int n = 2 * (int)(2 * sigma) + 3;
   const float mean = (float)floor(n / 2.0);
   float kernel[n * n]; // variable length array
   fprintf(stderr, "gaussian_filter: kernel size %d, sigma=%g\n",
           n, sigma);
   int c = 0;
   for (int i = 0; i < n; i++)
       for (int 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);
           c++;
       }
   convolution(in, out, kernel, nx, ny, n, true);

}

/*

* 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.
*/

pixel_t *canny_edge_detection(const pixel_t *in,

                             const bitmap_info_header_t *bmp_ih,
                             const int tmin, const int tmax,
                             const float sigma)

{

   const int nx = bmp_ih->width;
   const int ny = bmp_ih->height;
   pixel_t *G = calloc(nx * ny * sizeof(pixel_t), 1);
   pixel_t *after_Gx = calloc(nx * ny * sizeof(pixel_t), 1);
   pixel_t *after_Gy = calloc(nx * ny * sizeof(pixel_t), 1);
   pixel_t *nms = calloc(nx * ny * sizeof(pixel_t), 1);
   pixel_t *out = malloc(bmp_ih->bmp_bytesz * sizeof(pixel_t));
   if (!G || !after_Gx || !after_Gy || !nms || !out)
       exit(1);
   gaussian_filter(in, out, nx, ny, sigma);
   const float Gx[] = {-1, 0, 1,
                       -2, 0, 2,
                       -1, 0, 1};
   const float Gy[] = { 1, 2, 1,
                        0, 0, 0,
                       -1,-2,-1};
   convolution(out, after_Gx, Gx, nx, ny, 3, false);
   convolution(out, after_Gy, Gy, nx, ny, 3, false);
   int Gmax = 0;
   for (int i = 1; i < nx - 1; i++)
       for (int j = 1; j < ny - 1; j++) {
           const int c = i + nx * j;
           // G[c] = abs(after_Gx[c]) + abs(after_Gy[c]);
           G[c] = (pixel_t)hypot(after_Gx[c], after_Gy[c]);
           if (G[c] > Gmax)
               Gmax = G[c];
       }
   // Non-maximum suppression, straightforward implementation.
   for (int i = 1; i < nx - 1; i++)
       for (int j = 1; j < ny - 1; j++) {
           const int c = i + nx * j;
           const int nn = c - nx;
           const int ss = c + nx;
           const int ww = c + 1;
           const int ee = c - 1;
           const int nw = nn + 1;
           const int ne = nn - 1;
           const int sw = ss + 1;
           const int se = ss - 1;
           const float dir = (float)(fmod(atan2(after_Gy[c],
                                                after_Gx[c]) + M_PI,
                                          M_PI) / M_PI) * 8;
           if (((dir <= 1 || dir > 7) && G[c] > G[ee] &&
                G[c] > G[ww]) || // 0 deg
               ((dir > 1 && dir <= 3) && G[c] > G[nw] &&
                G[c] > G[se]) || // 45 deg
               ((dir > 3 && dir <= 5) && G[c] > G[nn] &&
                G[c] > G[ss]) || // 90 deg
               ((dir > 5 && dir <= 7) && 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);
   // Tracing edges with hysteresis . Non-recursive implementation.
   int c = 1;
   for (int j = 1; j < ny - 1; j++)
       for (int i = 1; i < nx - 1; i++) {
           if (nms[c] >= tmax && out[c] == 0) { // trace edges
               out[c] = MAX_BRIGHTNESS;
               int nedges = 1;
               edges[0] = c;
               do {
                   nedges--;
                   const int t = edges[nedges];
                   int nbs[8]; // neighbours
                   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 (int k = 0; k < 8; k++)
                       if (nms[nbs[k]] >= tmin && out[nbs[k]] == 0) {
                           out[nbs[k]] = MAX_BRIGHTNESS;
                           edges[nedges] = nbs[k];
                           nedges++;
                       }
               } while (nedges > 0);
           }
           c++;
       }
   free(after_Gx);
   free(after_Gy);
   free(G);
   free(nms);
   return out;

}

int main(const int argc, const char ** const argv) {

   if (argc < 2) {
       printf("Usage: %s image.bmp\n", argv[0]);
       return 1;
   }
   static bitmap_info_header_t ih;
   const pixel_t *in_bitmap_data = load_bmp(argv[1], &ih);
   if (in_bitmap_data == NULL)
       return 1;
   printf("Info: %d x %d x %d\n", ih.width, ih.height, ih.bitspp);
   const pixel_t *out_bitmap_data =
       canny_edge_detection(in_bitmap_data, &ih, 45, 50, 1.0f);
   if (out_bitmap_data == NULL)
       return 1;
   if (save_bmp("out.bmp", &ih, out_bitmap_data) != 0)
       return 1;
   free((pixel_t*)in_bitmap_data);
   free((pixel_t*)out_bitmap_data);
   return 0;

}</lang>

D

Translation of: C

This version uses C I/O functions and retains some of the style of the original C version. <lang d>import core.stdc.stdio: printf, fclose, fwrite, FILE, fopen, fread,

                       fprintf, perror, stderr, fseek, SEEK_SET;

import std.math: PI, floor, exp, pow, hypot, fmod, atan2;

enum 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 BMPfileHeader structure since it causes alignment problems

   BMPfileMagic should be written/read first

followed by the

   BMPfileHeader

[this avoids compiler-specific alignment pragmas etc.]

*/

struct BMPfileMagic {

   ubyte[2] magic;

}

struct BMPfileHeader {

   uint filesz;
   ushort creator1;
   ushort creator2;
   uint bmp_offset;

}

struct BitmapInfoHeader {

   uint header_sz;
   int width;
   int height;
   ushort nplanes;
   ushort bitspp;
   uint compress_type;
   uint bmp_bytesz;
   int hres;
   int vres;
   uint ncolors;
   uint nimpcolors;

}

struct Rgb {

   ubyte r;
   ubyte g;
   ubyte b;
   ubyte nothing;

}

// Use int instead `ubyte' so that we can store negative values. alias int Pixel;

Pixel[] loadBMP(in string fileName, ref BitmapInfoHeader bitmapInfoHeader) nothrow {

   auto filePtr = fopen((fileName ~ "\0").ptr, "rb");
   if (filePtr == null) {
       perror("fopen()");
       return null;
   }
   BMPfileMagic mag;
   if (fread(&mag, BMPfileMagic.sizeof, 1, filePtr) != 1) {
       fclose(filePtr);
       return null;
   }
   // verify that this is a bmp file by check bitmap id
   if (*(cast(ushort*)(mag.magic.ptr)) != 0x4D42) {
       fprintf(stderr, "Not a BMP file: magic=%c%c\n", mag.magic[0], mag.magic[1]);
       fclose(filePtr);
       return null;
   }
   BMPfileHeader bitmapFileHeader; // our bitmap file header
   // read the bitmap file header
   if (fread(&bitmapFileHeader, BMPfileHeader.sizeof, 1, filePtr) != 1) {
       fclose(filePtr);
       return null;
   }
   // read the bitmap info header
   if (fread(&bitmapInfoHeader, BitmapInfoHeader.sizeof, 1, filePtr) != 1) {
       fclose(filePtr);
       return null;
   }
   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
   auto bitmapImage = new Pixel[bitmapInfoHeader.bmp_bytesz];
   // read in the bitmap image data
   foreach (ref bi; bitmapImage) {
       ubyte c = void;
       if (fread(&c, ubyte.sizeof, 1, filePtr) != 1) {
           fclose(filePtr);
           return null;
       }
       bi = c;
   }
   // If we were using ubyte as Pixel, then:
   // fread(bitmapImage, 1, bitmapInfoHeader.bmp_bytesz, filePtr);
   // close file and return bitmap image data
   fclose(filePtr);
   return bitmapImage;

}


// Return: true on error. int saveBMP(in string fileName, const ref BitmapInfoHeader bmp_ih,

           in Pixel[] data) nothrow {
   immutable uint moffset = BMPfileMagic.sizeof +
                            BMPfileHeader.sizeof +
                            BitmapInfoHeader.sizeof +
                            ((1U << bmp_ih.bitspp) * 4);
   BMPfileHeader bmp_fh = {
       filesz: moffset + bmp_ih.bmp_bytesz,
       creator1: 0,
       creator2: 0,
       bmp_offset: moffset
   };
   auto fp = fopen((fileName ~ "\0").ptr, "wb");
   if (fp == null)
       return true;
   BMPfileMagic mag = {[0x42, 0x4d]};
   fwrite(&mag, 1, BMPfileMagic.sizeof, fp);
   fwrite(&bmp_fh, 1, BMPfileHeader.sizeof, fp);
   fwrite(&bmp_ih, 1, BitmapInfoHeader.sizeof, fp);
   // Palette
   foreach (i; 0 .. (1U << bmp_ih.bitspp)) {
       immutable Rgb color = Rgb(cast(ubyte)i, cast(ubyte)i, cast(ubyte)i);
       fwrite(&color, 1, Rgb.sizeof, 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);
   foreach (di; data) {
       immutable ubyte c = cast(ubyte)di;
       fwrite(&c, ubyte.sizeof, 1, fp);
   }
   fclose(fp);
   return false;

}


// if norm==1, map pixels to range 0..MAX_BRIGHTNESS void convolution(in Pixel[] inp, Pixel[] outp, in float[] kernel,

                in int nx, in int ny, in int kn, in int norm) /*pure*/ nothrow {
   immutable int khalf = cast(int)floor(kn / 2.0); // not pure, doesn't run at compile-time
   float min = float.max, max = -float.max;
   if (norm)
       foreach (m; khalf .. nx - khalf)
           foreach (n; khalf .. ny - khalf) {
               float pixel = 0.0;
               int c;
               foreach (j; -khalf .. khalf + 1)
                   foreach (i; -khalf .. khalf + 1) {
                       pixel += inp[(n - j) * nx + m - i] * kernel[c];
                       c++;
                   }
               if (pixel < min)
                   min = pixel;
               if (pixel > max)
                   max = pixel;
               }
   foreach (m; khalf .. nx - khalf)
       foreach (n; khalf .. ny - khalf) {
           float pixel = 0.0;
           int c;
           foreach (j; -khalf .. khalf + 1)
               foreach (i; -khalf .. khalf + 1) {
                   pixel += inp[(n - j) * nx + m - i] * kernel[c];
                   c++;
               }
           if (norm)
               pixel = MAX_BRIGHTNESS * (pixel - min) / (max - min);
           outp[n * nx + m] = cast(Pixel)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 gaussianFilter(float sigma)(in Pixel[] inp, Pixel[] outp,

                                in int nx, in int ny) nothrow {
   enum int n = 2 * cast(int)(2 * sigma) + 3;
   /*enum*/ immutable float mean = cast(float)floor(n / 2.0);
   float[n * n] kernel;
   fprintf(stderr, "gaussianFilter: kernel size %d, sigma=%g\n", n, sigma);
   int c;
   foreach (i; 0 .. n)
       foreach (j; 0 .. n) {
           kernel[c] = exp(-0.5 * (pow((i - mean) / sigma, 2.0) + pow((j - mean) / sigma, 2.0)))
                       / (2 * PI * sigma * sigma);
           c++;
       }
   convolution(inp, outp, 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.

  • /

Pixel[] cannyEdgeDetection(float sigma)(in Pixel[] inp, const ref BitmapInfoHeader bmp_ih,

                                       in int tmin, in int tmax) nothrow {
   __gshared immutable float[] Gx = [-1, 0, 1,
                                     -2, 0, 2,
                                     -1, 0, 1];
   __gshared immutable float[] Gy = [ 1, 2, 1,
                                      0, 0, 0,
                                     -1,-2,-1];
   immutable int nx = bmp_ih.width;
   immutable int ny = bmp_ih.height;
   auto outp = new Pixel[bmp_ih.bmp_bytesz];
   auto G = new Pixel[nx * ny];
   auto after_Gx = new Pixel[nx * ny];
   auto after_Gy = new Pixel[nx * ny];
   auto nms = new Pixel[nx * ny];
   gaussianFilter!(sigma)(inp, outp, nx, ny);
   convolution(outp, after_Gx, Gx, nx, ny, 3, 0);
   convolution(outp, after_Gy, Gy, nx, ny, 3, 0);
   int Gmax;
   foreach (i; 1 .. nx - 1)
       foreach (j; 1 .. ny - 1) {
           immutable int c = i + nx * j;
           // G[c] = abs(after_Gx[c]) + abs(after_Gy[c]);
           G[c] = cast(Pixel)hypot(after_Gx[c], after_Gy[c]);
           if (G[c] > Gmax)
               Gmax = G[c];
       }
   // Non-maximum suppression, straightforward implementation.
   foreach (i; 1 .. nx - 1)
       foreach (j; 1 .. ny - 1) {
           immutable int c = i + nx * j;
           immutable int nn = c - nx;
           immutable int ss = c + nx;
           immutable int ww = c + 1;
           immutable int ee = c - 1;
           immutable int nw = nn + 1;
           immutable int ne = nn - 1;
           immutable int sw = ss + 1;
           immutable int se = ss - 1;
           immutable float dir = cast(float)(fmod(atan2(cast(double)after_Gy[c],
                                                        cast(double)after_Gx[c]) + PI, PI) / PI) * 8;
           if (((dir <= 1 || dir > 7) && G[c] > G[ee] && G[c] > G[ww]) || // 0 deg
               ((dir > 1 && dir <= 3) && G[c] > G[nw] && G[c] > G[se]) || // 45 deg
               ((dir > 3 && dir <= 5) && G[c] > G[nn] && G[c] > G[ss]) || // 90 deg
               ((dir > 5 && dir <= 7) && G[c] > G[ne] && G[c] > G[sw]))   // 135 deg
               nms[c] = G[c];
           else
               nms[c] = 0;
       }
   // Reuse array
   auto edges = after_Gy; // used as a stack
   outp[] = Pixel.init;
   edges[] = Pixel.init;
   // Tracing edges with hysteresis . Non-recursive implementation.
   int c = 1;
   foreach (j; 1 .. ny - 1) {
       foreach (i; 1 .. nx - 1) {
           if (nms[c] >= tmax && outp[c] == 0) { // trace edges
               outp[c] = MAX_BRIGHTNESS;
               int nedges = 1;
               edges[0] = c;
               do {
                   nedges--;
                   immutable int t = edges[nedges];
                   int[8] nbs = void; // neighbours
                   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
                   foreach (k; 0 .. 8)
                       if (nms[nbs[k]] >= tmin && outp[nbs[k]] == 0) {
                           outp[nbs[k]] = MAX_BRIGHTNESS;
                           edges[nedges] = nbs[k];
                           nedges++;
                       }
               } while (nedges > 0);
           }
           c++;
       }
   }
   return outp;

}


int main(in string[] args) nothrow {

   if (args.length < 2) {
       printf("Usage: %s image.bmp\n", (args[0] ~ "\0").ptr);
       return 1;
   }
   BitmapInfoHeader ih;
   const inputBitmap = loadBMP(args[1], ih);
   if (inputBitmap.length == 0)
       return 1;
   printf("Info: %d x %d x %d\n", ih.width, ih.height, ih.bitspp);
   const outputBitmap = cannyEdgeDetection!(1.0f)(inputBitmap, ih, 45, 50);
   if (outputBitmap.length == 0)
       return 1;
   if (saveBMP("out.bmp", ih, outputBitmap))
       return 1;
   return 0;

}</lang>