「玩一玩」量化GDI+:快速Bitmap读写像素——到底有多快?

Conmajia 2012-07-10 12:42:24
加精
发个帖子然后蒸馒头吃

--------

这是一个古老的技巧:
使用Bitmap类时经常会用到GetPixel和SetPixel,但是这两个方法直接使用都比较慢,所以一般都会使用LockBits/UnlockBits将位图在内存中锁定,以加快操作速度。
MSDN上的标准参考是这样的:
    private void LockUnlockBitsExample(PaintEventArgs e)
{

// Create a new bitmap.创建位图
Bitmap bmp = new Bitmap("c:\\fakePhoto.jpg");

// Lock the bitmap's bits. 锁定位图
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData =
bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
bmp.PixelFormat);

// Get the address of the first line.获取首行地址
IntPtr ptr = bmpData.Scan0;

// Declare an array to hold the bytes of the bitmap.定义数组保存位图
int bytes = Math.Abs(bmpData.Stride) * bmp.Height;
byte[] rgbValues = new byte[bytes];

// Copy the RGB values into the array.复制RGB值到数组
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);

// Set every third value to 255. A 24bpp bitmap will look red. 把每像素第3个值设为255.24bpp的位图将变红
for (int counter = 2; counter < rgbValues.Length; counter += 3)
rgbValues[counter] = 255;

// Copy the RGB values back to the bitmap 把RGB值拷回位图
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);

// Unlock the bits.解锁
bmp.UnlockBits(bmpData);

// Draw the modified image.绘制更新了的位图
e.Graphics.DrawImage(bmp, 0, 150);
}

因为我比较闲,所以我在想这样的问题:加快之后到底有多快?
为此,我稍微调整了下之前用过的BitmapEx类(记得应该是人脸识别还是什么代码里用过),改成FastBitmap,然后创建了测试程序,搜集了一系列测试用例。(点击左上图片框打开图片文件,无异常处理)

测试用例如下:

为了保证不受文件格式影响,统一使用24bpp的bmp格式。(感谢科技发展,内存白菜价,不然单个文件将近200MB可真要让我麻烦一番。)

考察分为GetPixel和SetPixel两个部分,把读写分开。测试代码(以GetPixel为例)非常简单,如下:
for (int y = 0; y < h; y++)
{
for (int x = 0; x < w; x++)
{
tmp = bmp.GetPixel(x, y);
}
}

其中bmp分别为Bitmap和FastBitmap。
为了专注于对比结果,虽然逐像素遍历图像非常耗费时间,但并没有刻意使用并行计算,使用单个CPU内核完成。所以如果你打算用这个程序对特别巨大的图片(10000×10000数量级以上)进行测试,还请慎重。

经过测试,得到了这样的测试结果:

从测试结果来看,号称「Fast」果然有两把刷子,平均提升效率在90%~95%,也就说性能提高了10~20倍。
这个结果,虽然还不算很快,但我觉得基本到了GDI+的极限了(剩下的就是机器性能的提升了),如果再要提升,可以试试并行计算、C++ native、直接调用MMX/SSE指令、CUDA之类的技术。
我不知道现在技术发展下还有多少用到Bitmap的场合,只是觉得:追求开发效率和性能平衡的时候,Bitmap也能成为一个不错的选择。

测试程序下载:点击下载

--------------

顺便求解答:
1.C++ Native/Managed的测试结果
2.葱油饼/状元饼的做法
3.怎样揉面能让馒头更好吃
...全文
7479 161 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
161 条回复
切换为时间正序
请发表友善的回复…
发表回复
shateiel 2014-08-02
  • 打赏
  • 举报
回复
难得一见的好帖。顶起来。
wenzishou 2014-05-18
  • 打赏
  • 举报
回复
这么好的帖子,顶起, 谢了,正在学习中
言多必失 2012-10-07
  • 打赏
  • 举报
回复
值得标记
Hzi__ 2012-10-06
  • 打赏
  • 举报
回复
学习,谢谢分享
卧_槽 2012-09-24
  • 打赏
  • 举报
回复
[Quote=引用 156 楼 的回复:]

引用 153 楼 的回复:

事在人为。

算法为王,语言的话效率都不会差到哪里去。

对于C#来说,用lockbits这种方式来做专业图像处理时不好的而已。

在算法相同的情况下,不考虑多线程,并行运算,及时是MMX/SSE优化,对于复杂的算法来说差距不会特别大,就拿楼上的3000*3000半径50的高斯模糊,用高级语言600ms,用汇编400ms,也没有什么质的提升的。
……
http://files.cnblogs.com/Imageshop/ASM.rar

这里是ASM的代码,关于用C#写的要过段时间了,我刚学C#,语法还不熟悉。
[/Quote]

非常期待你的.net版本问世。
laviewpbt 2012-09-22
  • 打赏
  • 举报
回复
[Quote=引用 153 楼 的回复:]

事在人为。

算法为王,语言的话效率都不会差到哪里去。

对于C#来说,用lockbits这种方式来做专业图像处理时不好的而已。

在算法相同的情况下,不考虑多线程,并行运算,及时是MMX/SSE优化,对于复杂的算法来说差距不会特别大,就拿楼上的3000*3000半径50的高斯模糊,用高级语言600ms,用汇编400ms,也没有什么质的提升的。
[/Quote]


http://files.cnblogs.com/Imageshop/ASM.rar

这里是ASM的代码,关于用C#写的要过段时间了,我刚学C#,语法还不熟悉。
卧_槽 2012-09-22
  • 打赏
  • 举报
回复
[Quote=引用 152 楼 的回复:]

引用 149 楼 的回复:

我想说,楼主所有的实验我都尝试过了。为了给3000×3000的图片做半径50的高斯模糊。(我想达到ps在5.0版本就已经达到的效率。)

最终发现,.net中的gdi+,unsafe模式,其实和c/c++直接操作内存的速度在一个数量级上。效率大约要损失10%以下。而再直接调用MMX/SSE指令(写了一个C++的dll,ms有例子程序)之后,速度还是只能提升……
[/Quote]

还有一种优化,对于图像处理来说,那就是调用显卡运算。当然这个也不是语言上,而是在硬件上来优化了。
卧_槽 2012-09-22
  • 打赏
  • 举报
回复
[Quote=引用 153 楼 的回复:]

事在人为。

算法为王,语言的话效率都不会差到哪里去。

对于C#来说,用lockbits这种方式来做专业图像处理时不好的而已。

在算法相同的情况下,不考虑多线程,并行运算,及时是MMX/SSE优化,对于复杂的算法来说差距不会特别大,就拿楼上的3000*3000半径50的高斯模糊,用高级语言600ms,用汇编400ms,也没有什么质的提升的。
[/Quote]
600ms,您写个看看,我看到的快的都是6s这个数量级。
laviewpbt 2012-09-21
  • 打赏
  • 举报
回复
事在人为。

算法为王,语言的话效率都不会差到哪里去。

对于C#来说,用lockbits这种方式来做专业图像处理时不好的而已。

在算法相同的情况下,不考虑多线程,并行运算,及时是MMX/SSE优化,对于复杂的算法来说差距不会特别大,就拿楼上的3000*3000半径50的高斯模糊,用高级语言600ms,用汇编400ms,也没有什么质的提升的。
Conmajia 2012-09-21
  • 打赏
  • 举报
回复
[Quote=引用 149 楼 的回复:]

我想说,楼主所有的实验我都尝试过了。为了给3000×3000的图片做半径50的高斯模糊。(我想达到ps在5.0版本就已经达到的效率。)

最终发现,.net中的gdi+,unsafe模式,其实和c/c++直接操作内存的速度在一个数量级上。效率大约要损失10%以下。而再直接调用MMX/SSE指令(写了一个C++的dll,ms有例子程序)之后,速度还是只能提升6-7%.已经失去了优化的必要。
……
[/Quote]所以还是「人」最为重要。。语言的优化是最下等的优化。。
yaoyaoshoubi 2012-09-21
  • 打赏
  • 举报
回复
有没有源码??
卧_槽 2012-09-21
  • 打赏
  • 举报
回复
[Quote=引用 120 楼 的回复:]

一般来说,一次截图这么大的图本身就是个错误,应用小图来拼接,以避免内存不足.
另外,GetPixel和SetPixel本身不是就动态或整图操作来设计的函数,只是简化了代码,简化程序员使用.LockBits是针对高级操作来提供的,两者提供的作用场合不同,这个不能作为比较.当然,在.NET中使用LockBits是"只会.NET语言或不愿意用其他语言混合开发"的人来说,算是在.NET中最高速的做法了……
[/Quote]

C/C++可以比LockBits提高不到10%的效率。测试过多种方式。
但是用托管程序调用C/C++进行混合编程了,这个效率又比纯的C/C++损失不少,结果是得不偿失。而且写的一手好C,又甘于做.net应用开发的人,忒难找。
卧_槽 2012-09-21
  • 打赏
  • 举报
回复
我想说,楼主所有的实验我都尝试过了。为了给3000×3000的图片做半径50的高斯模糊。(我想达到ps在5.0版本就已经达到的效率。)

最终发现,.net中的gdi+,unsafe模式,其实和c/c++直接操作内存的速度在一个数量级上。效率大约要损失10%以下。而再直接调用MMX/SSE指令(写了一个C++的dll,ms有例子程序)之后,速度还是只能提升6-7%.已经失去了优化的必要。
.net中的GDI+不知道你们有没有翻过源码,最后就是调用一个c++的gdiplus.dll。

最后发现,其实图形图像还是得靠算法。
因为其实很简单,就是一个矩阵摆在内存里。图像处理速度的关键还是靠加减乘除。

ps:看帖子的时候有位老兄说只要零点几毫秒,着实吓我一跳,我以为c真的可以逆天了,后来发现原来是他test写的有问题了,又是一番失落,原以为可以在程序级别解决算法的问题了呢。
shesh 2012-07-23
  • 打赏
  • 举报
回复
[Quote=引用 99 楼 的回复:]

所谓图像,只不过是内存中一块连续的区域罢了。任何图像(静态的)在内存中的格式都是Bitmap.

那为什么不把它当成byte[]来搞呢?然后按位取反,不用休休答答地用什么GDI+之类的。

别喷我,我只是打酱油的。
[/Quote]

道理是没错,但有几个会自己去操作矩阵呢,一般都是利用GDI+提供的方法了
百分百好牛 2012-07-22
  • 打赏
  • 举报
回复
不明觉厉。
失落的code 2012-07-22
  • 打赏
  • 举报
回复
我就来围观一下大神。
Conmajia 2012-07-22
  • 打赏
  • 举报
回复
[Quote=引用 144 楼 的回复:]

楼主说的内容太深奥,不是太明白。纯粹路过回答下楼主关于和面的问题,面团和好以后最好放在冰箱里面冷却30分钟-1小时进行自然发酵,术语叫做醒面,据说这样做过面团会好吃一点。
[/Quote]
在冰箱里醒?酵母都冬眠了吧?有空试试。但我记得是最好20多到30度左右醒,酵母活性最大。
gekiranger 2012-07-22
  • 打赏
  • 举报
回复
楼主说的内容太深奥,不是太明白。纯粹路过回答下楼主关于和面的问题,面团和好以后最好放在冰箱里面冷却30分钟-1小时进行自然发酵,术语叫做醒面,据说这样做过面团会好吃一点。
秀小川 2012-07-19
  • 打赏
  • 举报
回复
厉害!
songweixing321 2012-07-19
  • 打赏
  • 举报
回复
我服了你了!……真的!
加载更多回复(108)

111,093

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧