Graphics.DrawImage 在 x86 和 x64 上创建不同的图像数据
嘿!
这是我的设置:
我有一个 c# 应用程序,可以从一系列图像中提取特征。由于数据集的大小(数千张图像),它是高度并行的,这就是为什么我们拥有一台在 Windows7 x64(.NET4 运行时)上运行的带有 SSD 的高端机器来减轻繁重的工作。我正在 Windows XP SP3 x86 计算机上使用 Windows Forms 在 Visual Studio 2008 (.NET3.5) 下开发它 - 顺便说一下,没有机会迁移到 WPF。
编辑3: 这很奇怪,但我想我终于知道发生了什么事。似乎是图像格式的编解码器在两台机器上产生不同的结果!我不知道到底发生了什么,但是 xp 机器上的解码器比 win7 机器上产生更合理的结果。遗憾的是,更好的版本仍然在 x86 XP 系统中:(。我想这个问题的唯一解决方案是将输入图像格式更改为无损格式,例如 png 或 bmp(愚蠢的我没有考虑该文件首先格式化:))。
编辑2: 感谢您的努力。我想我会坚持自己实现一个转换器,这并不完全是我想要的,但我必须以某种方式解决它:)。如果有人正在阅读本文并对我有一些想法,请告诉我。
编辑: 在评论中,建议我为此使用第三方库。我想我没有说得足够清楚,因为我真的不想使用 DrawImage 方法 - 这只是一个有缺陷的快速黑客以获得实际工作的 new Bitmap(tmp, ... myPixelFormat) code> 希望使用一些插值。我想要实现的只是通过一些标准插值将传入图像转换为常见的 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 实现中的一个错误,但这只是我的问题:-)。我只希望 x64 机器上的图像与 x86 机器上的图像具有相同的值。
如果有人有想法,我会非常高兴。我多年来一直在网上寻找类似的行为,但抵抗似乎是徒劳的:)
哦,小心……鲸鱼!
Hey there!
Here is my setting:
I've got a c# application that extracts features from a series of images. Due to the size of a dataset (several thousand images) it is heavily parallelized, that's why we have a high-end machine with ssd that runs on Windows7 x64 (.NET4 runtime) to lift the hard work. I'm developing it on a Windows XP SP3 x86 machine under Visual Studio 2008 (.NET3.5) with Windows Forms - no chance to move to WPF by the way.
Edit3:
It's weird but I think I finally found out what's going on. Seems to be the codec for the image format that yields different results on the two machines! I don't know exactly what is going on there but the decoder on the xp machine produces more sane results than the win7 one. Sadly the better version is still in the x86 XP system :(. I guess the only solution to this one is changing the input image format to something lossless like png or bmp (Stupid me not thinking about the file format in the first place :)).
Edit2:
Thank you for your efforts. I think I will stick to implementing a converter on my own, it's not exactly what I wanted but I have to solve it somehow :). If anybody is reading this who has some ideas for me please let me know.
Edit:
In the comments I was recommended to use a third party lib for this. I think I didn't made myself clear enough in that I don't really want to use the DrawImage approach anyway - it's just a flawed quickhack to get an actually working new Bitmap(tmp, ... myPixelFormat)
that would hopefully use some interpolation. The thing I want to achieve is solely to convert the incoming image to a common PixelFormat with some standard interpolation.
My problem is as follows. Some of the source images are in Indexed8bpp jpg format that don't get along very well with the WinForms imaging stuff. Therefore in my image loading logic there is a check for indexed images that will convert the image to my applications default format (e.g. Format16bpp) like that:
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;
}
I tracked the problem down to (*)
. I think the InterpolationMode has something to do with it but there's no difference which of them I choose the results are different at (**)
on the two systems anyway. I've been investigating test image data with some stupid copy&paste lines, to be sure it's not an issue with accessing the data in a wrong way.
The images all together look like this Electron Backscatter Diffraction Pattern. The actual color values differ subtly but they carry a lot of information - the interpolation even enhances it. It looks like the composition algorithm on the x86 machine uses the InterpolationMode property whereas the x64 thingy just spreads the palette values out without taking any interpolation into account.
I never noticed any difference between the output of the two machines until the day I implemented a histogram view feature on the data in my application. On the x86 machine it is balanced as one would expect it from watching the images. The x64 machine on the other hand would rather give some kind of sparse bar-diagram, an indication of indexed image data. It even effects the overall output data of the whole application - the output differs on both machines with the same data, that's not a good thing.
To me it looks like a bug in the x64 implementation, but that's just me :-). I just want the images on the x64 machine to have the same values as the x86 ones.
If anybody has an idea I'd be very pleased. I've been searching for similar behavior on the net for ages but resistance seems futile :)
Oh look out ... a whale!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
如果您想确保始终以相同的方式完成此操作,则必须编写自己的代码来处理它。幸运的是,这并不太难。
您的 8bpp 图像有一个包含实际颜色值的调色板。您需要读取该调色板并将颜色值(如果我没记错的话,是 24 位)转换为 16 位颜色值。您将在转换中丢失信息,但您已经在转换中丢失信息。至少这样,您就会以可预测的方式丢失信息。
将转换后的颜色值(不会超过 256 个)放入可用于查找的数组中。然后...
创建目标位图并调用 LockBits 来获取指向实际位图数据的指针。调用LockBits来获取指向源位图的位图数据的指针。然后,对于每个像素:
您可以使用
GetPixel
和SetPixel
执行此操作,但速度会非常慢。If you want to make sure that this is always done the same way, you'll have to write your own code to handle it. Fortunately, it's not too difficult.
Your 8bpp image has a palette that contains the actual color values. You need to read that palette and convert the color values (which, if I remember correctly, are 24 bits) to 16-bit color values. You're going to lose information in the conversion, but you're already losing information in your conversion. At least this way, you'll lost the information in a predictable way.
Put the converted color values (there won't be more than 256 of them) into an array that you can use for lookup. Then ...
Create your destination bitmap and call
LockBits
to get a pointer to the actual bitmap data. CallLockBits
to get a pointer to the bitmap data of the source bitmap. Then, for each pixel:You could do this with
GetPixel
andSetPixel
, but it would be very very slow.我依稀记得 .NET 图形类依赖于 GDI+。如果今天情况仍然如此,那么在具有不同视频驱动程序的不同 64 位系统上尝试您的应用程序就没有意义了。最好的选择是使用原始 GDI 操作 (P/Invoke) 进行插值,或者在软件中编写自己的像素插值例程。这两种选择都不是特别有吸引力。
I vaguely seem to recall that .NET graphics classes rely on GDI+. If that's still the case today, then there's no point in trying your app on different 64 bit systems with different video drivers. Your best bet would be to either do the interpolation using raw GDI operations (P/Invoke) or write your own pixel interpolation routine in software. Neither option is particularly attractive.
您确实应该使用 OpenCV 进行类似的图像处理,它在 C# 中可用:OpenCVSharp。
You really should use OpenCV for image handling like that, it's available in C# here: OpenCVSharp.
我对图形对象使用标准方法,并且使用此设置优于 X86。计算发布运行时的性能,而不是调试时的性能。还要在项目属性、构建选项卡中检查优化代码。 Studio 2017,框架 4.7.1
I use a standard method for the graphics object, and with this settings outperforms X86. Count performance at release runs, not debug. Also check optimize code at project properties, build tab. Studio 2017, framework 4.7.1