图像的逆 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)

(ImageDataConverter)

输出:

如您所见,FFT 工作正常,但 I-FFT 不正确。

那是因为位图到复合体和反之亦然没有按预期工作。

如何更正 ToComplex() 和 ToBitmap() 函数以使其不丢失数据?

我不会在 C# 中编写代码,所以以极端偏见处理这个答案!

快速浏览一下,我发现了几个问题:

  1. ToComplex()

    正在将 BMP 转换为二维复矩阵。当您转换时,虚部保持不变,但在同一函数的开头,您有:

    Complex[,] complex2D = null;
    complex2D = new Complex[width, height];
    

    因此虚部未定义或为零取决于您的复杂 class 构造函数。 这意味着您丢失了重建所需数据的一半!!!您应该从 2 个图像恢复原始复数矩阵,一个用于结果的实部,第二个用于结果的虚部。

  2. ToBitmap()

    你正在节省幅度,我认为 sqrt( Re*Re + Im*Im ) 所以它是功率谱而不是原始复数值,所以你不能重建回来......你应该将 Re,Im 存储在 2 个单独的图像中。

  3. 每像素 8 位

    这并不多,在 FFT/IFFT 之后可能会导致严重的舍入误差,因此重建可能会非常失真。

[Edit1] 补救措施

有更多选项可以修复此问题,例如:

  1. 使用浮动复杂矩阵进行计算,位图仅用于可视化。

    这是最安全的方法,因为您可以避免额外的转换四舍五入。这种方法具有最好的精度。但是你需要重写你的 DIP/CV 算法以支持复杂的域矩阵而不是需要大量工作的位图。

  2. 重写您的转换以支持实部和虚部图像

    你的转换真的很糟糕,因为它没有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. 方法示例 1

    图像采用浮点二维复矩阵形式,创建图像仅用于可视化。这样舍入误差很小。这些值未标准化,因此范围一开始是每个 pixel/cell <0.0,255.0>,但在转换和缩放之后它可能会发生很大变化。

    如您所见,我添加了缩放比例,因此所有像素都乘以 315 以实际看到任何东西,因为 FFT 输出值很小,除了少数单元格。但仅对于可视化,复数矩阵不变。

  2. 方法示例 #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 备注 部分。 可能这发生在你的情况下。 希望这有帮助。