「玩一玩」量化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.怎样揉面能让馒头更好吃
...全文
7312 161 打赏 收藏 转发到动态 举报
写回复
用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)
我将带领大家来系统学习Windows的窗口编程,包括消息、窗口、GDI绘图、游戏开发等。本课程比较基础,非常适合初学者入门,读者可以边学习边实践。具体的章节目录和课程内容如下所示:---------------------------------------------Windows游戏编程系列之1:GUI界面编程及游戏入门实战1、Windows创建第一个窗口 WinMain入口函数 5进行Windows编程的调试手法 6窗口从哪里来? 7窗口编程的步骤 7窗口编程需要的主要结构 8窗口编程需要的主要API 92、Windows的窗口过程与消息机制 如何留住窗口? 121)Windows的消息与消息循环 142)消息处理函数与常用消息 17)Windows的窗口过程函数 19 3、GDI编程之设备上下文 1)GDI的通用编程框架 222)GDI的绘图步骤 253)GDI获取设备句柄 254、GDI编程之绘制几何图形 画点、线 28颜色COLORREF 29矩形 29画圆、饼图、弦图 305、GDI编程之自定义画笔画刷画笔简介 32画刷简介 33画笔案例 33画刷案例 346、GDI编程之绘制文字 DrawText函数 35TextOut 函数 (wingdi.h) 36CreateFont函数 37绘制文本案例 377、GDI编程之绘制位图 位图简介 381)在资源中添加位图资源 392)从资源中加载位图: LoadBitmap 393)创建一个与当前DC相匹配的DC(内存DC) 394)将bitmap放入匹配的DC中:SelectObject 405)成像(1:1 比例 ) 406)取出位图 407)释放位图 418)释放匹配的DC 41绘制位图案例 41   8、Windows鼠标键盘消息 一、键盘消息 421、键盘消息 422、消息参数: 423、消息的使用: 424、键盘消息的案例代码 43二、鼠标消息 441、基本鼠标消息 442、双击消息 443、滚轮消息 454、不响应双击消息 45 9、Windows定时器消息 定时器消息介绍 47创建定时器 47关闭定时器 47定时器消息案例代码 4810、GDI游戏之跳舞动画 11、GDI游戏之走路动画 12、GDI贪吃蛇游戏实战  

110,535

社区成员

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

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

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