将高斯模糊应用于频域图像
Applying Gaussian blur to image in frequency domain
我对在频域中对图像应用高斯模糊感到困惑。
出于未知原因(可能我没有错)我收到有线图像而不是模糊图像。
这是我一步一步做的:
- 加载图像。
将图像拆分为单独的通道。
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;
}
因此,我得到了移位的红色模糊图像版本:link。
@edit - 通过对代码进行几处更改来更新问题。
我在 DSP stackexchange 的一些帮助下弄明白了...和一些作弊但它有效。主要问题是内核生成和对其应用 FFT。同样重要的是,AForge.NET 在转换为 ComplexImage 期间将图像像素除以 255,并在从 ComplexImage 转换为位图期间乘以 255(感谢 Olli Niemitalo @ DSP SE)。
我是如何解决这个问题的:
- 我发现内核在 FFT 后应该是什么样子(见下文)。
- 查找了该图像的颜色。
- 计算出 x = -2 的 gauss2d; y = -2;标准 = 1.
- 计算预分频器以从 pt 中计算的值接收颜色值。 3(参见 wolfram)。
- 使用 pt 的 perscaler 生成具有缩放值的内核。 4.
但是我不能在生成的过滤器上使用 FFT,因为生成的过滤器看起来已经像 FFT 之后的过滤器了。它有效 - 输出图像模糊,没有伪影,所以我认为这还不错。
图片(我不能 post 超过 2 个链接,而且图片非常大):
- Input image
- Generated filter (without FFT!)
- 以下函数的参数:
- 标准 = 1.0
- 大小 = 8.0
- 宽=高=512
- Result image
最终代码:
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]);
}
}
}
我对在频域中对图像应用高斯模糊感到困惑。 出于未知原因(可能我没有错)我收到有线图像而不是模糊图像。
这是我一步一步做的:
- 加载图像。
将图像拆分为单独的通道。
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; }
因此,我得到了移位的红色模糊图像版本:link。
@edit - 通过对代码进行几处更改来更新问题。
我在 DSP stackexchange 的一些帮助下弄明白了...和一些作弊但它有效。主要问题是内核生成和对其应用 FFT。同样重要的是,AForge.NET 在转换为 ComplexImage 期间将图像像素除以 255,并在从 ComplexImage 转换为位图期间乘以 255(感谢 Olli Niemitalo @ DSP SE)。
我是如何解决这个问题的:
- 我发现内核在 FFT 后应该是什么样子(见下文)。
- 查找了该图像的颜色。
- 计算出 x = -2 的 gauss2d; y = -2;标准 = 1.
- 计算预分频器以从 pt 中计算的值接收颜色值。 3(参见 wolfram)。
- 使用 pt 的 perscaler 生成具有缩放值的内核。 4.
但是我不能在生成的过滤器上使用 FFT,因为生成的过滤器看起来已经像 FFT 之后的过滤器了。它有效 - 输出图像模糊,没有伪影,所以我认为这还不错。
图片(我不能 post 超过 2 个链接,而且图片非常大):
- Input image
- Generated filter (without FFT!)
- 以下函数的参数:
- 标准 = 1.0
- 大小 = 8.0
- 宽=高=512
- Result image
最终代码:
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]);
}
}
}