我使用以下代码将Bitmap转换为Complex,反之亦然。
尽管这些是直接从Accord.NET framework复制的,但在测试这些静态方法的同时,我发现,重复使用这些静态方法会导致数据丢失'。结果,结束输出/结果变为失真。
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()函数,以免丢失数据?
答案 0 :(得分:4)
我不在 C#中编码,所以在极端偏见的情况下处理这个答案!
从快速看,我发现了一些问题:
<强> ToComplex()
强>
将BMP转换为2D复杂矩阵。当你转换时,你将假想的部分保持不变,但是在同一个函数的开头你有:
Complex[,] complex2D = null;
complex2D = new Complex[width, height];
因此,虚部是未定义的或零取决于您的复杂类构造函数。 这意味着您缺少重建所需数据的一半!!! 您应该从2个图像中恢复原始复杂矩阵,其中一个用于实数,第二个用于结果的虚部。
<强> ToBitmap()
强>
您正在保存我认为sqrt( Re*Re + Im*Im )
的幅度,因此功率谱不是原始的复杂值,因此您无法重建...您应该将Re,Im存储在2个单独的图像中。
每像素8位
这并不多,并且可能会在 FFT / IFFT 之后导致严重的舍入误差,因此重建可能会失真。
[编辑1]补救措施
还有更多选项可以修复此问题,例如:
使用浮动复杂矩阵进行计算,仅使用位图进行可视化。
这是最安全的方法,因为您避免了额外的转换回合。这种方法具有最佳精度。但您需要重写DIP / CV算法以支持复杂的域矩阵,而不是需要少量工作的位图。
重写您的转化以支持实部和虚部图像
你的转换真的很糟糕,因为它不会存储/恢复Real和Imaginary部分,而且它没有考虑负值(至少我没有看到它而是被切断降至零,这是错误的)。我会将转换重写为:
// 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;
或覆盖更大的区间然后复杂的矩阵值。
您还可以将Real和Imaginary部分编码为单个图像,例如图像的前半部分可以是Real,然后是Imaginary部分。在这种情况下,您根本不需要更改函数标题或名称..但您需要将图像处理为2个连接的正方形,每个正方形具有不同的含义......
您还可以使用R = Real, B = Imaginary
或任何其他适合您的编码的RGB图像。
[Edit2]一些例子让我的观点更清晰
方法#1的示例
图像采用浮点2D复杂矩阵的形式,图像仅为可视化而创建。这种方式几乎没有舍入错误。这些值没有标准化,因此最初每个像素/单元的范围为<0.0,255.0>
,但在变换和缩放后,它可能会发生很大变化。
如您所见,我添加了缩放比例,因此所有像素都乘以315以实际看到任何内容,因为 FFT 输出值很小,除了少数单元格。但仅限于可视化,复杂矩阵不变。
方法#2的示例
正如我之前提到的那样,您不处理负值,将值标准化为范围<0,1>
,然后通过缩放和舍入来回归,并且每个像素仅使用8
位来存储子结果。我尝试用我的代码模拟它,这就是我得到的(使用复杂的域而不是像你那样使用错误的功率谱)。这里C ++只作为模板示例,因为你没有它背后的函数和类:
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 之后看到的那样,并且图像包裹非常暗,大多数值都会四舍五入。因此解包后的结果和 IDFFT 非常纯粹。
这里有一些代码解释:
c.st(bmpre,bmpim)
与您的ToBitmap
c.ld(bmpre,bmpim)
与您的ToComplex
c.mul(scale)
将复杂矩阵c
乘以scale
rgb2i
将RGB转换为灰度强度<0,255>
i2iii
转换灰度强度ro灰度RGB图像答案 1 :(得分:0)
我在这个谜题中并不是很好,但要仔细检查这个划分。
comp[y, x] = new Complex((float)*src / 255, comp[y, x].Imaginary);
如此处所述,您可以放松精度 备注部分中的Complex class definition。 在你的情况下可能会发生这种情况。 希望这会有所帮助。