FFT卷积 - 3x3内核

5

我已经编写了一些例程,使用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;
    }

(3) 以下是我的图形用户界面代码。如果我使用一张图片作为掩模,SharpenFilter.ApplyWithPadding()可以正常工作。但是,如果我使用一个例如3x3的卷积核,则不起作用。

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

输出:

输入图像描述


源代码:

输入图像描述


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Antoine Bergamaschi
1个回答

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


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