MSE calculation for grayscale images - c#

I have two images(original and noisy). I'm calculating PSNR. I kinda did it for color RGB images, but i don't know how to do it with grayscale. As i read, MSE calculation is different. For RGB i'm doing it like you can see in following code (I'm using Visual C#):
for (int i = 0; i < bmp1.Width; i++)
{
for (int j = 0; j < bmp1.Height; j++)
{
mseR += Math.Pow(bmp1.GetPixel(i, j).R - bmp2.GetPixel(i, j).R, 2);
mseG += Math.Pow(bmp1.GetPixel(i, j).G - bmp2.GetPixel(i, j).G, 2);
mseB += Math.Pow(bmp1.GetPixel(i, j).B - bmp2.GetPixel(i, j).B, 2);
}
}
mse = (mseR + mseG + mseB) / ((bmp1.Width * bmp1.Height) * 3);
Here I am manipulating with R,G,B of pixels.But i don't know what should i take in case of grayscale images. Can I use RGB aswell, because it actually gives some results, or i should take something else?

To make grayscale you can make the picture out of averages (no need to vary your implementation). I'm assuming your images are bmp1 = grayImage and bmp2 = noisy image.
for (int i = 0; i < bmp1.Width; i++)
{
for (int j = 0; j < bmp1.Height; j++)
{
// As a grayscale image has rthe same color on all RGB just pick one
int gray1 = bmp1.GetPixel(i, j).R;
int gray2 = bmp2.GetPixel(i, j).R;
double sum = Math.Pow(gray1 - gray2, 2)
mseGray += sum;
}
}
mse = (mseGray) / ((bmp1.Width * bmp1.Height) * 3);
Also getting pixels one at a time is a slow process look into using the indexes, and a optimization in the loop. It should give about a tenfold in performance.
You need to make the bitmap into an indexable img, I'm assuming its BitmapSource for this example. the interesting part is the loop and the index building and not the precode, the precode is just to make the image indexable.
var height = bmp1.Height;
var width = bmp1.Width;
var pixelBytes1 = new byte[height * width * 4];
var pixelBytes2 = new byte[height * width * 4];
bmp1.CopyPixels(pixelBytes1, stride, 0);
bmp2.CopyPixels(pixelBytes2, stride, 0);
for (int x = 0; x < width; x++)
{
int woff = x * height;
for (int y = 0; y < height; y++)
{(R*0.3 + G*0.59+ B*0.11)
int index = woff + y;
int gray1 = bmp1[index];
int gray2 = bmp2[index];
double sum = Math.Pow(gray1 - gray2, 2)
mseGray += sum;
}
}
mse = (mseGray) / ((bmp1.Width * bmp1.Height) * 3);
EDIT:
http://www.mathworks.com/matlabcentral/answers/49906-how-to-calculate-psnr-of-compressed-images-and-how-to-compare-psnr-of-images-compressed-by-two-diff
I'm having an issue with your implementation of PSNR though im thinking its not per definition
here is an example from java (very similar to C#)
http://www.cyut.edu.tw/~yltang/program/Psnr.java

Related

Image convolution in spatial domain

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));
}
}
}

Performance issue while converting Rgb image to grayscale C# Code

I am writing a .Net wrapper for Tesseract Ocr and if I use a grayscale image instead of rgb image as an input file to it then results are pretty good.
So I was searching the web for C# solution to convert a Rgb image to grayscale image and I found this code.
This performs 3 operations to increase the accuracy of tesseract.
Resize the image
then convert into grayscale image and remove noise from image
Now this converted image gives almost 90% accurate results.
//Resize
public Bitmap Resize(Bitmap bmp, int newWidth, int newHeight)
{
Bitmap temp = (Bitmap)bmp;
Bitmap bmap = new Bitmap(newWidth, newHeight, temp.PixelFormat);
double nWidthFactor = (double)temp.Width / (double)newWidth;
double nHeightFactor = (double)temp.Height / (double)newHeight;
double fx, fy, nx, ny;
int cx, cy, fr_x, fr_y;
Color color1 = new Color();
Color color2 = new Color();
Color color3 = new Color();
Color color4 = new Color();
byte nRed, nGreen, nBlue;
byte bp1, bp2;
for (int x = 0; x < bmap.Width; ++x)
{
for (int y = 0; y < bmap.Height; ++y)
{
fr_x = (int)Math.Floor(x * nWidthFactor);
fr_y = (int)Math.Floor(y * nHeightFactor);
cx = fr_x + 1;
if (cx >= temp.Width)
cx = fr_x;
cy = fr_y + 1;
if (cy >= temp.Height)
cy = fr_y;
fx = x * nWidthFactor - fr_x;
fy = y * nHeightFactor - fr_y;
nx = 1.0 - fx;
ny = 1.0 - fy;
color1 = temp.GetPixel(fr_x, fr_y);
color2 = temp.GetPixel(cx, fr_y);
color3 = temp.GetPixel(fr_x, cy);
color4 = temp.GetPixel(cx, cy);
// Blue
bp1 = (byte)(nx * color1.B + fx * color2.B);
bp2 = (byte)(nx * color3.B + fx * color4.B);
nBlue = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
// Green
bp1 = (byte)(nx * color1.G + fx * color2.G);
bp2 = (byte)(nx * color3.G + fx * color4.G);
nGreen = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
// Red
bp1 = (byte)(nx * color1.R + fx * color2.R);
bp2 = (byte)(nx * color3.R + fx * color4.R);
nRed = (byte)(ny * (double)(bp1) + fy * (double)(bp2));
bmap.SetPixel(x, y, System.Drawing.Color.FromArgb(255, nRed, nGreen, nBlue));
}
}
//here i included the below to functions logic without the for loop to remove repetitive use of for loop but it did not work and taking the same time.
bmap = SetGrayscale(bmap);
bmap = RemoveNoise(bmap);
return bmap;
}
//SetGrayscale
public Bitmap SetGrayscale(Bitmap img)
{
Bitmap temp = (Bitmap)img;
Bitmap bmap = (Bitmap)temp.Clone();
Color c;
for (int i = 0; i < bmap.Width; i++)
{
for (int j = 0; j < bmap.Height; j++)
{
c = bmap.GetPixel(i, j);
byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);
bmap.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
}
}
return (Bitmap)bmap.Clone();
}
//RemoveNoise
public Bitmap RemoveNoise(Bitmap bmap)
{
for (var x = 0; x < bmap.Width; x++)
{
for (var y = 0; y < bmap.Height; y++)
{
var pixel = bmap.GetPixel(x, y);
if (pixel.R < 162 && pixel.G < 162 && pixel.B < 162)
bmap.SetPixel(x, y, Color.Black);
}
}
for (var x = 0; x < bmap.Width; x++)
{
for (var y = 0; y < bmap.Height; y++)
{
var pixel = bmap.GetPixel(x, y);
if (pixel.R > 162 && pixel.G > 162 && pixel.B > 162)
bmap.SetPixel(x, y, Color.White);
}
}
return bmap;
}
But the problem is it takes lot of time to convert it
So I included SetGrayscale(Bitmap bmap)
RemoveNoise(Bitmap bmap) function logic inside the Resize() method to remove repetitive use of for loop
but it did not solve my problem.
The Bitmap class's GetPixel() and SetPixel() methods are notoriously slow for multiple read/writes. A much faster way to access and set individual pixels in a bitmap is to lock it first.
There's a good example here on how to do that, with a nice class LockedBitmap to wrap around the stranger Marshaling code.
Essentially what it does is use the LockBits() method in the Bitmap class, passing a rectangle for the region of the bitmap you want to lock, and then copy those pixels from its unmanaged memory location to a managed one for easier access.
Here's an example on how you would use that example class with your SetGrayscale() method:
public Bitmap SetGrayscale(Bitmap img)
{
LockedBitmap lockedBmp = new LockedBitmap(img.Clone());
lockedBmp.LockBits(); // lock the bits for faster access
Color c;
for (int i = 0; i < lockedBmp.Width; i++)
{
for (int j = 0; j < lockedBmp.Height; j++)
{
c = lockedBmp.GetPixel(i, j);
byte gray = (byte)(.299 * c.R + .587 * c.G + .114 * c.B);
lockedBmp.SetPixel(i, j, Color.FromArgb(gray, gray, gray));
}
}
lockedBmp.UnlockBits(); // remember to release resources
return lockedBmp.Bitmap; // return the bitmap (you don't need to clone it again, that's already been done).
}
This wrapper class has saved me a ridiculous amount of time in bitmap processing. Once you've implemented this in all your methods, preferably only calling LockBits() once, then I'm sure your application's performance will improve tremendously.
I also see that you're cloning the images a lot. This probably doesn't take up as much time as the SetPixel()/GetPixel() thing, but its time can still be significant especially with larger images.
The easiest way would be to redraw the image onto itself using DrawImage and passing a suitable ColorMatrix. Google for ColorMatrix and gray scale and you'll find a ton of examples, this one for example: http://www.codeproject.com/Articles/3772/ColorMatrix-Basics-Simple-Image-Color-Adjustment

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]);
}
}
}

Categories

Resources