c# - FFT Convolution - 3x3 kernel -
i have written routines sharpen grayscale image using 3x3 kernel,
-1 -1 -1 -1 9 -1 -1 -1 -1
the following code working in case of non-fft (spatial-domain) convolution, but, not working in fft-based (frequency-domain) convolution.
the output image seems blurred.
i have several problems:
(1) routine not being able generate desired result. freezes application.
public static bitmap applywithpadding(bitmap image, bitmap mask) { if(image.pixelformat == pixelformat.format8bppindexed) { bitmap imageclone = (bitmap)image.clone(); bitmap maskclone = (bitmap)mask.clone(); ///////////////////////////////////////////////////////////////// complex[,] cpaddedlena = imagedataconverter.tocomplex(imageclone); complex[,] cpaddedmask = imagedataconverter.tocomplex(maskclone); complex[,] cconvolved = convolution.convolve(cpaddedlena, cpaddedmask); return imagedataconverter.tobitmap(cconvolved); } else { throw new exception("not grascale"); } }
(2) routine gives result. but, slow hell.
public static bitmap apply(bitmap sourcebitmap) { sharpen filter = new sharpen(); bitmapdata sourcedata = sourcebitmap.lockbits(new rectangle(0, 0, sourcebitmap.width, sourcebitmap.height), imagelockmode.readonly, pixelformat.format32bppargb); byte[] pixelbuffer = new byte[sourcedata.stride * sourcedata.height]; byte[] resultbuffer = new byte[sourcedata.stride * sourcedata.height]; marshal.copy(sourcedata.scan0, pixelbuffer, 0, pixelbuffer.length); sourcebitmap.unlockbits(sourcedata); double blue = 0.0; double green = 0.0; double red = 0.0; int filterwidth = filter.filtermatrix.getlength(1); int filterheight = filter.filtermatrix.getlength(0); int filteroffset = (filterwidth - 1) / 2; int calcoffset = 0; int byteoffset = 0; (int offsety = filteroffset; offsety < sourcebitmap.height - filteroffset; offsety++) { (int offsetx = filteroffset; offsetx < sourcebitmap.width - filteroffset; offsetx++) { blue = 0; green = 0; red = 0; byteoffset = offsety * sourcedata.stride + offsetx * 4; (int filtery = -filteroffset; filtery <= filteroffset; filtery++) { (int filterx = -filteroffset; filterx <= filteroffset; filterx++) { calcoffset = byteoffset + (filterx * 4) + (filtery * sourcedata.stride); blue += (double)(pixelbuffer[calcoffset]) * filter.filtermatrix[filtery + filteroffset, filterx + filteroffset]; green += (double)(pixelbuffer[calcoffset + 1]) * filter.filtermatrix[filtery + filteroffset, filterx + filteroffset]; red += (double)(pixelbuffer[calcoffset + 2]) * filter.filtermatrix[filtery + filteroffset, filterx + filteroffset]; } } blue = filter.factor * blue + filter.bias; green = filter.factor * green + filter.bias; red = filter.factor * red + filter.bias; if (blue > 255) { blue = 255; } else if (blue < 0) { blue = 0; } if (green > 255) { green = 255; } else if (green < 0) { green = 0; } if (red > 255) { red = 255; } else if (red < 0) { red = 0; } resultbuffer[byteoffset] = (byte)(blue); resultbuffer[byteoffset + 1] = (byte)(green); resultbuffer[byteoffset + 2] = (byte)(red); resultbuffer[byteoffset + 3] = 255; } } bitmap resultbitmap = new bitmap(sourcebitmap.width, sourcebitmap.height); bitmapdata resultdata = resultbitmap.lockbits(new rectangle(0, 0, resultbitmap.width, resultbitmap.height), imagelockmode.writeonly, pixelformat.format32bppargb); marshal.copy(resultbuffer, 0, resultdata.scan0, resultbuffer.length); resultbitmap.unlockbits(resultdata); return resultbitmap; }
(3) following gui code. sharpenfilter.applywithpadding()
works if use image mask. but, doesn't work if use a, say, 3
x3
kernel.
string path = @"e:\lena.png"; string path2 = @"e:\mask.png"; bitmap _inputimage; bitmap _maskimage; private void loadimages_click(object sender, eventargs e) { _inputimage = grayscale.tograyscale(bitmap.fromfile(path) bitmap); /* _maskimage = grayscale.tograyscale(bitmap.fromfile(path2) bitmap); */ sharpenfilter filter = new sharpenfilter(); double[,] mask = new double[,] { { -1, -1, -1, }, { -1, 9, -1, }, { -1, -1, -1, }, }; _maskimage = imagedataconverter.tobitmap(mask); inputimagepicturebox.image = _inputimage; maskpicturebox.image = _maskimage; } bitmap _paddedimage; bitmap _paddedmask; private void padbutton_click(object sender, eventargs e) { bitmap lena = grayscale.tograyscale(_inputimage); bitmap mask = grayscale.tograyscale(_maskimage); ////not working... //int maxwidth = (int)math.max(lena.width, mask.width); //int maxheight = (int)math.max(lena.height, mask.height); ////this working correctly in case if use png image mask. int maxwidth = (int)tools.tonextpow2(convert.touint32(lena.width + mask.width)); int maxheight = (int)tools.tonextpow2(convert.touint32(lena.height + mask.height)); _paddedimage = imagepadder.pad(lena, maxwidth, maxheight); _paddedmask = imagepadder.pad(mask, maxwidth, maxheight); paddedimagepicturebox.image = _paddedimage; paddedmaskpicturebox.image = _paddedmask; } private void filterbutton_click(object sender, eventargs e) { // not working properly. // freezes application. bitmap sharp = sharpenfilter.applywithpadding(_paddedimage, _paddedmask); ////works well. but, slow. //bitmap sharp = sharpenfilter.apply(_paddedimage); filteredpicturebox.image = sharp bitmap; }
output:
source code :
- you can download entire solution here in link.
the main issue appears interpretation of kernel image consisting of unsigned byte values. result -1
values converted 255
computing convolution kernel
255 255 255 255 9 255 255 255 255
this can observed white area in "convolution kernel" image. resulting kernel of low-pass filter, producing corresponding blurring effect.
probably best way handle read kernel matrix of signed values instead of image.
if still prefer handle kernel image, need convert image signed values. simplest way can think of achieving result create modified version of imagedataconverter.tointeger(bitmap)
map bytes signed values:
public static complex[,] unwrap(bitmap bitmap) { int width = bitmap.width; int height = bitmap.height; complex[,] array2d = new complex[bitmap.width, bitmap.height]; ... else// if there 1 channel: { iii = (int)(*address); if (iii >= 128) { iii -= 256; } } complex tempcomp = new complex((double)iii, 0.0); array2d[x, y] = tempcomp;
you able convert image in sharpenfilter.applywithpadding
with:
complex[,] cpaddedmask = imagedataconverter.unwrap(maskclone);
this should give following result:
while improves on sharpness of image, should notice image darker original. due convolution.rescale
function dynamically rescales image according it's minimum , maximum value. can convenient show image maximum dynamic range, result in different overall scaling standard convolution. achieve standard scaling (based on scaling of fft implementation), use following implementation:
//rescale values between 0 , 255. private static void rescale(complex[,] convolve) { int imagewidth = convolve.getlength(0); int imageheight = convolve.getlength(1); double scale = imagewidth * imageheight; (int j = 0; j < imageheight; j++) { (int = 0; < imagewidth; i++) { double re = math.max(0, math.min(convolve[i, j].real * scale, 255.0)); double im = math.max(0, math.min(convolve[i, j].imaginary * scale, 255.0)); convolve[i, j] = new complex(re, im); } } }
this should give image more appropriate brightness level:
finally, filtering operation 1 typically expect result match original image size (unlike convolution includes tails). cropping result in sharpenfilter.applywithpadding
with:
... // -3 terms due kernel size // +5 vertical offset term due vertical reflection & offset in setpixel rectangle rect = new rectangle((cpaddedlena.getlength(0) / 2 - 3) / 2, (cpaddedlena.getlength(1) / 2 - 3) / 2 + 5, cpaddedlena.getlength(0) / 2, cpaddedlena.getlength(1) / 2); return imagedataconverter.tobitmap(cconvolved).clone(rect, pixelformat.format8bppindexed);
should give you:
for easier visual comparison, here original image again:
Comments
Post a Comment