FFT 卷积后重新缩放复杂数据

Rescaling Complex data after FFT Convolution

我通过将它们应用于 FFT 卷积输出来测试两个重新缩放函数。

第一个收集自

    public static void RescaleComplex(Complex[,] convolve)
    {
        int imageWidth = convolve.GetLength(0);
        int imageHeight = convolve.GetLength(1);

        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 = 1.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);
            }
        }
    }

这里的问题是对比度不正确

第二个收集自

    public static void RescaleComplex(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.0, Math.Min(convolve[i, j].Real * scale, 1.0));
                double im = Math.Max(0.0, Math.Min(convolve[i, j].Imaginary * scale, 1.0));
                convolve[i, j] = new Complex(re, im);
            }
        }
    }

这里输出全白

因此,您可以看到其中两个版本给出了一个正确的输出,另一个给出了错误的输出。

如何解决这个难题?

注. 矩阵为如下内核:

 0  -1   0 
-1   5  -1 
 0  -1   0

源代码。 这是我的FFT卷积函数。

    private static Complex[,] ConvolutionFft(Complex[,] image, Complex[,] kernel)
    {
        Complex[,] imageCopy = (Complex[,])image.Clone();
        Complex[,] kernelCopy = (Complex[,])kernel.Clone();
        Complex[,] convolve = null;

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

        int kernelWidth = kernelCopy.GetLength(0);
        int kernelHeight = kernelCopy.GetLength(1);

        if (imageWidth == kernelWidth && imageHeight == kernelHeight)
        {
            Complex[,] fftConvolved = new Complex[imageWidth, imageHeight];

            Complex[,] fftImage = FourierTransform.ForwardFFT(imageCopy);
            Complex[,] fftKernel = FourierTransform.ForwardFFT(kernelCopy);                

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

            convolve = FourierTransform.InverseFFT(fftConvolved);

            RescaleComplex(convolve);

            convolve = FourierShifter.ShiftFft(convolve);
        }
        else
        {
            throw new Exception("Padded image and kernel dimensions must be same.");
        }

        return convolve;
    }

这并不是真正的两难选择。这只是显示范围有限的问题,以及您对这两种情况的期望不同。

  1. (顶部):这是一个归一化内核(其元素总和为 1)。它不会改变图像的对比度。但是因为它里面有负值,它可以生成超出原始范围的值。

  2. (底部):这不是规范化内核。它改变了输出的对比度。

例如,玩转内核

 0, -1,  0
-1,  6, -1
 0, -1,  0

(注意中间的 6)。总和为2。图像对比度将加倍。即输入全为0的区域,输出也为0,输入全为1的区域,输出为2。

通常,如果不打算改变图像对比度,卷积滤波器会被归一化。如果您应用这样的过滤器,则无需重新缩放输出以供显示(尽管您可能希望在出现超出范围的值时对其进行裁剪)。但是,超出范围的值可能是相关的,在这种情况下,您需要重新缩放输出以匹配显示范围。

在您的情况 2(图像内核)中,您可以规范化内核以避免重新缩放输出。但这不是一般的解决方案。一些滤波器加起来为 0(例如 Sobel 内核或 Laplace 内核,它们都基于去除 DC 分量的导数)。这些无法标准化,您将始终必须重新缩放输出图像以进行显示(尽管您不会重新缩放它们的输出以进行分析,因为它们的输出值具有在重新缩放时被破坏的物理意义)。

也就是说,卷积有时是为了产生与输入图像具有相同对比度(在大致相同范围内)的输出图像,有时则不是。您需要知道要为输出应用什么过滤器才有意义,并且能够在期望图像处于特定范围内的屏幕上显示输出。


编辑: 解释你的图中发生了什么。

第 1 个图: 这里您正在重新缩放,以便可以看到整个图像强度范围。从逻辑上讲,您不会得到任何饱和像素。但是因为矩阵核增强了高频,所以输出图像有超出原始范围的值。重新缩放以适应显示器范围内的整个范围会降低图像的对比度。

第2图:您正在将频域卷积结果重新缩放N = imageWidth * imageHeight。这会产生正确的输出。您需要应用此缩放表示您的正向 FFT 缩放 1/N,而您的反向 FFT 不缩放。

对于IFFT(FFT(img))==img,FFT或IFFT中的任何一个都需要缩放1/N。通常是缩放的 IFFT。原因是卷积在没有任何进一步缩放的情况下按预期进行。要看到这一点,请想象一个所有像素都具有相同值的图像。 FFT(img) 除了 0 频率分量(DC 分量)之外的所有地方都将为零,这将是 sum(img)。归一化核总和为1,所以它的直流分量是sum(kernel)==1。将这两者相乘,我们再次获得与输入类似的频谱,其中直流分量为 sum(img)。它的逆变换等于img。这正是我们对这个卷积的期望。

现在,使用另一种形式的规范化(即您有权访问的 FFT 使用的规范化)。 FFT(img) 的直流分量将为 sum(img)/N。内核的直流分量将为 1/N。将这两者相乘,得到sum(img)/(N*N)的直流分量。它的逆变换等于img/N。因此,您需要乘以 N 以获得预期结果。这正是您在 "matrix kernel" 的频域卷积中看到的,它是标准化的。

正如我上面提到的,"image kernel" 没有标准化。 FFT(kernel)的直流分量是sum(img)/N,乘以FFT(img)有一个直流分量sum(img)*sum(img)/(N*N),所以逆变换有一个对比度乘以sum(img)/N,乘以 N 仍然会使您的因子 sum(img) 过大。如果您要规范化内核,您会将其除以 sum(img),这将使您的输出进入预期范围。