C# FFT卷积-3x3核

C# FFT卷积-3x3核,c#,image-processing,filter,fft,convolution,C#,Image Processing,Filter,Fft,Convolution,我已经编写了一些使用3x3内核锐化灰度图像的例程 -1 -1 -1 -1 9 -1 -1 -1 -1 以下代码在非FFT(空域)卷积的情况下运行良好,但在基于FFT的(频域)卷积中不工作。 输出图像似乎模糊了 我有几个问题: (1)此例程无法生成所需的结果。它还冻结了应用程序 public static Bitmap ApplyWithPadding(Bitmap image, Bitmap mask) { if(image.PixelFormat ==

我已经编写了一些使用3x3内核锐化灰度图像的例程

-1 -1 -1 
-1  9 -1 
-1 -1 -1
以下代码在非FFT(空域)卷积的情况下运行良好,但在基于FFT的(频域)卷积中不工作。

输出图像似乎模糊了

我有几个问题:

(1)此例程无法生成所需的结果。它还冻结了应用程序

    public static Bitmap ApplyWithPadding(Bitmap image, Bitmap mask)
    {
        if(image.PixelFormat == PixelFormat.Format8bppIndexed)
        {
            Bitmap imageClone = (Bitmap)image.Clone();
            Bitmap maskClone = (Bitmap)mask.Clone();

            /////////////////////////////////////////////////////////////////
            Complex[,] cPaddedLena = ImageDataConverter.ToComplex(imageClone);
            Complex[,] cPaddedMask = ImageDataConverter.ToComplex(maskClone);

            Complex[,] cConvolved = Convolution.Convolve(cPaddedLena, cPaddedMask);

            return ImageDataConverter.ToBitmap(cConvolved);
        }
        else
        {
            throw new Exception("not a grascale");
        }
    }
(2)此例行程序产生了良好的结果。但是,非常缓慢

    public static Bitmap Apply(Bitmap sourceBitmap)
    {
        Sharpen filter = new Sharpen();

        BitmapData sourceData = sourceBitmap.LockBits(new Rectangle(0, 0,
                                 sourceBitmap.Width, sourceBitmap.Height),
                                 ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);

        byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height];
        byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];

        Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);

        sourceBitmap.UnlockBits(sourceData);

        double blue = 0.0;
        double green = 0.0;
        double red = 0.0;

        int filterWidth = filter.FilterMatrix.GetLength(1);
        int filterHeight = filter.FilterMatrix.GetLength(0);

        int filterOffset = (filterWidth - 1) / 2;
        int calcOffset = 0;

        int byteOffset = 0;

        for (int offsetY = filterOffset; offsetY < sourceBitmap.Height - filterOffset; offsetY++)
        {
            for (int offsetX = filterOffset; offsetX <
                sourceBitmap.Width - filterOffset; offsetX++)
            {
                blue = 0;
                green = 0;
                red = 0;

                byteOffset = offsetY *
                             sourceData.Stride +
                             offsetX * 4;

                for (int filterY = -filterOffset;
                    filterY <= filterOffset; filterY++)
                {
                    for (int filterX = -filterOffset;
                        filterX <= filterOffset; filterX++)
                    {

                        calcOffset = byteOffset +
                                     (filterX * 4) +
                                     (filterY * sourceData.Stride);

                        blue += (double)(pixelBuffer[calcOffset]) *
                                filter.FilterMatrix[filterY + filterOffset,
                                                    filterX + filterOffset];

                        green += (double)(pixelBuffer[calcOffset + 1]) *
                                 filter.FilterMatrix[filterY + filterOffset,
                                                    filterX + filterOffset];

                        red += (double)(pixelBuffer[calcOffset + 2]) *
                               filter.FilterMatrix[filterY + filterOffset,
                                                  filterX + filterOffset];
                    }
                }

                blue = filter.Factor * blue + filter.Bias;
                green = filter.Factor * green + filter.Bias;
                red = filter.Factor * red + filter.Bias;

                if (blue > 255)
                { blue = 255; }
                else if (blue < 0)
                { blue = 0; }

                if (green > 255)
                { green = 255; }
                else if (green < 0)
                { green = 0; }

                if (red > 255)
                { red = 255; }
                else if (red < 0)
                { red = 0; }

                resultBuffer[byteOffset] = (byte)(blue);
                resultBuffer[byteOffset + 1] = (byte)(green);
                resultBuffer[byteOffset + 2] = (byte)(red);
                resultBuffer[byteOffset + 3] = 255;
            }
        }

        Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);

        BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0,
                                 resultBitmap.Width, resultBitmap.Height),
                                 ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);

        Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
        resultBitmap.UnlockBits(resultData);

        return resultBitmap;
    }
输出:


源代码:

  • 您可以从下载整个解决方案

主要问题似乎是将内核解释为由无符号字节值组成的映像。结果,
-1
值被转换为
255
,有效地计算与内核的卷积

255 255 255
255  9  255
255 255 255
这可以从“卷积核”图像的白色区域立即观察到。由此产生的核是低通滤波器的核,产生相应的模糊效果

处理此问题的最佳方法可能是将内核作为有符号值的矩阵而不是图像来读取。

如果仍然希望将内核作为映像处理,则需要将映像转换回有符号值。我能想到的实现此结果的最简单方法是创建一个修改版的
ImageDataConverter.ToInteger(位图)
,其中将字节映射到签名值:

public static Complex[,] Unwrap(Bitmap bitmap)
{
  int Width = bitmap.Width;
  int Height = bitmap.Height;

  Complex[,] array2D = new Complex[bitmap.Width, bitmap.Height];
  ...

        else// If there is only one channel:
        {
          iii = (int)(*address);
          if (iii >= 128)
          {
            iii -= 256;
          }
        }
        Complex tempComp = new Complex((double)iii, 0.0);
        array2D[x, y] = tempComp;
然后,您就可以在
锐化过滤器.ApplyWithPadding
中使用以下各项转换图像:

Complex[,] cPaddedMask =  ImageDataConverter.Unwrap(maskClone);
...
// -3 terms are due to kernel size
// +5 vertical offset term is due to vertical reflection & offset in SetPixel
Rectangle rect = new Rectangle((cPaddedLena.GetLength(0) / 2 - 3) / 2,
                               (cPaddedLena.GetLength(1) / 2 - 3) / 2 + 5, 
                               cPaddedLena.GetLength(0) / 2,
                               cPaddedLena.GetLength(1) / 2);
return ImageDataConverter.ToBitmap(cConvolved).Clone(rect, PixelFormat.Format8bppIndexed);
这将为您提供以下结果:

虽然这样可以提高图像的清晰度,但您应该立即注意到图像比原始图像暗得多。这是由于
卷积.Rescale
函数根据图像的最小值和最大值动态重新缩放图像。这可以方便地显示具有最大动态范围的图像,但可能导致与标准卷积不同的整体缩放。要实现此标准缩放(基于FFT实现的缩放),可以使用以下实现:

    //Rescale values between 0 and 255.
    private static void Rescale(Complex[,] convolve)
    {
        int imageWidth = convolve.GetLength(0);
        int imageHeight = convolve.GetLength(1);

        double scale = imageWidth * imageHeight;

        for (int j = 0; j < imageHeight; j++)
        {
            for (int i = 0; i < imageWidth; i++)
            {
                double re = Math.Max(0, Math.Min(convolve[i, j].Real * scale, 255.0));
                double im = Math.Max(0, Math.Min(convolve[i, j].Imaginary * scale, 255.0));
                convolve[i, j] = new Complex(re, im);
            }
        }
    }
应该给你:

为了便于视觉比较,这里再次显示原始图像:


您好,您能指定卷积。卷积(x,y)的作用吗?在任何情况下,fourier空间中的卷积都是乘法。因此,如果你将CPAddedDelena和CPAddedTask的fourrier变换逐项复乘(它们应该在fourrier空间中填充为具有相同的维数),并进行反向fourrier变换,那么操作将起作用
...
// -3 terms are due to kernel size
// +5 vertical offset term is due to vertical reflection & offset in SetPixel
Rectangle rect = new Rectangle((cPaddedLena.GetLength(0) / 2 - 3) / 2,
                               (cPaddedLena.GetLength(1) / 2 - 3) / 2 + 5, 
                               cPaddedLena.GetLength(0) / 2,
                               cPaddedLena.GetLength(1) / 2);
return ImageDataConverter.ToBitmap(cConvolved).Clone(rect, PixelFormat.Format8bppIndexed);