作者: 乔克斯
查看: 12705|回复: 10

主题标签Tag

【快速Bitmap读写像素】量化GDI+:快速Bitmap读写像素

[复制链接]
乔克斯 发表于 2014-7-26 12:20:14 | 显示全部楼层 |阅读模式
查看: 12705|回复: 10
量化GDI+:快速Bitmap读写像素
写在前面的话:
本文针对GDI+下Bitmap操作(Get/SetPixel)进行测试,而非寻求最快速的位图处理方式。如果你需要速度上的提升,请使用GDI+以外的技术,如并行计算、调用MMX/SSE指令、CUDA等。

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

[C#] 纯文本查看 复制代码
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,然后创建了测试程序,搜集了一系列测试用例。(点击左上图片框打开图片文件,无异常处理)

--------

这是一个古老的技巧:
使用Bitmap类时经常会用到GetPixel和SetPixel,但是这两个方法直接使用都比较慢,所以一般都会使用LockBits/UnlockBits将位图在内存中锁定,以加快操作速度。
MSDN上的标准参考是这样的:
C# code
[C#] 纯文本查看 复制代码
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,然后创建了测试程序,搜集了一系列测试用例。(点击左上图片框打开图片文件,无异常处理)
1341893195_7503.png
测试用例如下:
1341893322_9891.png
为了保证不受文件格式影响,统一使用24bpp的bmp格式。(感谢科技发展,内存白菜价,不然单个文件将近200MB可真要让我麻烦一番。)
1341893195_6665.png
考察分为GetPixel和SetPixel两个部分,把读写分开。测试代码(以GetPixel为例)非常简单,如下:
[C#] 纯文本查看 复制代码
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数量级以上)进行测试,还请慎重。
1341893195_1548.png
经过测试,得到了这样的测试结果:
1341893195_4396.png
从测试结果来看,号称「Fast」果然有两把刷子,平均提升效率在90%~95%,也就说性能提高了10~20倍。
这个结果,虽然还不算很快,但我觉得基本到了GDI+的极限了(剩下的就是机器性能的提升了),如果再要提升,可以试试并行计算、C++ native、直接调用MMX/SSE指令、CUDA之类的技术。
我不知道现在技术发展下还有多少用到Bitmap的场合,只是觉得:追求开发效率和性能平衡的时候,Bitmap也能成为一个不错的选择。

后记

有朋友指出:

GDI+这种 LockBits是临时性的把图像的数据读到内存,是不适合于做专业的图像处理软件的,专业做的话一个图像加载后在内存中的格式应该是固定的,这样做算法也就是直接访问这段内存的数据。GetPixel之类的函数的存在也不是为了专业的图像处理的,而是对类似于屏幕取色或DC取色这样小批量数据时方便处理。
要玩速度,图像处理方面的算法先是用普通语言写出来,对算法的核心尽心优化,如果速度还不行,考虑用汇编进一步优化,越简单的算法,用汇编优化的速度能提高的倍数越高,比如,最简单的反色算法,3000*4000*24的图像,一般的语言要100ms左右的处理时间,用汇编的话20ms够了,不过复杂的算法,一般汇编能提升的档次不会有这么明显。

尽管本文的目的并非追求速度,仅仅是「测试」速度,我还是尝试着优化了一下代码,就用上述「反色」操作为例进行了测试。

测试用例选择#7(4096x4096 @ 24bpp),用时299ms,截图如下:

1345873357_6756.png

随后再次改进算法,得到了52ms的速度提升(约17%)。

1345873369_4238.png

这个结果,尽管较一般操作方式快了不少,但和「一般的语言100ms左右」比起来,还是「右」得多了一点(笑)。和汇编的「20ms」(无实验数据)比,差得更远了。

优化代码似乎比较有趣,我就继续试着优化一下。通过调整调用结构,改进算法,使用多线程并行计算,总算是进入50ms了。

仍旧基于Bitmap类的LockBits/UnlockBits。

语言:C#、C#指针

测试机:i3 380M @2.53GHz,2.92G DDR3-1333,Windows 7 32位

速度:约50ms

1345873380_5814.png


网友测试结果对比

以下是部分热心网友给出的测试结果数据,加以对比,供诸君参考。

测试项目统一为对规格为4096x4096x24bpp的位图图像进行反色处理。

测试1

ImageWizard(作者:laviewpbt)

实现:汇编+VB.NET

配置:i3 380M @2.53GHz,2.92G DDR3-1333,Windows 7 32位

用时:25ms

测试2

临时测试(作者:兰征鹏)

实现:VC++.NET调用SSE指令

配置:i7 860@2.93GHz,12G PC1333内存,Windows7 64位

用时:12~19ms

测试3

GebImage(作者:xiaotie)

实现:C#重写全部图像库、unsafe指针

配置:优于测试1

用时:33ms

测试4

本文(作者:野比)

实现:GDI+、unsafe指针

配置:同测试1

用时:46ms


本文示例源码下载: C#位图遍历-图片快速处理.rar (119.86 KB, 下载次数: 221)

评分

参与人数 1金钱 -1 收起 理由
xiaobo -1 转帖请附带源地址..

查看全部评分

回复 论坛版权

使用道具 举报

原始 发表于 2014-7-26 14:19:21 | 显示全部楼层
好高端的样子:)
cskin 发表于 2014-7-26 15:39:07 | 显示全部楼层
支持楼猪~



╭︿︿︿╮
{/ o  o /}  
( (oo) )   
  ︶ ︶︶
iHomeSoft 发表于 2014-7-27 11:04:51 | 显示全部楼层
哎,看不懂!
回复

使用道具 举报

土豆哥哥 发表于 2014-11-1 02:14:00 | 显示全部楼层
学习了,最后一个测试的我认识,哈哈
bbkidd 发表于 2017-5-31 09:39:27 | 显示全部楼层
很好的学习素材
WLKK 发表于 2017-9-29 16:54:25 | 显示全部楼层
本帖最后由 WLKK 于 2017-9-29 16:55 编辑

本文示例源码…怎么改使用GDI+图片像素输入Excel 单元格…???
QW去 发表于 2018-1-12 09:10:07 | 显示全部楼层
感谢楼主分享
l375527548 发表于 2019-3-12 22:03:12 | 显示全部楼层
说出了也没人相信,我来Cskin第一个下载的竟然是读像素
gayhub 发表于 2019-5-6 12:24:01 | 显示全部楼层
我需要测试下
您需要登录后才可以回帖 登录 | 加入CSkin论坛

本版积分规则

QQ|申请友链|小黑屋|手机版|Archiver|CSkin ( 粤ICP备13070794号

Powered by Discuz! X3.2  © 2001-2013 Comsenz Inc.  Designed by ARTERY.cn
GMT+8, 2019-7-22 01:08, Processed in 0.794991 second(s), 38 queries , Gzip On.

快速回复 返回顶部 返回列表