【问题标题】:Graphics.DrawImage creates different image data on x86 and x64Graphics.DrawImage 在 x86 和 x64 上创建不同的图像数据
【发布时间】:2012-01-09 06:46:18
【问题描述】:

嘿!

这是我的设置
我有一个从一系列图像中提取特征的 c# 应用程序。由于数据集(几千张图像)的大小,它是高度并行化的,这就是为什么我们有一台带有 ssd 的高端机器,它在 Windows7 x64(.NET4 运行时)上运行以解除繁重的工作。我正在使用 Windows 窗体的 Visual Studio 2008 (.NET3.5) 下的 Windows XP SP3 x86 机器上开发它 - 顺便说一句,没有机会迁移到 WPF。

编辑3: 这很奇怪,但我想我终于知道发生了什么。似乎是在两台机器上产生不同结果的图像格式的编解码器!我不确切知道那里发生了什么,但 xp 机器上的解码器产生的结果比 win7 的更健全。遗憾的是,更好 版本仍在 x86 XP 系统中:(。我想解决这个问题的唯一方法是将输入图像格式更改为无损格式,如 png 或 bmp(愚蠢的我没有考虑文件首先是格式:))。

编辑2: 感谢你付出的努力。我想我会坚持自己实现一个转换器,这不是我想要的,但我必须以某种方式解决它:)。如果有人正在阅读本文并对我有一些想法,请告诉我。

编辑: 在 cmets 中,我被建议为此使用第三方库。我认为我没有让自己足够清楚,因为我真的不想使用 DrawImage 方法——这只是一个有缺陷的快速破解来获得一个实际工作的new Bitmap(tmp, ... myPixelFormat),希望使用一些插值。我想要实现的只是将传入的图像转换为带有一些标准插值的通用 PixelFormat。

我的问题如下。一些源图像采用 Indexed8bpp jpg 格式,与 WinForms 图像处理不太好。因此,在我的图像加载逻辑中,会检查索引图像,它将图像转换为我的应用程序默认格式(例如 Format16bpp),如下所示:

Image GetImageByPath(string path)
{
    Image result = null;

    using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
    {
        Image tmp = Image.FromStream(fs); // Here goes the same image ...

        if (tmp.PixelFormat == PixelFormat.Format1bppIndexed ||
            tmp.PixelFormat == PixelFormat.Format4bppIndexed ||
            tmp.PixelFormat == PixelFormat.Format8bppIndexed ||
            tmp.PixelFormat == PixelFormat.Indexed)
        {
            // Creating a Bitmap container in the application's default format
            result = new Bitmap(tmp.Width, tmp.Height, DefConf.DefaultPixelFormat);
            Graphics g = Graphics.FromImage(result);
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;

            // We need not to scale anything in here
            Rectangle drawRect = new Rectangle(0, 0, tmp.Width, tmp.Height);

            // (*) Here is where the strange thing happens - I know I could use
            // DrawImageUnscaled - that isn't working either
            g.DrawImage(tmp, drawRect, drawRect, GraphicsUnit.Pixel);

            g.Dispose();
        }
        else 
        {
            result = new Bitmap(tmp); // Just copying the input stream
        }

        tmp.Dispose();
    }

    // (**) At this stage the x86 XP memory image differs from the 
    // the x64 Win7 image despite having the same settings
    // on the very same image o.O
    result.GetPixel(0, 0).B; // x86: 102, x64: 102
    result.GetPixel(1, 0).B; // x86: 104, x64: 102
    result.GetPixel(2, 0).B; // x86:  83, x64:  85
    result.GetPixel(3, 0).B; // x86: 117, x64: 121
    ...
    return result;
}

我将问题追踪到(*)。我认为 InterpolationMode 与它有关,但我选择其中的哪一个没有区别,结果在两个系统上的 (**) 上是不同的。我一直在研究带有一些愚蠢的复制和粘贴行的测试图像数据,以确保以错误的方式访问数据不是问题。

所有图像看起来像这样Electron Backscatter Diffraction Pattern。实际的颜色值略有不同,但它们携带了大量信息——插值甚至增强了它。看起来 x86 机器上的合成算法使用 InterpolationMode 属性,而 x64 只是将调色板值散开而不考虑任何插值。

直到我对我的应用程序中的数据实现直方图视图功能的那一天,我才注意到两台机器的输出之间有任何差异。在 x86 机器上,它是平衡的,正如人们在观看图像时所期望的那样。另一方面,x64 机器宁愿给出某种稀疏条形图,即索引图像数据的指示。它甚至会影响整个应用程序的整体输出数据——具有相同数据的两台机器上的输出不同,这不是一件好事。

对我来说,这看起来像是 x64 实现中的一个错误,但这只是我 :-)。我只希望 x64 机器上的图像具有与 x86 相同的值。

如果有人有想法,我会非常高兴。我多年来一直在网上寻找类似的行为,但抵抗似乎是徒劳的:)

哦,小心……鲸鱼!

【问题讨论】:

  • 是的,Graphics.DrawImage() 采用了一些捷径,这些捷径会导致像素颜色值发生细微的变化。太小而无法被人眼感知。 64 位算法会稍有不同。解决此问题的一种可能方法是声明 x86 版本错误:)
  • 哇,真快......我明白你的意思,但它并没有解决我的问题,因为它是 x86 版本产生“更好”的结果:)
  • 我不建议依赖 Graphics DrawImage 方法的数值稳定性,因为它的目的是显示图像,而不是保存信息。例如,它的实施可能会在未来发生变化。
  • 这正是我的想法,但对tmp.Clone() 的调用只会创建一个卷影副本,而new Bitmap(tmp) 方法即使提供不同的格式也不会改变PixelFormat ...我'我也不喜欢我的解决方案,所以如果有人有更好的方法,我愿意接受
  • 那里有很多图像处理库。如果 .NET 的内置库由于不一致而不够用,您可以使用第三方库。

标签: c# 32bit-64bit system.graphics


【解决方案1】:

如果您想确保始终以相同的方式完成此操作,则必须编写自己的代码来处理它。幸运的是,这并不难。

您的 8bpp 图像有一个包含实际颜色值的调色板。您需要阅读该调色板并将颜色值(如果我没记错的话,是 24 位)转换为 16 位颜色值。您将在转换中丢失信息,但您已经在转换中丢失了信息。至少这样,您会以可预见的方式丢失信息。

将转换后的颜色值(不会超过 256 个)放入可用于查找的数组中。那么……

创建您的目标位图并调用LockBits 以获取指向实际位图数据的指针。调用LockBits 获取指向源位图的位图数据的指针。然后,对于每个像素:

read the source bitmap pixel (8 bytes)
get the color value (16 bits) from your converted color array
store the color value in the destination bitmap

您可以使用GetPixelSetPixel 执行此操作,但速度会非常慢。

【讨论】:

  • 感谢您的建议,我会考虑的。这实际上是我想通过使用框架的内置功能来避免的,所以我不必关心这些事情。更不用说这个功能是应用程序的#1瓶颈。
【解决方案2】:

我隐约记得 .NET 图形类依赖于 GDI+。如果今天仍然如此,那么在具有不同视频驱动程序的不同 64 位系统上尝试您的应用程序是没有意义的。您最好的选择是使用原始 GDI 操作 (P/Invoke) 进行插值,或者在软件中编写自己的像素插值例程。这两种选择都不是特别有吸引力。

【讨论】:

  • System.Drawing 确实是基于 GDI+,而不是 GDI(主要的例外是 TextRenderer,它在这里没有发挥作用)。所以关于视频驱动程序的部分可能不相关。
  • 删除了怀旧背景。 :P
  • +1 历史矫枉过正,我被迷住了 :) ...我在编写 C# 时没有想到这么低级的东西 ...我还阅读了有关使用 GDI+ 的 .NET
【解决方案3】:

你真的应该使用 OpenCV 来处理这样的图像,它在 C# 中可用:OpenCVSharp

【讨论】:

  • 感谢您的链接,我一开始并不了解 OpenCV。我会进一步调查这个:)
  • 我现在复习了OpenCV,它似乎很强大。不幸的是,我做的不是这种特征检测:)。此外,LGPL 不是该项目的一个选项,但仍然感谢您向我指出这个!
【解决方案4】:

我对图形对象使用标准方法,并且使用此设置优于 X86。在发布运行时计算性能,而不是调试。还要检查项目属性中的优化代码,构建选项卡。 Studio 2017,框架 4.7.1

public static Graphics CreateGraphics(Image i)
{
    Graphics g = Graphics.FromImage(i);
    g.CompositingMode = CompositingMode.SourceOver;
    g.CompositingQuality = CompositingQuality.HighSpeed;
    g.InterpolationMode = InterpolationMode.NearestNeighbor;
    g.SmoothingMode = SmoothingMode.HighSpeed;
    return g;
}

【讨论】:

    猜你喜欢
    • 2018-03-08
    • 1970-01-01
    • 1970-01-01
    • 2017-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多