FFT卷积 - 3x3内核 [英] FFT Convolution - 3x3 kernel

查看:364
本文介绍了FFT卷积 - 3x3内核的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

pre code> -1 -1 -1
-1 9 -1
-1 -1 -1

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

<



我有几个问题:

(1 )这个程序不能产生想要的结果。它还冻结了应用程序。

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

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

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

返回ImageDataConverter.ToBitmap(cConvolved);
}
else
{
抛出新的异常(不是grascale);


code
$ b

(2)常规给出好的结果。但是,像地狱一样缓慢。

pre $ 公共静态位图应用(位图sourceBitmap)
{
锐化过滤器= 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; (int offsetY = filterOffset; offsetY< sourceBitmap.Height - filterOffset; offsetY ++)

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

byteOffset = offsetY *
sourceData.Stride +
offsetX * 4; (int filterY = -filterOffset;
filterY< = filterOffset; filterY ++)

(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;如果(绿色> 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;



位图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);

返回resultBitmap;

(3)以下是我的GUI代码。如果我使用图像作为蒙版, SharpenFilter.ApplyWithPadding()可以正常工作。但是,如果我使用 3 x 3 内核,则不起作用。

  string path = @E:\leyl.png; 
string path2 = @E:\mask.png;

位图_inputImage;
位图_maskImage;

private void LoadImages_Click(object sender,EventArgs e)
{
_inputImage = Grayscale.ToGrayscale(Bitmap.FromFile(path)as Bitmap);
$ b $ * b $ b _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;
}

位图_paddedImage;
位图_paddedMask;
private void padButton_Click(object sender,EventArgs e)
{
Bitmap lena = Grayscale.ToGrayscale(_inputImage);
位图掩码= Grayscale.ToGrayscale(_maskImage);

////不工作...
// int maxWidth =(int)Math.Max(lena.Width,mask.Width);
// int maxHeight =(int)Math.Max(lena.Height,mask.Height);

////这是工作正常的情况下,如果我使用PNG图像作为掩码。
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)
{
//不能正常工作。
//冻结应用程序。
位图sharp = SharpenFilter.ApplyWithPadding(_paddedImage,_paddedMask);

////运作良好。但是,非常缓慢。
// Bitmap sharp = SharpenFilter.Apply(_paddedImage);

filteredPictureBox.Image =清晰的位图;



$ b $输出:






源代码:






  • 您可以从



    虽然这改善了图像的清晰度,但您应该立即注意到图像比原本的。这是由于 Convolution.Rescale 函数根据它的最小值和最大值动态重新调整图像的大小。这可以方便地显示具有最大动态范围的图像,但可能导致与标准卷积不同的整体缩放比例。为了实现这个标准缩放(基于FFT实现的缩放),您可以使用以下实现:

      // Rescale值为0到255之间。
    private static void Rescale(Complex [,] convolve)
    {
    int imageWidth = convolve.GetLength(0);
    int imageHeight = convolve.GetLength(1);

    double scale = imageWidth * imageHeight; (int j = 0; j
    (int i = 0; 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);





    然后这应该给你一个一个更合适的亮度级别的图像:



    最后,对于滤波操作,通常会期望结果与原始图像大小相匹配(不同于包含尾部的卷积)。将结果剪裁到 SharpenFilter.ApplyWithPadding 中:

      ... 
    // -3项是由于内核大小
    // + 5垂直偏移项是由于垂直反射& (1)/ 2 - 3)/ 2 + 5,$($) b $ b cPaddedLena.GetLength(0)/ 2,
    cPaddedLena.GetLength(1)/ 2);
    return ImageDataConverter.ToBitmap(cConvolved).Clone(rect,PixelFormat.Format8bppIndexed);

    应该会给您:



    为了更容易的进行视觉比较,下面是原始图像:


    I have written some routines to sharpen a Grayscale image using a 3x3 kernel,

    -1 -1 -1 
    -1  9 -1 
    -1 -1 -1
    

    The following code is working well in case of non-FFT (spatial-domain) convolution, but, not working in FFT-based (frequency-domain) convolution.

    The output image seems to be blurred.

    I have several problems:

    (1) This routine is not being able to generate desired result. It also freezes the application.

        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) This routine gives good result. But, as slow as hell.

        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) The following is my GUI code. SharpenFilter.ApplyWithPadding() works properly if I use an image as a mask. But, doesn't work if I use a, say, 3x3 kernel.

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

    Output:


    Source Code :

    解决方案

    The main issue appears to be with the interpretation of the kernel as an image consisting of unsigned byte values. As a result the -1 values are converted to 255 effectively computing a convolution with the kernel

    255 255 255
    255  9  255
    255 255 255
    

    This can be immediately observed from white area in the "Convolution Kernel" image. The resulting kernel is thus that of a low-pass filter, producing a corresponding blurring effect.

    Probably the best way to handle this would be to read the kernel as a matrix of signed values instead of as an image.

    If you still prefer to handle the kernel as an image, you would need to convert the image back to signed values. The simplest way I can think of achieving this result would be to create a modified version of ImageDataConverter.ToInteger(Bitmap) where you map the bytes to signed values:

    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;
    

    You would then be able to convert your image in SharpenFilter.ApplyWithPadding with:

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

    This should then give you the following result:

    While this improves on the sharpness of the image, you should immediately notice that the image is much darker than the original. This is due to the Convolution.Rescale function which dynamically rescales the image according to it's minimum and maximum value. This can be convenient to show the image with maximum dynamic range, but could result in a different overall scaling than a standard convolution. To achieve this standard scaling (based on the scaling of your FFT implementation), you could use the following implementation:

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

    This should then give you an image with a more appropriate brightness level:

    Finally, for a filtering operation one would typically expect the result to match the original image size (unlike a convolution which includes the tails). Cropping the result in SharpenFilter.ApplyWithPadding with:

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

    should give you:

    For easier visual comparison, here is the original image again:

    这篇关于FFT卷积 - 3x3内核的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆