Image convolution in spatial domain - c#

I am trying to replicate the outcome of this link using linear convolution in spatial-domain.
Images are first converted to 2d double arrays and then convolved. Image and kernel are of the same size. The image is padded before convolution and cropped accordingly after the convolution.
As compared to the FFT-based convolution, the output is weird and incorrect.
How can I solve the issue?
Note that I obtained the following image output from Matlab which matches my C# FFT output:
.
Update-1: Following #Ben Voigt's comment, I changed the Rescale() function to replace 255.0 with 1 and thus the output is improved substantially. But, still, the output doesn't match the FFT output (which is the correct one).
.
Update-2: Following #Cris Luengo's comment, I have padded the image by stitching and then performed spatial convolution. The outcome has been as follows:
So, the output is worse than the previous one. But, this has a similarity with the 2nd output of the linked answer which means a circular convolution is not the solution.
.
Update-3: I have used the Sum() function proposed by #Cris Luengo's answer. The result is a more improved version of **Update-1**:
But, it is still not 100% similar to the FFT version.
.
Update-4: Following #Cris Luengo's comment, I have subtracted the two outcomes to see the difference:
,
1. spatial minus frequency domain
2. frequency minus spatial domain
Looks like, the difference is substantial which means, spatial convolution is not being done correctly.
.
Source Code:
(Notify me if you need more source code to see.)
public static double[,] LinearConvolutionSpatial(double[,] image, double[,] mask)
{
int maskWidth = mask.GetLength(0);
int maskHeight = mask.GetLength(1);
double[,] paddedImage = ImagePadder.Pad(image, maskWidth);
double[,] conv = Convolution.ConvolutionSpatial(paddedImage, mask);
int cropSize = (maskWidth/2);
double[,] cropped = ImageCropper.Crop(conv, cropSize);
return conv;
}
static double[,] ConvolutionSpatial(double[,] paddedImage1, double[,] mask1)
{
int imageWidth = paddedImage1.GetLength(0);
int imageHeight = paddedImage1.GetLength(1);
int maskWidth = mask1.GetLength(0);
int maskHeight = mask1.GetLength(1);
int convWidth = imageWidth - ((maskWidth / 2) * 2);
int convHeight = imageHeight - ((maskHeight / 2) * 2);
double[,] convolve = new double[convWidth, convHeight];
for (int y = 0; y < convHeight; y++)
{
for (int x = 0; x < convWidth; x++)
{
int startX = x;
int startY = y;
convolve[x, y] = Sum(paddedImage1, mask1, startX, startY);
}
}
Rescale(convolve);
return convolve;
}
static double Sum(double[,] paddedImage1, double[,] mask1, int startX, int startY)
{
double sum = 0;
int maskWidth = mask1.GetLength(0);
int maskHeight = mask1.GetLength(1);
for (int y = startY; y < (startY + maskHeight); y++)
{
for (int x = startX; x < (startX + maskWidth); x++)
{
double img = paddedImage1[x, y];
double msk = mask1[x - startX, y - startY];
sum = sum + (img * msk);
}
}
return sum;
}
static void Rescale(double[,] convolve)
{
int imageWidth = convolve.GetLength(0);
int imageHeight = convolve.GetLength(1);
double maxAmp = 0.0;
for (int j = 0; j < imageHeight; j++)
{
for (int i = 0; i < imageWidth; i++)
{
maxAmp = Math.Max(maxAmp, convolve[i, j]);
}
}
double scale = 1.0 / maxAmp;
for (int j = 0; j < imageHeight; j++)
{
for (int i = 0; i < imageWidth; i++)
{
double d = convolve[i, j] * scale;
convolve[i, j] = d;
}
}
}
public static Bitmap ConvolveInFrequencyDomain(Bitmap image1, Bitmap kernel1)
{
Bitmap outcome = null;
Bitmap image = (Bitmap)image1.Clone();
Bitmap kernel = (Bitmap)kernel1.Clone();
//linear convolution: sum.
//circular convolution: max
uint paddedWidth = Tools.ToNextPow2((uint)(image.Width + kernel.Width));
uint paddedHeight = Tools.ToNextPow2((uint)(image.Height + kernel.Height));
Bitmap paddedImage = ImagePadder.Pad(image, (int)paddedWidth, (int)paddedHeight);
Bitmap paddedKernel = ImagePadder.Pad(kernel, (int)paddedWidth, (int)paddedHeight);
Complex[,] cpxImage = ImageDataConverter.ToComplex(paddedImage);
Complex[,] cpxKernel = ImageDataConverter.ToComplex(paddedKernel);
// call the complex function
Complex[,] convolve = Convolve(cpxImage, cpxKernel);
outcome = ImageDataConverter.ToBitmap(convolve);
outcome = ImageCropper.Crop(outcome, (kernel.Width/2)+1);
return outcome;
}

Your current output looks more like the auto-correlation function than the convolution of Lena with herself. I think the issue might be in your Sum function.
If you look at the definition of the convolution sum, you'll see that the kernel (or the image, doesn't matter) is mirrored:
sum_m( f[n-m] g[m] )
For the one function, m appears with a plus sign, and for the other it appears with a minus sign.
You'll need to modify your Sum function to read the mask1 image in the right order:
static double Sum(double[,] paddedImage1, double[,] mask1, int startX, int startY)
{
double sum = 0;
int maskWidth = mask1.GetLength(0);
int maskHeight = mask1.GetLength(1);
for (int y = startY; y < (startY + maskHeight); y++)
{
for (int x = startX; x < (startX + maskWidth); x++)
{
double img = paddedImage1[x, y];
double msk = mask1[maskWidth - x + startX - 1, maskHeight - y + startY - 1];
sum = sum + (img * msk);
}
}
return sum;
}
The other option is to pass a mirrored version of mask1 to this function.

I have found the solution from this link. The main clue was to introduce an offset and a factor.
factor is the sum of all values in the kernel.
offset is an arbitrary value to fix the output further.
.
#Cris Luengo's answer also raised a valid point.
.
The following source code is supplied in the given link:
private void SafeImageConvolution(Bitmap image, ConvMatrix fmat)
{
//Avoid division by 0
if (fmat.Factor == 0)
return;
Bitmap srcImage = (Bitmap)image.Clone();
int x, y, filterx, filtery;
int s = fmat.Size / 2;
int r, g, b;
Color tempPix;
for (y = s; y < srcImage.Height - s; y++)
{
for (x = s; x < srcImage.Width - s; x++)
{
r = g = b = 0;
// Convolution
for (filtery = 0; filtery < fmat.Size; filtery++)
{
for (filterx = 0; filterx < fmat.Size; filterx++)
{
tempPix = srcImage.GetPixel(x + filterx - s, y + filtery - s);
r += fmat.Matrix[filtery, filterx] * tempPix.R;
g += fmat.Matrix[filtery, filterx] * tempPix.G;
b += fmat.Matrix[filtery, filterx] * tempPix.B;
}
}
r = Math.Min(Math.Max((r / fmat.Factor) + fmat.Offset, 0), 255);
g = Math.Min(Math.Max((g / fmat.Factor) + fmat.Offset, 0), 255);
b = Math.Min(Math.Max((b / fmat.Factor) + fmat.Offset, 0), 255);
image.SetPixel(x, y, Color.FromArgb(r, g, b));
}
}
}

Related

Hough Line Transform implementation

I am trying to implement Hough Line Transform.
Input. I am using the following image as input. This single line is expected to produce only one intersection of sine waves in the output.
Desired behavior. my source code is expected to produce the following output as it was generated by the sample application of AForge framework.
Here, we can see:
the dimension of the output is identical to the input image.
the intersection of sine waves are seen at almost at the center.
the intersection pattern of waves is very small and simple.
Present behavior. My source code is producing the following output which is different than that of the output generated by AForge.
the intersection is not at the center.
the wave patterns are also different.
Why is my code producing a different output?
.
Source Code
I have written the following code myself. The following is a Minimal, Complete, and Verifiable source code.
public class HoughMap
{
public int[,] houghMap { get; private set; }
public int[,] image { get; set; }
public void Compute()
{
if (image != null)
{
// get source image size
int inWidth = image.GetLength(0);
int inHeight = image.GetLength(1);
int inWidthHalf = inWidth / 2;
int inHeightHalf = inHeight / 2;
int outWidth = (int)Math.Sqrt(inWidth * inWidth + inHeight * inHeight);
int outHeight = 180;
int outHeightHalf = outHeight / 2;
houghMap = new int[outWidth, outHeight];
// scanning through each (x,y) pixel of the image--+
for (int y = 0; y < inHeight; y++) //|
{ //|
for (int x = 0; x < inWidth; x++)//<-----------+
{
if (image[x, y] != 0)//if a pixel is black, skip it.
{
// We are drawing some Sine waves. So, it may
// vary from -90 to +90 degrees.
for (int theta = -outHeightHalf; theta < outHeightHalf; theta++)
{
double rad = theta * Math.PI / 180;
// respective radius value is computed
//int radius = (int)Math.Round(Math.Cos(rad) * (x - inWidthHalf) - Math.Sin(rad) * (y - inHeightHalf));
//int radius = (int)Math.Round(Math.Cos(rad) * (x + inWidthHalf) - Math.Sin(rad) * (y + inHeightHalf));
int radius = (int)Math.Round(Math.Cos(rad) * (x) - Math.Sin(rad) * (outHeight - y));
// if the radious value is between 1 and
if ((radius > 0) && (radius <= outWidth))
{
houghMap[radius, theta + outHeightHalf]++;
}
}
}
}
}
}
}
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Bitmap bitmap = (Bitmap)pictureBox1.Image as Bitmap;
int[,] intImage = ToInteger(bitmap);
HoughMap houghMap = new HoughMap();
houghMap.image = intImage;
houghMap.Compute();
int[,] normalized = Rescale(houghMap.houghMap);
Bitmap hough = ToBitmap(normalized, bitmap.PixelFormat);
pictureBox2.Image = hough;
}
public static int[,] Rescale(int[,] image)
{
int[,] imageCopy = (int[,])image.Clone();
int Width = imageCopy.GetLength(0);
int Height = imageCopy.GetLength(1);
int minVal = 0;
int maxVal = 0;
for (int j = 0; j < Height; j++)
{
for (int i = 0; i < Width; i++)
{
double conv = imageCopy[i, j];
minVal = (int)Math.Min(minVal, conv);
maxVal = (int)Math.Max(maxVal, conv);
}
}
int minRange = 0;
int maxRange = 255;
int[,] array2d = new int[Width, Height];
for (int j = 0; j < Height; j++)
{
for (int i = 0; i < Width; i++)
{
array2d[i, j] = (maxRange - minRange) * (imageCopy[i,j] - minVal) / (maxVal - minVal) + minRange;
}
}
return array2d;
}
public int[,] ToInteger(Bitmap input)
{
int Width = input.Width;
int Height = input.Height;
int[,] array2d = new int[Width, Height];
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
Color cl = input.GetPixel(x, y);
int gray = (int)Convert.ChangeType(cl.R * 0.3 + cl.G * 0.59 + cl.B * 0.11, typeof(int));
array2d[x, y] = gray;
}
}
return array2d;
}
public Bitmap ToBitmap(int[,] image, PixelFormat pixelFormat)
{
int[,] imageCopy = (int[,])image.Clone();
int Width = imageCopy.GetLength(0);
int Height = imageCopy.GetLength(1);
Bitmap bitmap = new Bitmap(Width, Height, pixelFormat);
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
int iii = imageCopy[x, y];
Color clr = Color.FromArgb(iii, iii, iii);
bitmap.SetPixel(x, y, clr);
}
}
return bitmap;
}
}
I have solved the problem from this link. The source code from this link is the best one I have ever came across.
public class HoughMap
{
public int[,] houghMap { get; private set; }
public int[,] image { get; set; }
public void Compute()
{
if (image != null)
{
// get source image size
int Width = image.GetLength(0);
int Height = image.GetLength(1);
int centerX = Width / 2;
int centerY = Height / 2;
int maxTheta = 180;
int houghHeight = (int)(Math.Sqrt(2) * Math.Max(Width, Height)) / 2;
int doubleHeight = houghHeight * 2;
int houghHeightHalf = houghHeight / 2;
int houghWidthHalf = maxTheta / 2;
houghMap = new int[doubleHeight, maxTheta];
// scanning through each (x,y) pixel of the image--+
for (int y = 0; y < Height; y++) //|
{ //|
for (int x = 0; x < Width; x++)//<-------------+
{
if (image[x, y] != 0)//if a pixel is black, skip it.
{
// We are drawing some Sine waves.
// It may vary from -90 to +90 degrees.
for (int theta = 0; theta < maxTheta; theta++)
{
double rad = theta *Math.PI / 180;
// respective radius value is computed
int rho = (int)(((x - centerX) * Math.Cos(rad)) + ((y - centerY) * Math.Sin(rad)));
// get rid of negative value
rho += houghHeight;
// if the radious value is between
// 1 and twice the houghHeight
if ((rho > 0) && (rho <= doubleHeight))
{
houghMap[rho, theta]++;
}
}
}
}
}
}
}
}
Just look at this C++ code, and this C# code. So, complicated and messy that my brain got arrested. Especially, the C++ one. I never anticipated someone to store 2D values in a 1D array.

C# Convolution filter for any size matrix (1x1, 3x3, 5x5, ...) not fully applied

I'm making a convolution filter for my project and I managed to make it for any size of matrix but as it gets bigger I noticed that not all bits are changed.
Here are the pictures showing the problem:
First one is the original
Filter: Blur 9x9
Filter: EdgeDetection 9x9:
As you can see, there is a little stripe that is never changed and as the matrix gets bigger, the stripe also gets bigger (in 3x3 it wasn't visible)
My convolution matrix class:
public class ConvMatrix
{
public int Factor = 1;
public int Height, Width;
public int Offset = 0;
public int[,] Arr;
//later I assign functions to set these variables
...
}
The filter function:
Bitmap Conv3x3(Bitmap b, ConvMatrix m)
{
if (0 == m.Factor)
return b;
Bitmap bSrc = (Bitmap)b.Clone();
BitmapData bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format24bppRgb);
BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
System.IntPtr SrcScan0 = bmSrc.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
byte* pSrc = (byte*)(void*)SrcScan0;
int nOffset = stride - b.Width * m.Width;
int nWidth = b.Width - (m.Size-1);
int nHeight = b.Height - (m.Size-2);
int nPixel = 0;
for (int y = 0; y < nHeight; y++)
{
for (int x = 0; x < nWidth; x++)
{
for (int r = 0; r < m.Height; r++)
{
nPixel = 0;
for (int i = 0; i < m.Width; i++)
for (int j = 0; j < m.Height; j++)
{
nPixel += (pSrc[(m.Width * (i + 1)) - 1 - r + stride * j] * m.Arr[j, i]);
}
nPixel /= m.Factor;
nPixel += m.Offset;
if (nPixel < 0) nPixel = 0;
if (nPixel > 255) nPixel = 255;
p[(m.Width * (m.Height / 2 + 1)) - 1 - r + stride * (m.Height / 2)] = (byte)nPixel;
}
p += m.Width;
pSrc += m.Width;
}
p += nOffset;
pSrc += nOffset;
}
}
b.UnlockBits(bmData);
bSrc.UnlockBits(bmSrc);
return b;
}
Please help
The problem is that your code explicitly stops short of the edges. The calculation for the limits for your outer loops (nWidth and nHeight) shouldn't involve the size of the matrix, they should be equal to the size of your bitmap.
When you do this, if you imagine what happens when you lay the center point of your matrix over each pixel in this case (because you need to read from all sizes of the pixel) the matrix will partially be outside of the image near the edges.
There are a few approaches as to what to do near the edges, but a reasonable one is to clamp the coordinates to the edges. I.e. when you would end up reading a pixel from outside the bitmap, just get the nearest pixel from the edge (size or corner).
I also don't understand why you need five loops - you seem to be looping through the height of the matrix twice. That doesn't look right. All in all the general structure should be something like this:
for (int y = 0; y < bitmap.Height; y++) {
for (int x = 0; x < bitmap.Width; x++) {
int sum = 0;
for (int matrixY = -matrix.Height/2; matrixY < matrix.Height/2; matrixY++)
for (int matrixX = -matrix.Width/2; matrixX < matrix.Width/2; matrixX++) {
// these coordinates will be outside the bitmap near all edges
int sourceX = x + matrixX;
int sourceY = y + matrixY;
if (sourceX < 0)
sourceX = 0;
if (sourceX >= bitmap.Width)
sourceX = bitmap.Width - 1;
if (sourceY < 0)
sourceY = 0;
if (sourceY >= bitmap.Height)
sourceY = bitmap.Height - 1;
sum += source[sourceX, sourceY];
}
}
// factor and clamp sum
destination[x, y] = sum;
}
}
You might need an extra loop to handle each color channel which need to be processed separately. I couldn't immediately see where in your code you might be doing that from all the cryptic variables.

Correctly executing bicubic resampling

I've been experimenting with the image bicubic resampling algorithm present in the AForge framework with the idea of introducing something similar into my image processing solution. See the original algorithm here and interpolation kernel here
Unfortunately I've hit a wall. It looks to me like somehow I am calculating the sample destination position incorrectly, probably due to the algorithm being designed for Format24bppRgb images where as I am using a Format32bppPArgb format.
Here's my code:
public Bitmap Resize(Bitmap source, int width, int height)
{
int sourceWidth = source.Width;
int sourceHeight = source.Height;
Bitmap destination = new Bitmap(width, height, PixelFormat.Format32bppPArgb);
destination.SetResolution(source.HorizontalResolution, source.VerticalResolution);
using (FastBitmap sourceBitmap = new FastBitmap(source))
{
using (FastBitmap destinationBitmap = new FastBitmap(destination))
{
double heightFactor = sourceWidth / (double)width;
double widthFactor = sourceHeight / (double)height;
// Coordinates of source points
double ox, oy, dx, dy, k1, k2;
int ox1, oy1, ox2, oy2;
// Width and height decreased by 1
int maxHeight = height - 1;
int maxWidth = width - 1;
for (int y = 0; y < height; y++)
{
// Y coordinates
oy = (y * widthFactor) - 0.5;
oy1 = (int)oy;
dy = oy - oy1;
for (int x = 0; x < width; x++)
{
// X coordinates
ox = (x * heightFactor) - 0.5f;
ox1 = (int)ox;
dx = ox - ox1;
// Destination color components
double r = 0;
double g = 0;
double b = 0;
double a = 0;
for (int n = -1; n < 3; n++)
{
// Get Y cooefficient
k1 = Interpolation.BiCubicKernel(dy - n);
oy2 = oy1 + n;
if (oy2 < 0)
{
oy2 = 0;
}
if (oy2 > maxHeight)
{
oy2 = maxHeight;
}
for (int m = -1; m < 3; m++)
{
// Get X cooefficient
k2 = k1 * Interpolation.BiCubicKernel(m - dx);
ox2 = ox1 + m;
if (ox2 < 0)
{
ox2 = 0;
}
if (ox2 > maxWidth)
{
ox2 = maxWidth;
}
Color color = sourceBitmap.GetPixel(ox2, oy2);
r += k2 * color.R;
g += k2 * color.G;
b += k2 * color.B;
a += k2 * color.A;
}
}
destinationBitmap.SetPixel(
x,
y,
Color.FromArgb(a.ToByte(), r.ToByte(), g.ToByte(), b.ToByte()));
}
}
}
}
source.Dispose();
return destination;
}
And the kernel which should represent the given equation on Wikipedia
public static double BiCubicKernel(double x)
{
if (x < 0)
{
x = -x;
}
double bicubicCoef = 0;
if (x <= 1)
{
bicubicCoef = (1.5 * x - 2.5) * x * x + 1;
}
else if (x < 2)
{
bicubicCoef = ((-0.5 * x + 2.5) * x - 4) * x + 2;
}
return bicubicCoef;
}
Here's the original image at 500px x 667px.
And the image resized to 400px x 543px.
Visually it appears that the image is over reduced and then the same pixels are repeatedly applied once we hit a particular point.
Can anyone give me some pointers here to solve this?
Note FastBitmap is a wrapper for Bitmap that uses LockBits to manipulate pixels in memory. It works well with everything else I apply it to.
Edit
As per request here's the methods involved in ToByte
public static byte ToByte(this double value)
{
return Convert.ToByte(ImageMaths.Clamp(value, 0, 255));
}
public static T Clamp<T>(T value, T min, T max) where T : IComparable<T>
{
if (value.CompareTo(min) < 0)
{
return min;
}
if (value.CompareTo(max) > 0)
{
return max;
}
return value;
}
You are limiting your ox2 and oy2 to destination image dimensions, instead of source dimensions.
Change this:
// Width and height decreased by 1
int maxHeight = height - 1;
int maxWidth = width - 1;
to this:
// Width and height decreased by 1
int maxHeight = sourceHeight - 1;
int maxWidth = sourceWidth - 1;
Well, I've met a very strange thing, which might be or might be not a souce of the problem.
I've started to try implementing convolution matrix by myself and encountered strange behaviour. I was testing code on a small image 4x4 pixels. The code is following:
var source = Bitmap.FromFile(#"C:\Users\Public\Pictures\Sample Pictures\Безымянный.png");
using (FastBitmap sourceBitmap = new FastBitmap(source))
{
for (int TY = 0; TY < 4; TY++)
{
for (int TX = 0; TX < 4; TX++)
{
Color color = sourceBitmap.GetPixel(TX, TY);
Console.Write(color.B.ToString().PadLeft(5));
}
Console.WriteLine();
}
}
Althought I'm printing out only blue channel value, it's still clearly incorrect.
On the other hand, your solution partitially works, what makes the thing I've found kind of irrelevant. One more guess I have: what is your system's DPI?
From what I have found helpfull, here are some links:
C++ implementation of bicubic interpolation on
matrix
C# implemetation of bicubic interpolation, lacking the part about rescaling
Thread on gamedev.net which has almost working solution
That's my answer so far, but I will try further.

Applying Gaussian blur to image in frequency domain

I've got torubles with appling gaussian blur to image in frequency domain.
For unknown reasons (probably I've dont something wrong) I recieve wired image instead of blurred one.
There's what i do step by step:
Load the image.
Split image into separate channels.
private static Bitmap[] separateColorChannels(Bitmap source, int channelCount)
{
if (channelCount != 3 && channelCount != 4)
{
throw new NotSupportedException("Bitmap[] FFTServices.separateColorChannels(Bitmap, int): Only 3 and 4 channels are supported.");
}
Bitmap[] result = new Bitmap[channelCount];
LockBitmap[] locks = new LockBitmap[channelCount];
LockBitmap sourceLock = new LockBitmap(source);
sourceLock.LockBits();
for (int i = 0; i < channelCount; ++i)
{
result[i] = new Bitmap(source.Width, source.Height, PixelFormat.Format8bppIndexed);
locks[i] = new LockBitmap(result[i]);
locks[i].LockBits();
}
for (int x = 0; x < source.Width; x++)
{
for (int y = 0; y < source.Height; y++)
{
switch (channelCount)
{
case 3:
locks[0].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).R));
locks[1].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).G));
locks[2].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).B));
break;
case 4:
locks[0].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).A));
locks[1].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).R));
locks[2].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).G));
locks[3].SetPixel(x, y, Color.FromArgb(sourceLock.GetPixel(x, y).B));
break;
default:
break;
}
}
}
for (int i = 0; i < channelCount; ++i)
{
locks[i].UnlockBits();
}
sourceLock.UnlockBits();
}
Convert every channel into complex images (with AForge.NET).
public static AForge.Imaging.ComplexImage[] convertColorChannelsToComplex(Emgu.CV.Image<Emgu.CV.Structure.Gray, Byte>[] channels)
{
AForge.Imaging.ComplexImage[] result = new AForge.Imaging.ComplexImage[channels.Length];
for (int i = 0; i < channels.Length; ++i)
{
result[i] = AForge.Imaging.ComplexImage.FromBitmap(channels[i].Bitmap);
}
return result;
}
Apply Gaussian blur.
First i create the kernel (For testing purposes kernel size is equal to image size, tho only center part of it is calculated with gaussian function, rest of kernel is equal to re=1 im=0).
private ComplexImage makeGaussKernel(int side, double min, double max, double step, double std)
{
// get value at top left corner
double _0x0 = gauss2d(min, min, std);
// top left corner should be 1, so making scaler for rest of the values
double scaler = 1 / _0x0;
int pow2 = SizeServices.getNextNearestPowerOf2(side);
Bitmap bitmap = new Bitmap(pow2, pow2, PixelFormat.Format8bppIndexed);
var result = AForge.Imaging.ComplexImage.FromBitmap(bitmap);
// For test purposes my kernel is size of image, so first, filling with 1 only.
for (int i = 0; i < result.Data.GetLength(0); ++i)
{
for (int j = 0; j < result.Data.GetLength(0); ++j)
{
result.Data[i, j].Re = 1;
result.Data[i, j].Im = 0;
}
}
// The real kernel's size.
int count = (int)((Math.Abs(max) + Math.Abs(min)) / step);
double h = min;
// Calculating kernel's values and storing them somewhere in the center of kernel.
for (int i = result.Data.GetLength(0) / 2 - count / 2; i < result.Data.GetLength(0) / 2 + count / 2; ++i)
{
double w = min;
for (int j = result.Data.GetLength(1) / 2 - count / 2; j < result.Data.GetLength(1) / 2 + count / 2; ++j)
{
result.Data[i, j].Re = (scaler * gauss2d(w, h, std)) * 255;
w += step;
}
h += step;
}
return result;
}
// The gauss function
private double gauss2d(double x, double y, double std)
{
return ((1.0 / (2 * Math.PI * std * std)) * Math.Exp(-((x * x + y * y) / (2 * std * std))));
}
Apply FFT to every channel and kernel.
Multiply center part of every channel by kernel.
void applyFilter(/*shortened*/)
{
// Image's size is 512x512 that's why 512 is hardcoded here
// min = -2.0; max = 2.0; step = 0.33; std = 11
ComplexImage filter = makeGaussKernel(512, min, max, step, std);
// Applies FFT (with AForge.NET) to every channel and filter
applyFFT(complexImage);
applyFFT(filter);
for (int i = 0; i < 3; ++i)
{
applyGauss(complexImage[i], filter, side);
}
// Applies IFFT to every channel
applyIFFT(complexImage);
}
private void applyGauss(ComplexImage complexImage, ComplexImage filter, int side)
{
int width = complexImage.Data.GetLength(1);
int height = complexImage.Data.GetLength(0);
for(int i = 0; i < height; ++i)
{
for(int j = 0; j < width; ++j)
{
complexImage.Data[i, j] = AForge.Math.Complex.Multiply(complexImage.Data[i, j], filter.Data[i, j]);
}
}
}
Apply IFFT to every channel.
Convert every channel back to bitmaps (with AForge.NET).
public static System.Drawing.Bitmap[] convertComplexColorChannelsToBitmap(AForge.Imaging.ComplexImage[] channels)
{
System.Drawing.Bitmap[] result = new System.Drawing.Bitmap[channels.Length];
for (int i = 0; i < channels.Length; ++i)
{
result[i] = channels[i].ToBitmap();
}
return result;
}
Merge bitmaps into single bitmap
public static Bitmap mergeColorChannels(Bitmap[] channels)
{
Bitmap result = null;
switch (channels.Length)
{
case 1:
return channels[0];
case 3:
result = new Bitmap(channels[0].Width, channels[0].Height, PixelFormat.Format24bppRgb);
break;
case 4:
result = new Bitmap(channels[0].Width, channels[0].Height, PixelFormat.Format32bppArgb);
break;
default:
throw new NotSupportedException("Bitmap FFTServices.mergeColorChannels(Bitmap[]): Only 1, 3 and 4 channels are supported.");
}
LockBitmap resultLock = new LockBitmap(result);
resultLock.LockBits();
LockBitmap red = new LockBitmap(channels[0]);
LockBitmap green = new LockBitmap(channels[1]);
LockBitmap blue = new LockBitmap(channels[2]);
red.LockBits();
green.LockBits();
blue.LockBits();
for (int y = 0; y < result.Height; y++)
{
for (int x = 0; x < result.Width; x++)
{
resultLock.SetPixel(x, y, Color.FromArgb((int)red.GetPixel(x, y).R, (int)green.GetPixel(x, y).G, (int)blue.GetPixel(x, y).B));
}
}
red.UnlockBits();
green.UnlockBits();
blue.UnlockBits();
resultLock.UnlockBits();
return result;
}
As a result I've got shifted, red-colored blurred version of image: link.
#edit - Updated the question with several changes to the code.
I figured it out with some help at DSP stackexchange... and some cheating but it works. The main problem was kernel generation and applying FFT to it. Also important thing is that AForge.NET divides image pixels by 255 during conversion to ComplexImage and multiplies by 255 during conversion from ComplexImage to Bitmap (thanks Olli Niemitalo # DSP SE).
How I solved this:
I've found how kernel should look like after FFT (see below).
Looked up colors of that image.
Calculated gauss2d for x = -2; y = -2; std = 1.
Calculated the prescaler to receive color value from value calculated in pt. 3 (see wolfram).
Generated kernel with scaled values with perscaler from pt. 4.
However I cant use FFT on generated filter, because generated filter looks like filter after FFT already. It works - the output image is blurred without artifacts so I think that's not too bad.
The images (I cant post more than 2 links, and images are farily big):
Input image
Generated filter (without FFT!)
Parameters for below function:
std = 1.0
size = 8.0
width = height = 512
Result image
The final code:
private ComplexImage makeGaussKernel(double size, double std, int imgWidth, int imgHeight)
{
double scale = 2000.0;
double hsize = size / 2.0;
Bitmap bmp = new Bitmap(imgWidth, imgHeight, PixelFormat.Format8bppIndexed);
LockBitmap lbmp = new LockBitmap(bmp);
lbmp.LockBits();
double y = -hsize;
double yStep = hsize / (lbmp.Height / 2.0);
double xStep = hsize / (lbmp.Width / 2.0);
for (int i = 0; i < lbmp.Height; ++i)
{
double x = -hsize;
for (int j = 0; j < lbmp.Width; ++j)
{
double g = gauss2d(x, y, std) * scale;
g = g < 0.0 ? 0.0 : g;
g = g > 255.0 ? 255.0 : g;
lbmp.SetPixel(j, i, Color.FromArgb((int)g));
x += xStep;
}
y += yStep;
}
lbmp.UnlockBits();
return ComplexImage.FromBitmap(bmp);
}
private double gauss2d(double x, double y, double std)
{
return (1.0 / (2 * Math.PI * std * std)) * Math.Exp(-(((x * x) + (y * y)) / (2 * std * std)));
}
private void applyGaussToImage(ComplexImage complexImage, ComplexImage filter)
{
for (int i = 0; i < complexImage.Height; ++i)
{
for (int j = 0; j < complexImage.Width; ++j)
{
complexImage.Data[i, j] = AForge.Math.Complex.Multiply(complexImage.Data[i, j], filter.Data[i, j]);
}
}
}

Draw rotated sine image

I'm working with 2D Fourier transforms, and I have a method that will output the following result:
The code looks like this:
private Bitmap PaintSin(int s, int n)
{
Data = new int[Width, Height];
Bitmap buffer = new Bitmap(Width, Height);
double inner = (2 * Math.PI * s) / n;
BitmapData originalData = buffer.LockBits(
new Rectangle(0, 0, buffer.Width, buffer.Height),
ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
for (int i = 0; i < buffer.Width; i++)
{
for (int j = 0; j < buffer.Height; j++)
{
double val;
int c = 0;
if (j == 0)
{
val = Math.Sin(inner * i);
val += 1;
val *= 128;
val = val > 255 ? 255 : val;
c = (int)val;
Color col = Color.FromArgb(c, c, c);
SetPixel(originalData, i, j, col);
}
else
SetPixel(originalData, i, j, GetColor(originalData, i, 0));
Data[i, j] = c;
}
}
buffer.UnlockBits(originalData);
return buffer;
}
Now, I'm trying to think of a formula that will accept an angle and will out put an image where the solid lines are at the given angle.
For example, if I inputted 45 Degrees, the result would be:
Thank you for any help!
Here is the SetPixel code if that is needed:
private unsafe void SetPixel(BitmapData originalData, int x, int y, Color color)
{
//set the number of bytes per pixel
int pixelSize = 3;
//get the data from the original image
byte* oRow = (byte*)originalData.Scan0 + (y * originalData.Stride);
//set the new image's pixel to the grayscale version
oRow[x * pixelSize] = color.B; //B
oRow[x * pixelSize + 1] = color.G; //G
oRow[x * pixelSize + 2] = color.R; //R
}
This should be possible by using a rotated coordinate system. Transformation of i and j is as following:
x = i * cos(angle) - j * sin(angle);
y = j * cos(angle) + i * sin(angle);
Note: I'm not sure about degree/radians here, so adjust angle so it fits to the unit that cos and sin do need. Also, you might have to negate the angle depending on your desired rotation direction.
In fact, you only need x which replaces your use of i. We'll precompute sin(angle) and cos(angle) because these are quite costy operations we don't want in inner loops. Additionaly, your optimization is removed as we can't draw only one line and repeat it:
[...]
// double angle = ...
double cos_angle = cos(angle);
double sin_angle = sin(angle);
for (int i = 0; i < buffer.Width; i++)
{
for (int j = 0; j < buffer.Height; j++)
{
double val;
double x;
int c = 0;
x = i * cos_angle - j * sin_angle;
val = Math.Sin(inner * x);
val += 1;
val *= 128;
val = val > 255 ? 255 : val;
c = (int)val;
Color col = Color.FromArgb(c, c, c);
SetPixel(originalData, i, j, col);
Data[i, j] = c;
}
}
You can write a simple function rotate(x,y,angle) and use its result in SetPixel. You can google for rotation matrix.
A call with angle = 0, should produce default output.

Categories

Resources