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)。它不会改变图像的对比度。但是因为它里面有负值,它可以生成超出原始范围的值。
(底部):这不是规范化内核。它改变了输出的对比度。
例如,玩转内核
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)
,这将使您的输出进入预期范围。
我通过将它们应用于 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)。它不会改变图像的对比度。但是因为它里面有负值,它可以生成超出原始范围的值。
(底部):这不是规范化内核。它改变了输出的对比度。
例如,玩转内核
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)
,这将使您的输出进入预期范围。