我在频域中对图像应用高斯模糊有很多困难。由于未知的原因(可能我没有做错什么),我收到了有线图像而不是模糊图像。
这是我一步一步做的:
- 加载图像
-
将图像拆分为单独的通道。
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(); }
-
将每个通道转换为复杂的图像(使用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; }
-
应用高斯模糊。
-
首先,我创建内核(出于测试目的,内核大小等于图像大小,只有它的中心部分是用高斯函数计算的,内核的其余部分等于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)))); }
-
将FFT应用于每个通道和内核。
-
将每个通道的中心部分乘以内核。
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]); } } }
-
- 将IFFT应用于每个通道
-
将每个通道转换回位图(使用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; }
-
将位图合并为单个位图
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; }
结果我得到了移动,红色模糊版本的图像:链接。
@edit-通过对代码的几处更改更新了问题。
我在DSP stackexchange上得到了一些帮助。。。还有一些作弊行为,但确实有效。主要的问题是内核生成和FFT的应用。同样重要的是,AForge.NET在转换为ComplexImage时将图像像素除以255,在从ComplexImage转换为位图时乘以255(感谢Olli Niemialo@DSP SE)。
我是如何解决这个问题的:
- 我发现了FFT后内核应该是什么样子(见下文)
- 查找了该图像的颜色
- x=-2时的计算gauss2d;y=-2;std=1
- 计算预分频器以从pt.3中计算的值接收颜色值(参见wolfram)
- 从pt.4生成具有缩放值的内核
然而,我不能在生成的滤波器上使用FFT,因为生成的滤波器看起来已经像FFT之后的滤波器了。它是有效的-输出图像是模糊的,没有伪影,所以我认为这还不错。
图片(我不能发布超过2个链接,图片大得离谱):
- 输入图像
- 生成的滤波器(无FFT!)
- 以下函数的参数:
- std=1.0
- 大小=8.0
- 宽度=高度=512
- 结果图像
最终代码:
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]);
}
}
}