Применение размытия по Гауссу к изображению в частотной области
У меня есть проблемы с применением размытия по Гауссу к изображению в частотной области. По неизвестным причинам (возможно, я не ошибаюсь) я получаю проводное изображение вместо размытого.
Вот что я делаю шаг за шагом:
- Загрузите изображение.
Разделить изображение на отдельные каналы.
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)))); }
Примените БПФ к каждому каналу и ядру.
Умножьте центральную часть каждого канала на ядро.
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 - обновлен вопрос с несколькими изменениями в коде.
1 ответ
Я понял это с некоторой помощью в DSP stackexchange... и некотором обмане, но это работает. Основная проблема заключалась в генерации ядра и применении к нему FFT. Также важно то, что AForge.NET делит пиксели изображения на 255 во время преобразования в ComplexImage и умножает на 255 во время преобразования из ComplexImage в Bitmap (спасибо Olli Niemitalo @ DSP SE).
Как я решил это:
- Я нашел, как должно выглядеть ядро после FFT (см. Ниже).
- Посмотрел цвета этого изображения.
- Рассчитано gauss2d для x = -2; у = -2; стандартный = 1.
- Вычисляется предварительный пересчет для получения значения цвета из значения, рассчитанного в пт. 3 (см. Вольфрам).
- Сгенерированное ядро с масштабированными значениями с помощью perscaler из pt. 4.
Однако я не могу использовать FFT для сгенерированного фильтра, потому что сгенерированный фильтр уже выглядит как фильтр после FFT. Это работает - выходное изображение размыто без артефактов, так что я думаю, что это не так уж плохо.
Изображения (я не могу опубликовать более 2 ссылок, и изображения очень большие):
- Входное изображение
- Сгенерированный фильтр (без БПФ!)
- Параметры для функции ниже:
- стандартное отклонение = 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]);
}
}
}