FFT卷积 - 3x3内核

时间:2016-08-24 04:08:37

标签: c# image-processing filter fft convolution

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

    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");
        }
    }

以下代码在非FFT(空间域)卷积的情况下运行良好,但不适用于基于FFT(频域)的卷积。

输出图像似乎模糊不清。

我有几个问题:

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

    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;
    }

(2)此例程给出了良好的结果。但是,就像地狱一样慢。

SharpenFilter.ApplyWithPadding()

(3)以下是我的GUI代码。如果我使用图像作为蒙版,3可以正常工作。但是,如果我使用3 x string path = @"E:\lena.png"; string path2 = @"E:\mask.png"; Bitmap _inputImage; Bitmap _maskImage; private void LoadImages_Click(object sender, EventArgs e) { _inputImage = Grayscale.ToGrayscale(Bitmap.FromFile(path) as Bitmap); /* _maskImage = Grayscale.ToGrayscale(Bitmap.FromFile(path2) as Bitmap); */ SharpenFilter filter = new SharpenFilter(); double[,] mask = new double[,] { { -1, -1, -1, }, { -1, 9, -1, }, { -1, -1, -1, }, }; _maskImage = ImageDataConverter.ToBitmap(mask); inputImagePictureBox.Image = _inputImage; maskPictureBox.Image = _maskImage; } Bitmap _paddedImage; Bitmap _paddedMask; private void padButton_Click(object sender, EventArgs e) { Bitmap lena = Grayscale.ToGrayscale(_inputImage); Bitmap mask = Grayscale.ToGrayscale(_maskImage); ////Not working... //int maxWidth = (int)Math.Max(lena.Width, mask.Width); //int maxHeight = (int)Math.Max(lena.Height, mask.Height); ////This is working correctly in case if I use a png image as a mask. int maxWidth = (int)Tools.ToNextPow2(Convert.ToUInt32(lena.Width + mask.Width)); int maxHeight = (int)Tools.ToNextPow2(Convert.ToUInt32(lena.Height + mask.Height)); _paddedImage = ImagePadder.Pad(lena, maxWidth, maxHeight); _paddedMask = ImagePadder.Pad(mask, maxWidth, maxHeight); paddedImagePictureBox.Image = _paddedImage; paddedMaskPictureBox.Image = _paddedMask; } private void filterButton_Click(object sender, EventArgs e) { // Not working properly. // Freezes the application. Bitmap sharp = SharpenFilter.ApplyWithPadding(_paddedImage, _paddedMask); ////Works well. But, very slow. //Bitmap sharp = SharpenFilter.Apply(_paddedImage); filteredPictureBox.Image = sharp as Bitmap; } 内核,那么它​​是不行的。

URL                       GAAccount
------------------------------------
http://domain.com/abc     UA-xxxxxabc
http://domains.com/def     UA-xxxxxdef
http://domain.com/xyx     UA-xxxxxxyz
http://domains.com/qwe     UA-xxxxxqwe

输出:

enter image description here

源代码:

enter image description here

1 个答案:

答案 0 :(得分:4)

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

255 255 255
255  9  255
255 255 255

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

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

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

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;

然后,您就可以将SharpenFilter.ApplyWithPadding中的图片转换为:

Complex[,] cPaddedMask =  ImageDataConverter.Unwrap(maskClone);

这应该会给你以下结果:

Dynamic scaling lena

虽然这会改善图像的清晰度,但您应立即注意到图像比原始图像更暗。这是由Convolution.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);
            }
        }
    }

然后应该为您提供具有更合适亮度级别的图像:

Standard scaling

最后,对于过滤操作,人们通常期望结果与原始图像大小匹配(与包括尾部的卷积不同)。使用以下内容将结果裁剪为SharpenFilter.ApplyWithPadding

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

应该给你:

sharpened image

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

original image