图形.DrawImage在x86和x64上创建不同的图像数据

本文关键字:图像 数据 创建 DrawImage x86 x64 图形 | 更新日期: 2023-09-27 18:16:03

大家好!

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

Edit3: 很奇怪,但我想我终于知道是怎么回事了。似乎是图像格式的编解码器在两台机器上产生不同的结果!我不知道到底发生了什么,但是xp机器上的解码器比win7产生更理智的结果。遗憾的是,更好的版本仍然在x86 XP系统中:(。我想唯一的解决方案是将输入图像格式更改为png或bmp等无损格式(愚蠢的我一开始就没有考虑文件格式:))。

Edit2: 谢谢你的努力。我想我会坚持自己实现一个转换器,这不是我想要的,但我必须以某种方式解决它:)。如果有人读到这篇文章,对我有什么建议,请告诉我。

编辑:

在评论中,有人建议我使用第三方库。我想我没有让自己足够清楚,因为我真的不想使用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与它有关,但我选择的结果在两个系统上的(**)是不同的,没有区别。我一直在用一些愚蠢的复制粘贴行调查测试图像数据,以确保它不是以错误的方式访问数据的问题。

这些图像在一起看起来像这个电子后向散射衍射图。实际的颜色值略有不同,但它们携带了很多信息-插值甚至增强了它。看起来x86机器上的组合算法使用了InterpolationMode属性,而x64只是将调色板的值扩散出去,而不考虑任何插值。

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

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

如果有人有什么主意,我将非常高兴。我一直在网上寻找类似的行为,但抵抗似乎是徒劳的:)

哦,小心…一头鲸鱼!

图形.DrawImage在x86和x64上创建不同的图像数据

如果您想确保这总是以相同的方式完成,您将不得不编写自己的代码来处理它。幸运的是,这并不太难。

您的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中这样做,但它会非常非常慢

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

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

我使用图形对象的标准方法,在此设置下性能优于X86。在发布运行时计算性能,而不是调试时。还要在项目属性、构建选项卡中检查优化代码。Studio 2017, framework 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;
}