图像的逆 FFT 期间数据丢失
Loss of data during the Inverse-FFT of an Image
我正在使用以下代码将 Bitmap 转换为 Complex,反之亦然。
尽管这些是直接从 Accord.NET framework 复制的,但在测试这些静态方法时,我发现重复使用这些静态方法会导致 'data-loss'。结果,结尾output/result变成了扭曲。
public partial class ImageDataConverter
{
#region private static Complex[,] FromBitmapData(BitmapData bmpData)
private static Complex[,] ToComplex(BitmapData bmpData)
{
Complex[,] comp = null;
if (bmpData.PixelFormat == PixelFormat.Format8bppIndexed)
{
int width = bmpData.Width;
int height = bmpData.Height;
int offset = bmpData.Stride - (width * 1);//1 === 1 byte per pixel.
if ((!Tools.IsPowerOf2(width)) || (!Tools.IsPowerOf2(height)))
{
throw new Exception("Imager width and height should be n of 2.");
}
comp = new Complex[width, height];
unsafe
{
byte* src = (byte*)bmpData.Scan0.ToPointer();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++, src++)
{
comp[y, x] = new Complex((float)*src / 255,
comp[y, x].Imaginary);
}
src += offset;
}
}
}
else
{
throw new Exception("EightBppIndexedImageRequired");
}
return comp;
}
#endregion
public static Complex[,] ToComplex(Bitmap bmp)
{
Complex[,] comp = null;
if (bmp.PixelFormat == PixelFormat.Format8bppIndexed)
{
BitmapData bmpData = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format8bppIndexed);
try
{
comp = ToComplex(bmpData);
}
finally
{
bmp.UnlockBits(bmpData);
}
}
else
{
throw new Exception("EightBppIndexedImageRequired");
}
return comp;
}
public static Bitmap ToBitmap(Complex[,] image, bool fourierTransformed)
{
int width = image.GetLength(0);
int height = image.GetLength(1);
Bitmap bmp = Imager.CreateGrayscaleImage(width, height);
BitmapData bmpData = bmp.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.ReadWrite,
PixelFormat.Format8bppIndexed);
int offset = bmpData.Stride - width;
double scale = (fourierTransformed) ? Math.Sqrt(width * height) : 1;
unsafe
{
byte* address = (byte*)bmpData.Scan0.ToPointer();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++, address++)
{
double min = System.Math.Min(255, image[y, x].Magnitude * scale * 255);
*address = (byte)System.Math.Max(0, min);
}
address += offset;
}
}
bmp.UnlockBits(bmpData);
return bmp;
}
}
(The DotNetFiddle link of the complete source code)
输出:
如您所见,FFT 工作正常,但 I-FFT 不正确。
那是因为位图到复合体和反之亦然没有按预期工作。
如何更正 ToComplex() 和 ToBitmap() 函数以使其不丢失数据?
我不会在 C# 中编写代码,所以以极端偏见处理这个答案!
快速浏览一下,我发现了几个问题:
ToComplex()
正在将 BMP 转换为二维复矩阵。当您转换时,虚部保持不变,但在同一函数的开头,您有:
Complex[,] complex2D = null;
complex2D = new Complex[width, height];
因此虚部未定义或为零取决于您的复杂 class 构造函数。 这意味着您丢失了重建所需数据的一半!!!您应该从 2 个图像恢复原始复数矩阵,一个用于结果的实部,第二个用于结果的虚部。
ToBitmap()
你正在节省幅度,我认为 sqrt( Re*Re + Im*Im )
所以它是功率谱而不是原始复数值,所以你不能重建回来......你应该将 Re,Im 存储在 2 个单独的图像中。
每像素 8 位
这并不多,在 FFT/IFFT 之后可能会导致严重的舍入误差,因此重建可能会非常失真。
[Edit1] 补救措施
有更多选项可以修复此问题,例如:
使用浮动复杂矩阵进行计算,位图仅用于可视化。
这是最安全的方法,因为您可以避免额外的转换四舍五入。这种方法具有最好的精度。但是你需要重写你的 DIP/CV 算法以支持复杂的域矩阵而不是需要大量工作的位图。
重写您的转换以支持实部和虚部图像
你的转换真的很糟糕,因为它没有store/restore实部和虚部,而且它没有考虑负值(至少我这样做了看不到它,而是将它们减少到零,这是错误的)。我会将转换重写为:
// conversion scales
float Re_ofset=256.0,Re_scale=512.0/255.0;
float Im_ofset=256.0,Im_scale=512.0/255.0;
private static Complex[,] ToComplex(BitmapData bmpRe,BitmapData bmpIm)
{
//...
byte* srcRe = (byte*)bmpRe.Scan0.ToPointer();
byte* srcIm = (byte*)bmpIm.Scan0.ToPointer();
complex c = new Complex(0.0,0.0);
// for each line
for (int y = 0; y < height; y++)
{
// for each pixel
for (int x = 0; x < width; x++, src++)
{
complex2D[y, x] = c;
c.Real = (float)*(srcRe*Re_scale)-Re_ofset;
c.Imaginary = (float)*(srcIm*Im_scale)-Im_ofset;
}
src += offset;
}
//...
}
public static Bitmap ToBitmapRe(Complex[,] complex2D)
{
//...
float Re = (complex2D[y, x].Real+Re_ofset)/Re_scale;
Re = min(Re,255.0);
Re = max(Re, 0.0);
*address = (byte)Re;
//...
}
public static Bitmap ToBitmapIm(Complex[,] complex2D)
{
//...
float Im = (complex2D[y, x].Imaginary+Im_ofset)/Im_scale;
Re = min(Im,255.0);
Re = max(Im, 0.0);
*address = (byte)Im;
//...
}
其中:
Re_ofset = min(complex2D[,].Real);
Im_ofset = min(complex2D[,].Imaginary);
Re_scale = (max(complex2D[,].Real )-min(complex2D[,].Real ))/255.0;
Im_scale = (max(complex2D[,].Imaginary)-min(complex2D[,].Imaginary))/255.0;
或覆盖比复数矩阵值更大的区间。
您还可以将实部和虚部都编码为单个图像,例如图像的前半部分可以是实部,下半部分是虚部。在这种情况下,您根本不需要更改函数 headers 或名称......但您需要将图像处理为 2 个连接的正方形,每个正方形具有不同的含义 ...
您还可以使用 R = Real, B = Imaginary
或任何其他适合您的编码的 RGB 图像。
[Edit2]一些例子让我的观点更清楚
方法示例 1
图像采用浮点二维复矩阵形式,创建图像仅用于可视化。这样舍入误差很小。这些值未标准化,因此范围一开始是每个 pixel/cell <0.0,255.0>
,但在转换和缩放之后它可能会发生很大变化。
如您所见,我添加了缩放比例,因此所有像素都乘以 315 以实际看到任何东西,因为 FFT 输出值很小,除了少数单元格。但仅对于可视化,复数矩阵不变。
方法示例 #2
好吧,正如我之前提到的,您不处理负值,通过缩放和四舍五入将值归一化到范围 <0,1>
并返回,并且每个像素仅使用 8
位来存储子结果。我试图用我的代码模拟它,这就是我得到的(使用复杂的域而不是像你那样错误地使用功率谱)。这里的 C++ 源代码仅作为模板示例,因为您没有函数和它背后的 classes:
transform t;
cplx_2D c;
rgb2i(bmp0);
c.ld(bmp0,bmp0);
null_im(c);
c.mul(1.0/255.0);
c.mul(255.0); c.st(bmp0,bmp1); c.ld(bmp0,bmp1); i2iii(bmp0); i2iii(bmp1); c.mul(1.0/255.0);
bmp0->SaveToFile("_out0_Re.bmp");
bmp1->SaveToFile("_out0_Im.bmp");
t. DFFT(c,c);
c.wrap();
c.mul(255.0); c.st(bmp0,bmp1); c.ld(bmp0,bmp1); i2iii(bmp0); i2iii(bmp1); c.mul(1.0/255.0);
bmp0->SaveToFile("_out1_Re.bmp");
bmp1->SaveToFile("_out1_Im.bmp");
c.wrap();
t.iDFFT(c,c);
c.mul(255.0); c.st(bmp0,bmp1); c.ld(bmp0,bmp1); i2iii(bmp0); i2iii(bmp1); c.mul(1.0/255.0);
bmp0->SaveToFile("_out2_Re.bmp");
bmp1->SaveToFile("_out2_Im.bmp");
这里是子结果:
正如您在 DFFT 和环绕图像之后看到的那样,图像真的很暗并且大部分值都被四舍五入。所以 unwrap 和 IDFFT 之后的结果真的很纯。
这里对代码进行一些解释:
c.st(bmpre,bmpim)
与您的 ToBitmap
相同
c.ld(bmpre,bmpim)
与您的 ToComplex
相同
c.mul(scale)
将复数矩阵 c
乘以 scale
rgb2i
将 RGB 转换为灰度强度 <0,255>
i2iii
将灰度强度转换为灰度RGB图像
我不太擅长这个谜题,但请仔细检查这个除法。
comp[y, x] = new Complex((float)*src / 255, comp[y, x].Imaginary);
您可以按照此处所述降低精度
Complex class definition 在 备注 部分。
可能这发生在你的情况下。
希望这有帮助。
我正在使用以下代码将 Bitmap 转换为 Complex,反之亦然。
尽管这些是直接从 Accord.NET framework 复制的,但在测试这些静态方法时,我发现重复使用这些静态方法会导致 'data-loss'。结果,结尾output/result变成了扭曲。
public partial class ImageDataConverter
{
#region private static Complex[,] FromBitmapData(BitmapData bmpData)
private static Complex[,] ToComplex(BitmapData bmpData)
{
Complex[,] comp = null;
if (bmpData.PixelFormat == PixelFormat.Format8bppIndexed)
{
int width = bmpData.Width;
int height = bmpData.Height;
int offset = bmpData.Stride - (width * 1);//1 === 1 byte per pixel.
if ((!Tools.IsPowerOf2(width)) || (!Tools.IsPowerOf2(height)))
{
throw new Exception("Imager width and height should be n of 2.");
}
comp = new Complex[width, height];
unsafe
{
byte* src = (byte*)bmpData.Scan0.ToPointer();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++, src++)
{
comp[y, x] = new Complex((float)*src / 255,
comp[y, x].Imaginary);
}
src += offset;
}
}
}
else
{
throw new Exception("EightBppIndexedImageRequired");
}
return comp;
}
#endregion
public static Complex[,] ToComplex(Bitmap bmp)
{
Complex[,] comp = null;
if (bmp.PixelFormat == PixelFormat.Format8bppIndexed)
{
BitmapData bmpData = bmp.LockBits( new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format8bppIndexed);
try
{
comp = ToComplex(bmpData);
}
finally
{
bmp.UnlockBits(bmpData);
}
}
else
{
throw new Exception("EightBppIndexedImageRequired");
}
return comp;
}
public static Bitmap ToBitmap(Complex[,] image, bool fourierTransformed)
{
int width = image.GetLength(0);
int height = image.GetLength(1);
Bitmap bmp = Imager.CreateGrayscaleImage(width, height);
BitmapData bmpData = bmp.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.ReadWrite,
PixelFormat.Format8bppIndexed);
int offset = bmpData.Stride - width;
double scale = (fourierTransformed) ? Math.Sqrt(width * height) : 1;
unsafe
{
byte* address = (byte*)bmpData.Scan0.ToPointer();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++, address++)
{
double min = System.Math.Min(255, image[y, x].Magnitude * scale * 255);
*address = (byte)System.Math.Max(0, min);
}
address += offset;
}
}
bmp.UnlockBits(bmpData);
return bmp;
}
}
(The DotNetFiddle link of the complete source code)
输出:
如您所见,FFT 工作正常,但 I-FFT 不正确。
那是因为位图到复合体和反之亦然没有按预期工作。
如何更正 ToComplex() 和 ToBitmap() 函数以使其不丢失数据?
我不会在 C# 中编写代码,所以以极端偏见处理这个答案!
快速浏览一下,我发现了几个问题:
ToComplex()
正在将 BMP 转换为二维复矩阵。当您转换时,虚部保持不变,但在同一函数的开头,您有:
Complex[,] complex2D = null; complex2D = new Complex[width, height];
因此虚部未定义或为零取决于您的复杂 class 构造函数。 这意味着您丢失了重建所需数据的一半!!!您应该从 2 个图像恢复原始复数矩阵,一个用于结果的实部,第二个用于结果的虚部。
ToBitmap()
你正在节省幅度,我认为
sqrt( Re*Re + Im*Im )
所以它是功率谱而不是原始复数值,所以你不能重建回来......你应该将 Re,Im 存储在 2 个单独的图像中。每像素 8 位
这并不多,在 FFT/IFFT 之后可能会导致严重的舍入误差,因此重建可能会非常失真。
[Edit1] 补救措施
有更多选项可以修复此问题,例如:
使用浮动复杂矩阵进行计算,位图仅用于可视化。
这是最安全的方法,因为您可以避免额外的转换四舍五入。这种方法具有最好的精度。但是你需要重写你的 DIP/CV 算法以支持复杂的域矩阵而不是需要大量工作的位图。
重写您的转换以支持实部和虚部图像
你的转换真的很糟糕,因为它没有store/restore实部和虚部,而且它没有考虑负值(至少我这样做了看不到它,而是将它们减少到零,这是错误的)。我会将转换重写为:
// conversion scales float Re_ofset=256.0,Re_scale=512.0/255.0; float Im_ofset=256.0,Im_scale=512.0/255.0; private static Complex[,] ToComplex(BitmapData bmpRe,BitmapData bmpIm) { //... byte* srcRe = (byte*)bmpRe.Scan0.ToPointer(); byte* srcIm = (byte*)bmpIm.Scan0.ToPointer(); complex c = new Complex(0.0,0.0); // for each line for (int y = 0; y < height; y++) { // for each pixel for (int x = 0; x < width; x++, src++) { complex2D[y, x] = c; c.Real = (float)*(srcRe*Re_scale)-Re_ofset; c.Imaginary = (float)*(srcIm*Im_scale)-Im_ofset; } src += offset; } //... } public static Bitmap ToBitmapRe(Complex[,] complex2D) { //... float Re = (complex2D[y, x].Real+Re_ofset)/Re_scale; Re = min(Re,255.0); Re = max(Re, 0.0); *address = (byte)Re; //... } public static Bitmap ToBitmapIm(Complex[,] complex2D) { //... float Im = (complex2D[y, x].Imaginary+Im_ofset)/Im_scale; Re = min(Im,255.0); Re = max(Im, 0.0); *address = (byte)Im; //... }
其中:
Re_ofset = min(complex2D[,].Real); Im_ofset = min(complex2D[,].Imaginary); Re_scale = (max(complex2D[,].Real )-min(complex2D[,].Real ))/255.0; Im_scale = (max(complex2D[,].Imaginary)-min(complex2D[,].Imaginary))/255.0;
或覆盖比复数矩阵值更大的区间。
您还可以将实部和虚部都编码为单个图像,例如图像的前半部分可以是实部,下半部分是虚部。在这种情况下,您根本不需要更改函数 headers 或名称......但您需要将图像处理为 2 个连接的正方形,每个正方形具有不同的含义 ...
您还可以使用
R = Real, B = Imaginary
或任何其他适合您的编码的 RGB 图像。
[Edit2]一些例子让我的观点更清楚
方法示例 1
图像采用浮点二维复矩阵形式,创建图像仅用于可视化。这样舍入误差很小。这些值未标准化,因此范围一开始是每个 pixel/cell
<0.0,255.0>
,但在转换和缩放之后它可能会发生很大变化。如您所见,我添加了缩放比例,因此所有像素都乘以 315 以实际看到任何东西,因为 FFT 输出值很小,除了少数单元格。但仅对于可视化,复数矩阵不变。
方法示例 #2
好吧,正如我之前提到的,您不处理负值,通过缩放和四舍五入将值归一化到范围
<0,1>
并返回,并且每个像素仅使用8
位来存储子结果。我试图用我的代码模拟它,这就是我得到的(使用复杂的域而不是像你那样错误地使用功率谱)。这里的 C++ 源代码仅作为模板示例,因为您没有函数和它背后的 classes:transform t; cplx_2D c; rgb2i(bmp0); c.ld(bmp0,bmp0); null_im(c); c.mul(1.0/255.0); c.mul(255.0); c.st(bmp0,bmp1); c.ld(bmp0,bmp1); i2iii(bmp0); i2iii(bmp1); c.mul(1.0/255.0); bmp0->SaveToFile("_out0_Re.bmp"); bmp1->SaveToFile("_out0_Im.bmp"); t. DFFT(c,c); c.wrap(); c.mul(255.0); c.st(bmp0,bmp1); c.ld(bmp0,bmp1); i2iii(bmp0); i2iii(bmp1); c.mul(1.0/255.0); bmp0->SaveToFile("_out1_Re.bmp"); bmp1->SaveToFile("_out1_Im.bmp"); c.wrap(); t.iDFFT(c,c); c.mul(255.0); c.st(bmp0,bmp1); c.ld(bmp0,bmp1); i2iii(bmp0); i2iii(bmp1); c.mul(1.0/255.0); bmp0->SaveToFile("_out2_Re.bmp"); bmp1->SaveToFile("_out2_Im.bmp");
这里是子结果:
正如您在 DFFT 和环绕图像之后看到的那样,图像真的很暗并且大部分值都被四舍五入。所以 unwrap 和 IDFFT 之后的结果真的很纯。
这里对代码进行一些解释:
c.st(bmpre,bmpim)
与您的ToBitmap
相同
c.ld(bmpre,bmpim)
与您的ToComplex
相同
c.mul(scale)
将复数矩阵c
乘以scale
rgb2i
将 RGB 转换为灰度强度<0,255>
i2iii
将灰度强度转换为灰度RGB图像
我不太擅长这个谜题,但请仔细检查这个除法。
comp[y, x] = new Complex((float)*src / 255, comp[y, x].Imaginary);
您可以按照此处所述降低精度 Complex class definition 在 备注 部分。 可能这发生在你的情况下。 希望这有帮助。