图像的逆FFT期间丢失数据

时间:2016-07-20 23:46:42

标签: c#-4.0 image-processing bitmap fft ifft

我使用以下代码将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

ImageDataConverter

输出:

enter image description here

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

这是因为复杂的位图反之亦然,并不像预期的那样工作。

如何纠正ToComplex()和ToBitmap()函数,以免丢失数据?

2 个答案:

答案 0 :(得分:4)

我不在 C#中编码,所以在极端偏见的情况下处理这个答案!

从快速看,我发现了一些问题:

  1. <强> ToComplex()

    将BMP转换为2D复杂矩阵。当你转换时,你将假想的部分保持不变,但是在同一个函数的开头你有:

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

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

  2. <强> ToBitmap()

    您正在保存我认为sqrt( Re*Re + Im*Im )的幅度,因此功率谱不是原始的复杂值,因此您无法重建...您应该将Re,Im存储在2个单独的图像中。

  3. 每像素8位

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

  4. [编辑1]补救措施

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

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

      这是最安全的方法,因为您避免了额外的转换回合。这种方法具有最佳精度。但您需要重写DIP / CV算法以支持复杂的域矩阵,而不是需要少量工作的位图。

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

      你的转换真的很糟糕,因为它不会存储/恢复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图像。

    3. [Edit2]一些例子让我的观点更清晰

      1. 方法#1的示例

        图像采用浮点2D复杂矩阵的形式,图像仅为可视化而创建。这种方式几乎没有舍入错误。这些值没有标准化,因此最初每个像素/单元的范围为<0.0,255.0>,但在变换和缩放后,它可能会发生很大变化。

        approach #1

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

      2. 方法#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");
        

        这里的子结果:

        approach #2 8bit

        正如您在 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。 在你的情况下可能会发生这种情况。 希望这会有所帮助。