在频域对图像应用高斯模糊

4

我在频域对图像应用高斯模糊时遇到了困难。由于某些未知原因(可能是我做错了什么),我收到的图像不是模糊的,而是出现了奇怪的效果。

以下是我一步一步所做的:

  1. Load the image.
  2. 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();
    }
    
  3. 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;
    }
    
  4. Apply Gaussian blur.

    1. 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))));
      }
      
    2. Apply FFT to every channel and kernel.

    3. 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]);
              }
          }
      }
      
  5. Apply IFFT to every channel.
  6. 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;
    }
    
  7. 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;
    }
    
作为结果,我得到了移位的、红色模糊的图像版本:链接
@编辑 - 更新了代码中的几个更改。

如果使用全为1的内核会发生什么? - Moby Disk
@MobyDisk 结果是相同的... - user2475983
结果与原始图像相同,还是与奇怪的图像相同?如果所有1的内核给出了奇怪的图像,则您知道错误不在模糊代码中。因此,它必须在FFT / IFFT或在拆分通道和重新组合它们的过程中,或者在使用aforge.net生成复杂图像的过程中。另一方面,如果所有1的内核将原始图像返回,则问题在于模糊/内核。这种思路是否有帮助? - Moby Disk
结果是奇怪的图像,对于不准确的回复很抱歉。它有点帮助,谢谢。然而,如果有人想添加任何内容,问题仍然是开放的。 - user2475983
我已经更新了问题,包括代码的多处更改以及结果图像的变化。 - user2475983
1个回答

0

在 DSP stackexchange 上得到了一些帮助...然后有点作弊,但是它管用。主要问题是内核生成和应用 FFT。另外一个重要的事情是,AForge.NET 在将图像像素转换为 ComplexImage 时除以 255,在将 ComplexImage 转换为位图时乘以 255(感谢 Olli Niemitalo @ DSP SE)。

我是如何解决这个问题的:

我已经找到了经过FFT处理后内核应该是什么样子(见下文)。 查找了那个图像的颜色。 计算了x = -2;y = -2;std = 1时的高斯函数gauss2d。 计算了用于从第3点计算得出的值中获取颜色值的预缩放器(参见wolfram)。 使用第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]);
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接