图形.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。
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机器上的映像相同的值。
如果有人有什么主意,我将非常高兴。我一直在网上寻找类似的行为,但抵抗似乎是徒劳的:)
哦,小心…一头鲸鱼!
如果您想确保这总是以相同的方式完成,您将不得不编写自己的代码来处理它。幸运的是,这并不太难。
您的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
你可以在GetPixel
和SetPixel
中这样做,但它会非常非常慢
我隐约记得。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;
}