Image convolution in frequency domain - c#

I want to Convolve Lena with itself in the Frequency Domain. Here is an excerpt from a book. which suggests how should the output of the convolution be:
I have written the following application to achieve the Convolution of two images in the frequency domain. The steps I followed are as follows:
Convert Lena into a matrix of complex numbers.
Apply FFT to obtain a complex matrix.
Multiply two complex matrices element by element (if that is the definition of Convolution).
Apply IFFT to the result of the multiplication.
The output seems to be not coming as expected:
Two issues are visible here:
The output only contains a black background with only one dot at its center.
The original image gets distorted after the the execution of convolution.
.
Note. FFT and I-FFT are working perfectly with the same libraries.
Note-2. There is a thread in SO that seems to be discussing the same topic.
.
Source Code:
public static class Convolution
{
public static Complex[,] Convolve(Complex[,]image, Complex[,]mask)
{
Complex[,] convolve = null;
int imageWidth = image.GetLength(0);
int imageHeight = image.GetLength(1);
int maskWidth = mask.GetLength(0);
int maskeHeight = mask.GetLength(1);
if (imageWidth == maskWidth && imageHeight == maskeHeight)
{
FourierTransform ftForImage = new FourierTransform(image); ftForImage.ForwardFFT();
FourierTransform ftForMask = new FourierTransform(mask); ftForMask.ForwardFFT();
Complex[,] fftImage = ftForImage.FourierTransformedImageComplex;
Complex[,] fftKernel = ftForMask.FourierTransformedImageComplex;
Complex[,] fftConvolved = new Complex[imageWidth, imageHeight];
for (int i = 0; i < imageWidth; i++)
{
for (int j = 0; j < imageHeight; j++)
{
fftConvolved[i, j] = fftImage[i, j] * fftKernel[i, j];
}
}
FourierTransform ftForConv = new FourierTransform();
ftForConv.InverseFFT(fftConvolved);
convolve = ftForConv.GrayscaleImageComplex;
//convolve = fftConvolved;
}
else
{
throw new Exception("padding needed");
}
return convolve;
}
}
private void convolveButton_Click(object sender, EventArgs e)
{
Bitmap lena = inputImagePictureBox.Image as Bitmap;
Bitmap paddedMask = paddedMaskPictureBox.Image as Bitmap;
Complex[,] cLena = ImageDataConverter.ToComplex(lena);
Complex[,] cPaddedMask = ImageDataConverter.ToComplex(paddedMask);
Complex[,] cConvolved = Convolution.Convolve(cLena, cPaddedMask);
Bitmap convolved = ImageDataConverter.ToBitmap(cConvolved);
convolvedImagePictureBox.Image = convolved;
}

There is a difference in how you call InverseFFT between the working FFT->IFFT application, and the broken Convolution application. In the latter case you do not pass explicitly the Width and Height parameters (which you are supposed to get from the input image):
public void InverseFFT(Complex[,] fftImage)
{
if (FourierTransformedImageComplex == null)
{
FourierTransformedImageComplex = fftImage;
}
GrayscaleImageComplex = FourierFunction.FFT2D(FourierTransformedImageComplex, Width, Height, -1);
GrayscaleImageInteger = ImageDataConverter.ToInteger(GrayscaleImageComplex);
InputImageBitmap = ImageDataConverter.ToBitmap(GrayscaleImageInteger);
}
As a result both Width and Height are 0 and the code skips over most of the inverse 2D transformation. Initializing those parameters should give you something which is at least not all black.
if (FourierTransformedImageComplex == null)
{
FourierTransformedImageComplex = fftImage;
Width = fftImage.GetLength(0);
Height = fftImage.GetLength(1);
}
Then you should notice some sharp white/black edges. Those are caused by wraparounds in the output values. To avoid this, you may want to rescale the output after the inverse transform to fit the available scale with something such as:
double maxAmp = 0.0;
for (int i = 0; i < imageWidth; i++)
{
for (int j = 0; j < imageHeight; j++)
{
maxAmp = Math.Max(maxAmp, convolve[i, j].Magnitude);
}
}
double scale = 255.0 / maxAmp;
for (int i = 0; i < imageWidth; i++)
{
for (int j = 0; j < imageHeight; j++)
{
convolve[i, j] = new Complex(convolve[i, j].Real * scale, convolve[i, j].Imaginary * scale);
maxAmp = Math.Max(maxAmp, convolve[i, j].Magnitude);
}
}
This should then give the more reasonable output:
However that is still not as depicted in your book. At this point we have a 2D circular convolution. To get a 2D linear convolution, you need to make sure the images are both padded to the sum of the dimensions:
Bitmap lena = inputImagePictureBox.Image as Bitmap;
Bitmap mask = paddedMaskPictureBox.Image as Bitmap;
Bitmap paddedLena = ImagePadder.Pad(lena, lena.Width+ mask.Width, lena.Height+ mask.Height);
Bitmap paddedMask = ImagePadder.Pad(mask, lena.Width+ mask.Width, lena.Height+ mask.Height);
Complex[,] cLena = ImageDataConverter.ToComplex(paddedLena);
Complex[,] cPaddedMask = ImageDataConverter.ToComplex(paddedMask);
Complex[,] cConvolved = Convolution.Convolve(cLena, cPaddedMask);
And as you adjust the padding, you may want to change the padding color to black otherwise your padding will in itself introduce a large correlation between the two images:
public class ImagePadder
{
public static Bitmap Pad(Bitmap maskImage, int newWidth, int newHeight)
{
...
Grayscale.Fill(resizedImage, Color.Black);
Now you should be getting the following:
We are getting close, but the peak of the autocorrelation result is not in the center, and that's because you FourierShifter.FFTShift in the forward transform but do not call the corresponding FourierShifter.RemoveFFTShift in the inverse transform. If we adjust those (either remove FFTShift in ForwardFFT, or add RemoveFFTShift in InverseFFT), then we finally get:

Related

Convert ARGB to PARGB

I've been looking for a fast alternative method of SetPixel() and I have found this link : C# - Faster Alternatives to SetPixel and GetPixel for Bitmaps for Windows Forms App
So my problem is that I've an image and I want to create a copy as a DirectBitmap object but first I need to convert ARGB to PARGB so I used this code:
public static Color PremultiplyAlpha(Color pixel)
{
return Color.FromArgb(
pixel.A,
PremultiplyAlpha_Component(pixel.R, pixel.A),
PremultiplyAlpha_Component(pixel.G, pixel.A),
PremultiplyAlpha_Component(pixel.B, pixel.A));
}
private static byte PremultiplyAlpha_Component(byte source, byte alpha)
{
return (byte)((float)source * (float)alpha / (float)byte.MaxValue + 0.5f);
}
and Here's my copy code:
DirectBitmap DBMP = new DirectBitmap(img.Width, img.Height);
MyImage myImg = new MyImage(img as Bitmap);
for (int i = 0; i < img.Width; i++)
{
for (int j = 0; j < img.Height; j++)
{
Color PARGB = NativeWin32.PremultiplyAlpha(Color.FromArgb(myImg.RGB[i, j].Alpha,
myImg.RGB[i, j].R, myImg.RGB[i, j].G, myImg.RGB[i, j].B));
byte[] bitMapData = new byte[4];
bitMapData[3] = (byte)PARGB.A;
bitMapData[2] = (byte)PARGB.R;
bitMapData[1] = (byte)PARGB.G;
bitMapData[0] = (byte)PARGB.B;
DBMP.Bits[(i * img.Height) + j] = BitConverter.ToInt32(bitMapData, 0);
}
}
MyImage : a class containing a Bitmap object along with an array of struct RGB storing the colors of each pixel
However, this code gives me a messed up image. what am I doing wrong?
Bitmap data is organized horizontal line after horizontal line. Therefore, your last line should be:
DBMP.Bits[j * img.Width + i] = BitConverter.ToInt32(bitMapData, 0);

Calculation of correlation coefficient

In this research article, in the section 4.3.1 (core detection),
How can I calculate correlation coefficients between two pixels?
public static Complex[,] Correlation(Complex[,]image, Complex[,]mask)
{
Complex[,] convolve = null;
int imageWidth = image.GetLength(0);
int imageHeight = image.GetLength(1);
int maskWidth = mask.GetLength(0);
int maskeHeight = mask.GetLength(1);
if (imageWidth == maskWidth && imageHeight == maskeHeight)
{
FourierTransform ftForImage = new FourierTransform(image); ftForImage.ForwardFFT();
FourierTransform ftForMask = new FourierTransform(mask); ftForMask.ForwardFFT();
Complex[,] fftImage = ftForImage.FourierImageComplex;
Complex[,] fftKernel = ftForMask.FourierImageComplex;
Complex[,] fftConvolved = new Complex[imageWidth, imageHeight];
for (int j = 0; j < imageHeight; j++)
{
for (int i = 0; i < imageWidth; i++)
{
fftConvolved[i,j] = Complex.Conjugate(fftImage[i,j]) * fftKernel[i,j];
}
}
FourierTransform ftForConv = new FourierTransform();
ftForConv.InverseFFT(fftConvolved);
convolve = ftForConv.GrayscaleImageComplex;
Rescale(convolve);
convolve = FourierShifter.FFTShift(convolve);
}
else
{
throw new Exception("padding needed");
}
return convolve;
}
Is this the correct procedure to calculate correlations?
If yes, how can I find Correlation-coefficients from that?
In article correlation are calculated between two "windows", i.e. between two sets of points, not between two points. if i'm not mistaken, correlation coefficient is a scalar value, not a vector. In signal processing , correlation calculated as sum of multiplications divided by sum of squares of signal values. It may be incorrect in details, but in general, correlation calculated like this:
correlation = sum(S1[i]*S2[i])/sqrt(sum(S1[i]^2 * S2[i]^2));
For 2-dimention case (window) just add second index:
correlation = sum(S1[i,j]*S2[i,j])/sqrt(sum(S1[i,j]^2 * S2[i,j]^2));

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

C# Normalizing RGB and creating a new image

I am trying to create a program that accepts an image, recursively goes through each pixel, normalizes the pixel and re-creates a NEW image that looks the same as the original, but has normalized pixels instead.
public void parseJpeg(String jpegPath)
{
var normalizedRed = 0.0;
var normalizedGreen = 0.0;
var normalizedBlue = 0.0;
Bitmap normalizedImage = null;
var image = new Bitmap(jpegPath);
normalizedImage = new Bitmap(image.Width, image.Height);
for (int x = 0; x < image.Width; ++x)
{
for (int y = 0; y < image.Height; ++y)
{
Color color = image.GetPixel(x, y);
double exponent = 2;
double redDouble = Convert.ToDouble(color.R);
double blueDouble = Convert.ToDouble(color.B);
double greenDouble = Convert.ToDouble(color.G);
double redResult = Math.Pow(redDouble, exponent);
double blueResult = Math.Pow(blueDouble, exponent);
double greenResult = Math.Pow(greenDouble, exponent);
double totalResult = redResult + blueResult + greenResult;
normalizedRed = Convert.ToDouble(color.R) / Math.Sqrt(totalResult);
normalizedGreen = Convert.ToDouble(color.G) / Math.Sqrt(totalResult);
normalizedBlue = Convert.ToDouble(color.B) / Math.Sqrt(totalResult);
Color newCol = Color.FromArgb(Convert.ToInt32(normalizedRed), Convert.ToInt32(normalizedGreen), Convert.ToInt32(normalizedBlue));
normalizedImage.SetPixel(x, y, newCol);
}
}
normalizedImage.Save("C:\\Users\\username\\Desktop\\test1.jpeg");
resultsViewBox.AppendText("Process completed.\n");
}
Using the above code produces all black pixels and I do not understand why. When it normalizes it sets RGB = 1. After normalization, how do I set pixels with the NEW normalized value?
When I perform the below code, I get a black and blue image in my preview, but when I open the file it's blank. This is better than what I was getting before, which was ALL black pixels. This only works on one image though. So I am not sure how much of a step forward it is.
public void parseJpeg(String jpegPath)
{
Bitmap normalizedImage = null;
var image = new Bitmap(jpegPath);
normalizedImage = new Bitmap(image.Width, image.Height);
for (int x = 0; x < image.Width; ++x)
{
for (int y = 0; y < image.Height; ++y)
{
Color color = image.GetPixel(x, y);
float norm = (float)System.Math.Sqrt(color.R * color.R + color.B * color.B + color.G * color.G);
Color newCol = Color.FromArgb(Convert.ToInt32(norm));
normalizedImage.SetPixel(x, y, newCol);
}
}
normalizedImage.Save("C:\\Users\\username\\Desktop\\test1.jpeg");
resultsViewBox.AppendText("Process completed.\n");
}
I found the code for what I was trying to do:
http://www.lukehorvat.com/blog/normalizing-image-brightness-in-csharp/
public void parseJpeg(String jpegPath)
{
var image = new Bitmap(jpegPath);
normalizedImage = new Bitmap(image.Width, image.Height);
for (int x = 0; x < image.Width; ++x)
{
for (int y = 0; y < image.Height; ++y)
{
float pixelBrightness = image.GetPixel(x, y).GetBrightness();
minBrightness = Math.Min(minBrightness, pixelBrightness);
maxBrightness = Math.Max(maxBrightness, pixelBrightness);
}
}
for (int x = 0; x < image.Width; x++)
{
for (int y = 0; y < image.Height; y++)
{
Color pixelColor = image.GetPixel(x, y);
float normalizedPixelBrightness = (pixelColor.GetBrightness() - minBrightness) / (maxBrightness - minBrightness);
Color normalizedPixelColor = ColorConverter.ColorFromAhsb(pixelColor.A, pixelColor.GetHue(), pixelColor.GetSaturation(), normalizedPixelBrightness);
normalizedImage.SetPixel(x, y, normalizedPixelColor);
}
}
normalizedImage.Save("C:\\Users\\username\\Desktop\\test1.jpeg");
resultsViewBox.AppendText("Process completed.\n");
}
You are creating a new Bitmap and saving over the file for every pixel in your image. Move the
normalizedImage = new Bitmap(image.Width, image.Height);
line to before your loops, and the
normalizedImage.Save("C:\\Users\\username\\Desktop\\test1.jpeg");
line to after your loops.
Your normalization algorithm does not appear to be correct. Let's say your original color was red (255,0,0) Then your totalResult will be 65025, and your normalizedRed will be 255/sqrt(65025), which is 1, giving you a new normalized color of (1,0,0), which is essentially black.
Just as a note, your code will run a bit faster if you define all the doubles once outside the look and then assign them within the loop rather than defining and deleting each of the 8 doubles each iteration
Instead of messing with the colors you should use the brightness or luminosity factor to achieve normalization. Here is a link to the already answered question that can help you. you can convert each RGB pixel to HSL and minupulate L factor:
How do I normalize an image?
The code that you shared is actually a trim down version of HSL manipulation.

How can I speed up this histogram class?

This is supposed to calculate the histogram of an 8-bit grayscale image. With a 1024x770 test bitmap, CreateTime ends up at around 890ms. How can I make this go (way, way) faster?
EDIT: I should mention that this doesn't actually compute the histogram yet, it only gets the values out of the bitmap. So I really should have asked, what is the fastest way to retrieve all pixel values from an 8-bit grayscale image?
public class Histogram {
private static int[,] values;
public Histogram(Bitmap b) {
var sw = Stopwatch.StartNew();
values = new int[b.Width, b.Height];
for (int w = 0; w < b.Width; ++w) {
for (int h = 0; h < b.Height; ++h) {
values[w, h] = b.GetPixel(w, h).R;
}
}
sw.Stop();
CreateTime = (sw.ElapsedTicks /
(double)Stopwatch.Frequency) * 1000;
}
public double CreateTime { get; set; }
}
The basic histogram algorithm is something like:
int[] hist = new hist[256];
//at this point dont forget to initialize your vector with 0s.
for(int i = 0; i < height; ++i)
{
for(int j = 0 ; j < widthl ++j)
{
hist[ image[i,j] ]++;
}
}
The algorithm sums how many pixels with value 0 you have, how many with value=1 and so on.
The basic idea is to use the pixel value as the index to the position of the histogram where you will count.
I have one version of this algorithm written for C# using unmanaged code (which is fast) I dont know if is faster than your but feel free to take it and test, here is the code:
public void Histogram(double[] histogram, Rectangle roi)
{
BitmapData data = Util.SetImageToProcess(image, roi);
if (image.PixelFormat != PixelFormat.Format8bppIndexed)
return;
if (histogram.Length < Util.GrayLevels)
return;
histogram.Initialize();
int width = data.Width;
int height = data.Height;
int offset = data.Stride - width;
unsafe
{
byte* ptr = (byte*)data.Scan0;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x, ++ptr)
histogram[ptr[0]]++;
ptr += offset;
}
}
image.UnlockBits(data);
}
static public BitmapData SetImageToProcess(Bitmap image, Rectangle roi)
{
if (image != null)
return image.LockBits(
roi,
ImageLockMode.ReadWrite,
image.PixelFormat);
return null;
}
I hope I could help you.
You'll want to use the Bitmap.LockBits method to access the pixel data. This is a good reference on the process. Essentially, you're going to need to use unsafe code to iterate over the bitmap data.
Here's a copy/pastable version of the function I've come up w/ based on on this thread.
The unsafe code expects the bitmap to be Format24bppRgb, and if it's not, it'll convert the bitmap to that format and operate on the cloned version.
Note that the call to image.Clone() will throw if you pass in a bitmap using an indexed pixel format, such as Format4bppIndexed.
Takes ~200ms to get a histogram from an image 9100x2048 on my dev machine.
private long[] GetHistogram(Bitmap image)
{
var histogram = new long[256];
bool imageWasCloned = false;
if (image.PixelFormat != PixelFormat.Format24bppRgb)
{
//the unsafe code expects Format24bppRgb, so convert the image...
image = image.Clone(new Rectangle(0, 0, image.Width, image.Height), PixelFormat.Format24bppRgb);
imageWasCloned = true;
}
BitmapData bmd = null;
try
{
bmd = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly,
PixelFormat.Format24bppRgb);
const int pixelSize = 3; //pixels are 3 bytes each w/ Format24bppRgb
//For info on locking the bitmap bits and finding the
//pixels using unsafe code, see http://www.bobpowell.net/lockingbits.htm
int height = bmd.Height;
int width = bmd.Width;
int rowPadding = bmd.Stride - (width * pixelSize);
unsafe
{
byte* pixelPtr = (byte*)bmd.Scan0;//starts on the first row
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
histogram[(pixelPtr[0] + pixelPtr[1] + pixelPtr[2]) / 3]++;
pixelPtr += pixelSize;//advance to next pixel in the row
}
pixelPtr += rowPadding;//advance ptr to the next pixel row by skipping the padding # the end of each row.
}
}
}
finally
{
if (bmd != null)
image.UnlockBits(bmd);
if (imageWasCloned)
image.Dispose();
}
return histogram;
}

Categories

Resources