Применение размытия по Гауссу к изображению в частотной области

У меня есть проблемы с применением размытия по Гауссу к изображению в частотной области. По неизвестным причинам (возможно, я не ошибаюсь) я получаю проводное изображение вместо размытого.

Вот что я делаю шаг за шагом:

  1. Загрузите изображение.
  2. Разделить изображение на отдельные каналы.

    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();
    }
    
  3. Конвертировать каждый канал в сложные изображения (с 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;
    }
    
  4. Применить размытие по Гауссу.

    1. Сначала я создаю ядро ​​(для целей тестирования размер ядра равен размеру изображения, только его центральная часть вычисляется с помощью гауссовой функции, остальное ядро ​​равно 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))));
      }
      
    2. Примените БПФ к каждому каналу и ядру.

    3. Умножьте центральную часть каждого канала на ядро.

      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]);
              }
          }
      }
      
  5. Примените IFFT к каждому каналу.
  6. Конвертировать каждый канал обратно в растровые изображения (с 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;
    }
    
  7. Объединить растровые изображения в один растровый

    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).

Как я решил это:

  1. Я нашел, как должно выглядеть ядро ​​после FFT (см. Ниже).
  2. Посмотрел цвета этого изображения.
  3. Рассчитано gauss2d для x = -2; у = -2; стандартный = 1.
  4. Вычисляется предварительный пересчет для получения значения цвета из значения, рассчитанного в пт. 3 (см. Вольфрам).
  5. Сгенерированное ядро ​​с масштабированными значениями с помощью perscaler из pt. 4.

Однако я не могу использовать FFT для сгенерированного фильтра, потому что сгенерированный фильтр уже выглядит как фильтр после FFT. Это работает - выходное изображение размыто без артефактов, так что я думаю, что это не так уж плохо.

Изображения (я не могу опубликовать более 2 ссылок, и изображения очень большие):

Финальный код:

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]);
        }
    }
}
Другие вопросы по тегам