【问题标题】:Loss of data during the Inverse-FFT of an Image图像逆 FFT 期间的数据丢失
【发布时间】:2016-07-20 23:46:41
【问题描述】:

我正在使用以下代码将位图转换为复杂,反之亦然。

尽管这些是直接从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)

输出:

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

这是因为复杂的位图(反之亦然)没有按预期工作。

如何纠正 ToComplex() 和 ToBitmap() 函数以使它们不会丢失数据?

【问题讨论】:

  • 小提琴不包含ImageDataConverter 类。我们可以看看吗?我怀疑转换回来后数据的动态范围存在问题。
  • 我希望您知道 FFT 结果很复杂......您的 FFT 结果看起来只是灰度标量,并不复杂,因此您很可能会丢弃虚部或使用功率谱而不是复杂域此类数据无法重建原始图像。更别说换班了……看看What should be the input and output for an FFT image transformation?
  • @Spektre 更有理由检查ImageDataConverter 类的外观。我认为这是可疑结果背后的罪魁祸首。看着小提琴,FFT 代码似乎很好,但缺少显示图像数据时处理转换的代码。顺便说一句,很棒的链接。

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


【解决方案1】:

我不使用 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 之后导致显着的舍入误差,因此重建可能会真正失真。

[Edit1] 补救措施

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

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

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

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

    您的转换非常糟糕,因为它没有按应有的方式存储/恢复实部和虚部,而且 它没有考虑负值(至少我没有看到它,而是它们被剪切了降至零,这是错误的)。我会将转换重写为:

    // 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;
    

    或覆盖比复矩阵值更大的区间。

    您还可以将实部和虚部都编码为单个图像,例如图像的前半部分可以是实部,接下来是虚部。在这种情况下,您根本不需要更改函数标题或名称..但您需要将图像处理为 2 个连接的正方形,每个正方形具有不同的含义...

    您还可以在 R = Real, B = Imaginary 或任何其他适合您的编码中使用 RGB 图像。

[Edit2]一些例子让我的观点更清楚

  1. 方法#1的示例

    图像是浮点二维复数矩阵的形式,图像仅用于可视化。这种方式几乎没有舍入误差。这些值未标准化,因此最初的范围是每个像素/单元格 &lt;0.0,255.0&gt;,但经过变换和缩放后,它可能会发生很大变化。

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

  2. 方法#2的示例

    正如我之前提到的,您不处理负值,将值标准化为&lt;0,1&gt; 范围,然后通过缩放和舍入以及仅使用每像素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 和环绕之后看到的那样,图像真的很暗,并且大多数值都被四舍五入。所以 unwrap 和 IDFFT 之后的结果真的很纯。

    这里是对代码的一些解释:

    • c.st(bmpre,bmpim) 与您的 ToBitmap 相同
    • c.ld(bmpre,bmpim) 与您的 ToComplex 相同
    • c.mul(scale) 将复数矩阵 c 乘以 scale
    • rgb2i 将 RGB 转换为灰度强度 &lt;0,255&gt;
    • i2iii将灰度强度转换为灰度RGB图像

【讨论】:

  • 首先,90%的代码是从accord.net复制而来的。其次,这些功能单独运行良好。
  • @anonymous 我不怀疑他们工作正常,但他们没有做你想要/需要他们做的事情。您正在对不是 FFT 直接结果的数据进行 IFFT,因此您不能期望结果将是原始图像甚至是接近它的任何图像
  • 是的。即使 1 和 2 是直接从accord.net 框架复制的,它们也没有按预期工作。那是真正的废话和令人沮丧的经历。
  • @anonymous 顺便说一句,由于类似和许多其他原因,我很久以前就停止使用 3th 方框架和其他东西了……制作某些东西的时间通常比自己编写代码要长更不用说您可以管理自己的代码。更不用说代码的连续性了(我的应用程序必须工作数十年......而且框架通常在版本之间不兼容甚至停止使用,如果你依赖它们几年后你就会搞砸......)
  • @anonymous 添加了一些示例[Edit2]
【解决方案2】:

我不太擅长这个谜题,但请仔细检查这个划分。

comp[y, x] = new Complex((float)*src / 255, comp[y, x].Imaginary);

您可以按照此处的描述降低精度 Complex class definition备注部分。 在你的情况下可能会发生这种情况。 希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 2014-06-05
    • 2016-05-03
    • 2014-08-31
    • 1970-01-01
    • 2022-08-23
    • 1970-01-01
    • 2011-05-29
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多