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) |
Tuple!(Pixel[],BMPheader) loadBMP(in string fileName) { |
||
⚫ | |||
auto filePtr = fopen((fileName ~ "\0").ptr, "rb"); |
auto filePtr = fopen((fileName ~ "\0").ptr, "rb"); |
||
if (filePtr == null) |
if (filePtr == null) |
||
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) |
||
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) |
||
throw new Exception(text("loadBMP: not a BMP file: magic=", |
|||
header.smagic)); |
|||
return typeof(return).init; |
|||
⚫ | |||
if (header.compress_type != 0) |
if (header.compress_type != 0) |
||
throw new Exception("loadBMP: compression is not supported."); |
|||
⚫ | |||
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 |
||
throw new Exception("loadBMP: fread bitmap data."); |
|||
return typeof(return).init; |
|||
⚫ | |||
bi = c; |
bi = c; |
||
} |
} |
||
⚫ | |||
return tuple(bitmapImage, header); |
return tuple(bitmapImage, header); |
||
} |
} |
||
⚫ | |||
// Return: true on error. |
|||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
auto |
auto filePtr = fopen((fileName ~ "\0").ptr, "wb"); |
||
if (fp == null) |
|||
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 |
// 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; |
||
⚫ | |||
// write new BMP header |
|||
⚫ | |||
ubyte r, g, b, nothing; |
|||
throw new Exception("saveBMP: fwrite BMP header."); |
|||
} |
|||
// |
// write palette |
||
foreach (i; 0 .. (1U << header.bitspp)) { |
foreach (i; 0 .. (1U << header.bitspp)) { |
||
static struct Rgb { |
|||
ubyte r, g, b, nothing; |
|||
⚫ | |||
⚫ | |||
⚫ | |||
immutable colorEntry = Rgb(cast(ubyte)i, |
|||
cast(ubyte)i, |
|||
⚫ | |||
⚫ | |||
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, |
if (fwrite(&c, ubyte.sizeof, 1, filePtr) != 1) // slow |
||
throw new Exception("saveBMP: fwrite bitmap data."); |
|||
} |
} |
||
fclose(fp); |
|||
return false; |
|||
} |
} |
||
// if |
// if normalize is true, map pixels to range 0...MAX_BRIGHTNESS |
||
void convolution(in Pixel[] inp, Pixel[] outp |
void convolution(bool normalize)(in Pixel[] inp, Pixel[] outp, |
||
in float[] kernel, |
|||
⚫ | |||
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 ( |
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 > pMax) |
|||
⚫ | |||
} |
} |
||
⚫ | |||
⚫ | |||
⚫ | |||
} |
|||
⚫ | |||
⚫ | |||
⚫ | |||
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 ( |
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 |
void gaussianFilter(in Pixel[] inp, Pixel[] outp, |
||
in int nx, in int ny, float sigma) nothrow { |
|||
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] |
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++; |
||
} |
} |
||
⚫ | |||
⚫ | |||
⚫ | |||
} |
} |
||
Line 619: | Line 619: | ||
tMin, and tMax are lower and upper thresholds. |
tMin, and tMax are lower and upper thresholds. |
||
*/ |
*/ |
||
Pixel[] cannyEdgeDetection |
Pixel[] cannyEdgeDetection(in Pixel[] inp, |
||
const ref BMPheader header, |
|||
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 after_Gy = new Pixel[nx * ny]; |
|||
auto nms = new Pixel[nx * ny]; |
|||
gaussianFilter |
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]; |
||
auto after_Gx = new Pixel[nx * ny]; |
|||
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); |
|||
⚫ | |||
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]); |
||
⚫ | |||
Gmax = G[c]; |
|||
} |
} |
||
// Non-maximum suppression, straightforward implementation. |
// Non-maximum suppression, straightforward implementation. |
||
⚫ | |||
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] |
int[8] neighbours = void; |
||
neighbours[0] = t - nx; // nn |
|||
neighbours[1] = t + nx; // ss |
|||
neighbours[2] = t + 1; // ww |
|||
neighbours[3] = t - 1; // ee |
|||
neighbours[4] = neighbours[0] + 1; // nw |
|||
neighbours[5] = neighbours[0] - 1; // ne |
|||
neighbours[6] = neighbours[1] + 1; // sw |
|||
neighbours[7] = neighbours[1] - 1; // se |
|||
foreach (k; 0 .. 8) |
foreach (k; 0 .. 8) |
||
if (nms[ |
if (nms[neighbours[k]] >= tMin && |
||
outp[ |
outp[neighbours[k]] == 0) { |
||
outp[neighbours[k]] = MAX_BRIGHTNESS; |
|||
edges[nedges] = neighbours[k]; |
|||
nedges++; |
nedges++; |
||
} |
} |
||
Line 728: | Line 727: | ||
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 |
return; |
||
} |
} |
||
const inputBitmap_ih = loadBMP(args[1]); |
const inputBitmap_ih = loadBMP(args[1]); |
||
const inputBitmap = inputBitmap_ih[0]; |
const inputBitmap = inputBitmap_ih[0]; |
||
immutable ih = inputBitmap_ih[1]; |
|||
if (inputBitmap.length == 0) |
|||
⚫ | |||
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); |
||
⚫ | |||
ih, 45, 50, 1.0f)); |
|||
⚫ | |||
⚫ | |||
if (outputBitmap.length == 0) |
|||
return 1; |
|||
if (saveBMP("out.bmp", ih, outputBitmap)) |
|||
return 1; |
|||
return 0; |
|||
}</lang> |
}</lang> |