频域图像卷积

Image convolution in frequency domain

我想在频域中将 Lena 与其自身进行卷积。 Here is an excerpt from a book. 这表明卷积的输出应该如何:

我写了下面的应用来实现两幅图像在频域的卷积。我遵循的步骤如下:

  1. Convert Lena into a matrix of complex numbers.
  2. Apply FFT to obtain a complex matrix.
  3. Multiply two complex matrices element by element (if that is the definition of Convolution).
  4. Apply IFFT to the result of the multiplication.

输出似乎没有按预期出现:

这里有两个问题:

注意。 FFT 和 I-FFT 与相同的库完美配合。

Note-2. There is a thread in SO 似乎在讨论同一个话题。

源代码:

public static class Convolution
{
    public static Complex[,] Convolve(Complex[,]image, Complex[,]mask)
    {
        Complex[,] convolve = null;

        int imageWidth = image.GetLength(0);
        int imageHeight = image.GetLength(1);

        int maskWidth = mask.GetLength(0);
        int maskeHeight = mask.GetLength(1);

        if (imageWidth == maskWidth && imageHeight == maskeHeight)
        {
            FourierTransform ftForImage = new FourierTransform(image); ftForImage.ForwardFFT();
            FourierTransform ftForMask = new FourierTransform(mask); ftForMask.ForwardFFT();

            Complex[,] fftImage = ftForImage.FourierTransformedImageComplex;                
            Complex[,] fftKernel = ftForMask.FourierTransformedImageComplex;

            Complex[,] fftConvolved = new Complex[imageWidth, imageHeight];

            for (int i = 0; i < imageWidth; i++)
            {
                for (int j = 0; j < imageHeight; j++)
                {
                    fftConvolved[i, j] = fftImage[i, j] * fftKernel[i, j];
                }
            }

            FourierTransform ftForConv = new FourierTransform();
            ftForConv.InverseFFT(fftConvolved);
            convolve = ftForConv.GrayscaleImageComplex;

            //convolve = fftConvolved;
        }
        else
        {
            throw new Exception("padding needed");
        }

        return convolve;
    }
}

    private void convolveButton_Click(object sender, EventArgs e)
    {
        Bitmap lena = inputImagePictureBox.Image as Bitmap;
        Bitmap paddedMask = paddedMaskPictureBox.Image as Bitmap;

        Complex[,] cLena = ImageDataConverter.ToComplex(lena);
        Complex[,] cPaddedMask = ImageDataConverter.ToComplex(paddedMask);

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

        Bitmap convolved = ImageDataConverter.ToBitmap(cConvolved);

        convolvedImagePictureBox.Image = convolved;
    }

在工作的 FFT->IFFT 应用程序和损坏的卷积应用程序之间调用 InverseFFT 的方式有所不同。在后一种情况下,您没有明确传递 WidthHeight 参数(您应该从输入图像中获取):

public void InverseFFT(Complex[,] fftImage)
{
    if (FourierTransformedImageComplex == null)
    {
       FourierTransformedImageComplex = fftImage;
    }

    GrayscaleImageComplex = FourierFunction.FFT2D(FourierTransformedImageComplex, Width, Height, -1);

    GrayscaleImageInteger = ImageDataConverter.ToInteger(GrayscaleImageComplex);
    InputImageBitmap = ImageDataConverter.ToBitmap(GrayscaleImageInteger);
}

因此,WidthHeight 都为 0,并且代码跳过了大部分逆向 2D 变换。初始化这些参数应该会给你一些至少不是全黑的东西。

    if (FourierTransformedImageComplex == null)
    {
        FourierTransformedImageComplex = fftImage;
        Width = fftImage.GetLength(0);
        Height = fftImage.GetLength(1);
    }

然后您应该注意到一些尖锐的 white/black 边缘。这些是由输出值中的环绕引起的。为避免这种情况,您可能需要在逆变换后重新调整输出以适应可用比例,例如:

double maxAmp = 0.0;
for (int i = 0; i < imageWidth; i++)
{
    for (int j = 0; j < imageHeight; j++)
    {
        maxAmp = Math.Max(maxAmp, convolve[i, j].Magnitude);
    }
}
double scale = 255.0 / maxAmp;
for (int i = 0; i < imageWidth; i++)
{
    for (int j = 0; j < imageHeight; j++)
    {
        convolve[i, j] = new Complex(convolve[i, j].Real * scale, convolve[i, j].Imaginary * scale);
        maxAmp = Math.Max(maxAmp, convolve[i, j].Magnitude);
    }
}

这应该给出更合理的输出:

然而,这仍然不是你书中描述的那样。此时我们有一个二维循环卷积。要获得 2D 线性卷积,您需要确保 图像都被填充到尺寸之和 :

Bitmap lena = inputImagePictureBox.Image as Bitmap;
Bitmap mask = paddedMaskPictureBox.Image as Bitmap;

Bitmap paddedLena = ImagePadder.Pad(lena, lena.Width+ mask.Width, lena.Height+ mask.Height);
Bitmap paddedMask = ImagePadder.Pad(mask, lena.Width+ mask.Width, lena.Height+ mask.Height);

Complex[,] cLena = ImageDataConverter.ToComplex(paddedLena);
Complex[,] cPaddedMask = ImageDataConverter.ToComplex(paddedMask);

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

并且在调整填充时,您可能希望将填充颜色更改为黑色,否则填充本身会在两个图像之间引入很大的相关性:

public class ImagePadder
{
    public static Bitmap Pad(Bitmap maskImage, int newWidth, int newHeight)
    {
        ...
        Grayscale.Fill(resizedImage, Color.Black);

现在您应该得到以下内容:

我们越来越接近了,但是自相关结果的峰值不在中心,那是因为你在正变换中FourierShifter.FFTShift而在逆变换中没有调用相应的FourierShifter.RemoveFFTShift转变。如果我们调整这些(在 ForwardFFT 中删除 FFTShift,或者在 InverseFFT 中添加 RemoveFFTShift),那么我们最终得到: