Canny edge detector: Difference between revisions

Content added Content deleted
(Simpler D code)
(More idiomatic D code)
Line 419: Line 419:
import std.math: PI, floor, exp, pow, hypot, fmod, atan2;
import std.math: PI, floor, exp, pow, hypot, fmod, atan2;
import std.typecons: tuple, Tuple;
import std.typecons: tuple, Tuple;
import std.string: text;


enum MAX_BRIGHTNESS = 255;
enum MAX_BRIGHTNESS = 255;
Line 451: Line 452:




Tuple!(Pixel[],BMPheader) loadBMP(in string fileName) nothrow {
Tuple!(Pixel[],BMPheader) loadBMP(in string fileName) {
scope(exit) if (filePtr) fclose(filePtr);

auto filePtr = fopen((fileName ~ "\0").ptr, "rb");
auto filePtr = fopen((fileName ~ "\0").ptr, "rb");
if (filePtr == null) {
if (filePtr == null)
perror("fopen()");
throw new Exception("loadBMP: fopen()");
return typeof(return).init;
}


BMPheader header;
BMPheader header;
if (fread(&header, header.sizeof, 1, filePtr) != 1) {
if (fread(&header, header.sizeof, 1, filePtr) != 1)
fclose(filePtr);
throw new Exception("loadBMP: fread header");
return typeof(return).init;
}


// verify that this is a bmp file by check bitmap id
// verify that this is a bmp file by check bitmap id
if (header.smagic != 0x4D42) {
if (header.smagic != 0x4D42)
fprintf(stderr, "Not a BMP file: magic=%u\n", header.smagic);
throw new Exception(text("loadBMP: not a BMP file: magic=",
fclose(filePtr);
header.smagic));
return typeof(return).init;
}


if (header.compress_type != 0) {
if (header.compress_type != 0)
fprintf(stderr, "Warning, compression is not supported.\n");
throw new Exception("loadBMP: compression is not supported.");
fclose(filePtr);
return typeof(return).init;
}


// move file point to the beginning of bitmap data
// move file point to the beginning of bitmap data
Line 484: Line 478:
foreach (ref bi; bitmapImage) {
foreach (ref bi; bitmapImage) {
ubyte c = void;
ubyte c = void;
if (fread(&c, ubyte.sizeof, 1, filePtr) != 1) {
if (fread(&c, ubyte.sizeof, 1, filePtr) != 1) // slow
fclose(filePtr);
throw new Exception("loadBMP: fread bitmap data.");
return typeof(return).init;
}
bi = c;
bi = c;
}
}


fclose(filePtr);
return tuple(bitmapImage, header);
return tuple(bitmapImage, header);
}
}




void saveBMP(in string fileName, const ref BMPheader header,
// Return: true on error.
in Pixel[] data) {
int saveBMP(in string fileName, const ref BMPheader header,
scope(exit) if (filePtr) fclose(filePtr);
in Pixel[] data) nothrow {

auto fp = fopen((fileName ~ "\0").ptr, "wb");
auto filePtr = fopen((fileName ~ "\0").ptr, "wb");
if (fp == null)
return true;
if (filePtr == null)
throw new Exception("saveBMP: fopen()");


immutable uint moffset = BMPheader.sizeof +
immutable uint moffset = BMPheader.sizeof +
((1U << header.bitspp) * 4);
((1U << header.bitspp) * 4);


// generate and write new BMP header
// generate new BMP header
BMPheader newHeader = header;
BMPheader newHeader = header;
newHeader.smagic = 0x4D42;
newHeader.filesz = moffset + header.bmp_bytesz;
newHeader.filesz = moffset + header.bmp_bytesz;
newHeader.creator1 = 0;
newHeader.creator2 = 0;
newHeader.bmp_offset = moffset;
newHeader.bmp_offset = moffset;
fwrite(&newHeader, 1, newHeader.sizeof, fp);


static struct Rgb {
// write new BMP header
if (fwrite(&newHeader, newHeader.sizeof, 1, filePtr) != 1)
ubyte r, g, b, nothing;
throw new Exception("saveBMP: fwrite BMP header.");
}


// Write palette
// write palette
foreach (i; 0 .. (1U << header.bitspp)) {
foreach (i; 0 .. (1U << header.bitspp)) {
immutable color = Rgb(cast(ubyte)i,
static struct Rgb {
cast(ubyte)i,
ubyte r, g, b, nothing;
}
cast(ubyte)i);

fwrite(&color, 1, Rgb.sizeof, fp);
immutable colorEntry = Rgb(cast(ubyte)i,
cast(ubyte)i,
cast(ubyte)i);
if (fwrite(&colorEntry, Rgb.sizeof, 1, filePtr) != 1)
throw new Exception("saveBMP: fwrite palette.");
}
}


// write bitmap data
foreach (di; data) {
foreach (di; data) {
immutable ubyte c = cast(ubyte)di;
immutable ubyte c = cast(ubyte)di;
fwrite(&c, ubyte.sizeof, 1, fp);
if (fwrite(&c, ubyte.sizeof, 1, filePtr) != 1) // slow
throw new Exception("saveBMP: fwrite bitmap data.");
}
}

fclose(fp);
return false;
}
}




// if norm is true, map pixels to range 0...MAX_BRIGHTNESS
// if normalize is true, map pixels to range 0...MAX_BRIGHTNESS
void convolution(in Pixel[] inp, Pixel[] outp, in float[] kernel,
void convolution(bool normalize)(in Pixel[] inp, Pixel[] outp,
in int nx, in int ny, in int kn, in bool norm)
in float[] kernel,
in int nx, in int ny, in int kn)
pure nothrow {
pure nothrow {
immutable int khalf = kn / 2;
immutable int khalf = kn / 2;
float pMin = float.max, pMax = -float.max;
float pMin = float.max, pMax = -float.max;


if (norm)
static if (normalize) {
foreach (m; khalf .. nx - khalf)
foreach (m; khalf .. nx - khalf) {
foreach (n; khalf .. ny - khalf) {
foreach (n; khalf .. ny - khalf) {
float pixel = 0.0;
float pixel = 0.0;
int c;
int c;
foreach (j; -khalf .. khalf + 1)
foreach (j; -khalf .. khalf + 1) {
foreach (i; -khalf .. khalf + 1) {
foreach (i; -khalf .. khalf + 1) {
pixel += inp[(n-j) * nx + m - i] * kernel[c];
pixel += inp[(n - j) * nx + m - i] * kernel[c];
c++;
c++;
}
}
if (pixel < pMin)
pMin = pixel;
if (pixel > pMax)
pMax = pixel;
}
}


if (pixel < pMin) pMin = pixel;
foreach (m; khalf .. nx - khalf)
if (pixel > pMax) pMax = pixel;
}
}
}

foreach (m; khalf .. nx - khalf) {
foreach (n; khalf .. ny - khalf) {
foreach (n; khalf .. ny - khalf) {
float pixel = 0.0;
float pixel = 0.0;
int c;
int c;
foreach (j; -khalf .. khalf + 1)
foreach (j; -khalf .. khalf + 1) {
foreach (i; -khalf .. khalf + 1) {
foreach (i; -khalf .. khalf + 1) {
pixel += inp[(n - j) * nx + m - i] * kernel[c];
pixel += inp[(n - j) * nx + m - i] * kernel[c];
c++;
c++;
}
}
}


if (norm)
static if (normalize)
pixel = MAX_BRIGHTNESS * (pixel - pMin) / (pMax - pMin);
pixel = MAX_BRIGHTNESS * (pixel - pMin) / (pMax - pMin);
outp[n * nx + m] = cast(Pixel)pixel;
outp[n * nx + m] = cast(Pixel)pixel;
}
}
}
}
}


Line 589: Line 586:
kernelSize = 2 * int(2*sigma) + 3;
kernelSize = 2 * int(2*sigma) + 3;
*/
*/
void gaussianFilter(float sigma)(in Pixel[] inp, Pixel[] outp,
void gaussianFilter(in Pixel[] inp, Pixel[] outp,
in int nx, in int ny) nothrow {
in int nx, in int ny, float sigma) nothrow {
enum int n = 2 * cast(int)(2 * sigma) + 3;
immutable int n = 2 * cast(int)(2 * sigma) + 3;
/*enum*/ immutable float mean = cast(float)floor(n / 2.0);
/*enum*/ immutable float mean = cast(float)floor(n / 2.0);
float[n * n] kernel;
auto kernel = new float[n * n];


debug fprintf(stderr,
debug fprintf(stderr,
"gaussianFilter: kernel size %d, sigma=%g\n",
"gaussianFilter: kernel size %d, sigma=%g\n",
n, sigma);
n, sigma);

int c;
int c;
foreach (i; 0 .. n)
foreach (i; 0 .. n) {
foreach (j; 0 .. n) {
foreach (j; 0 .. n) {
kernel[c] = exp(-0.5 * (pow((i - mean) / sigma, 2.0) +
kernel[c] = exp(-0.5 * (pow((i - mean) / sigma, 2.0) +
Line 606: Line 604:
c++;
c++;
}
}
}
convolution(inp, outp, kernel[], nx, ny, n, true);

convolution!(true)(inp, outp, kernel, nx, ny, n);
}
}


Line 619: Line 619:
tMin, and tMax are lower and upper thresholds.
tMin, and tMax are lower and upper thresholds.
*/
*/
Pixel[] cannyEdgeDetection(float sigma)(in Pixel[] inp,
Pixel[] cannyEdgeDetection(in Pixel[] inp,
const ref BMPheader header,
const ref BMPheader header,
in int tMin, in int tMax)
in int tMin, in int tMax, float sigma)
nothrow {
nothrow {
immutable int nx = header.width;
immutable int nx = header.width;
immutable int ny = header.height;
immutable int ny = header.height;
auto outp = new Pixel[header.bmp_bytesz];
auto outp = new Pixel[header.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);
gaussianFilter(inp, outp, nx, ny, sigma);


__gshared immutable float[] Gx = [-1, 0, 1,
__gshared immutable float[] Gx = [-1, 0, 1,
Line 641: Line 637:
-1,-2,-1];
-1,-2,-1];


convolution(outp, after_Gx, Gx, nx, ny, 3, false);
auto after_Gx = new Pixel[nx * ny];
convolution(outp, after_Gy, Gy, nx, ny, 3, false);
auto after_Gy = new Pixel[nx * ny];


convolution!(false)(outp, after_Gx, Gx, nx, ny, 3);
int Gmax;
convolution!(false)(outp, after_Gy, Gy, nx, ny, 3);

auto G = new Pixel[nx * ny];
foreach (i; 1 .. nx - 1)
foreach (i; 1 .. nx - 1)
foreach (j; 1 .. ny - 1) {
foreach (j; 1 .. ny - 1) {
immutable int c = i + nx * j;
immutable int c = i + nx * j;
G[c] = cast(Pixel)hypot(after_Gx[c], 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.
// Non-maximum suppression, straightforward implementation.
auto nms = new Pixel[nx * ny];
foreach (i; 1 .. nx - 1)
foreach (i; 1 .. nx - 1)
foreach (j; 1 .. ny - 1) {
foreach (j; 1 .. ny - 1) {
Line 702: Line 700:
immutable int t = edges[nedges];
immutable int t = edges[nedges];


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


foreach (k; 0 .. 8)
foreach (k; 0 .. 8)
if (nms[nbs[k]] >= tMin && outp[nbs[k]] == 0) {
if (nms[neighbours[k]] >= tMin &&
outp[nbs[k]] = MAX_BRIGHTNESS;
outp[neighbours[k]] == 0) {
edges[nedges] = nbs[k];
outp[neighbours[k]] = MAX_BRIGHTNESS;
edges[nedges] = neighbours[k];
nedges++;
nedges++;
}
}
Line 728: Line 727:




int main(in string[] args) nothrow {
void main(in string[] args) {
if (args.length < 2) {
if (args.length < 2) {
printf("Usage: %s image.bmp\n", (args[0] ~ "\0").ptr);
printf("Usage: %s image.bmp\n", (args[0] ~ "\0").ptr);
return 1;
return;
}
}


const inputBitmap_ih = loadBMP(args[1]);
const inputBitmap_ih = loadBMP(args[1]);
const inputBitmap = inputBitmap_ih[0];
const inputBitmap = inputBitmap_ih[0];
const ih = inputBitmap_ih[1];
immutable ih = inputBitmap_ih[1];
if (inputBitmap.length == 0)
return 1;


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);
saveBMP("out.bmp", ih, cannyEdgeDetection(inputBitmap,

ih, 45, 50, 1.0f));
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>
}</lang>