C#图片处理常见方法性能比较

在.NET编程中,因为GDI+的出现,使得对于图像的处理功能大大加强。在文经过一个简单黑白处理实例介绍在.NET中常见的图片处理方法和原理并比较各类方法的性能。算法

 

黑白处理原理:彩色图像处理成黑白效果一般有3种算法编程

(1).最大值法: 使每一个像素点的 R, G, B 值等于原像素点的 RGB (颜色值) 中最大的一个;c#

(2).平均值法: 使用每一个像素点的 R,G,B值等于原像素点的RGB值的平均值;数组

(3).加权平均值法: 对每一个像素点的 R, G, B值进行加权安全

自认为第三种方法作出来的黑白效果图像最 "真实".数据结构

 

1.GetPixel方法

 GetPixel(i,j)和SetPixel(i, j,Color)能够直接获得图像的一个像素的Color结构,可是处理速度比较慢.ide

复制代码
复制代码
 /// <summary>
        /// 像素法
        /// </summary>
        /// <param name="curBitmap"></param>
        private void PixelFun(Bitmap curBitmap)
        {
            int width = curBitmap.Width;
            int height = curBitmap.Height;
            for (int i = 0; i <width; i++) //这里若是用i<curBitmap.Width作循环对性能有影响
            {
                for (int j = 0; j < height; j++)
                {
                   Color  curColor = curBitmap.GetPixel(i, j);
                   int ret = (int)(curColor.R * 0.299 + curColor.G * 0.587 + curColor.B * 0.114);
                    curBitmap.SetPixel(i, j, Color.FromArgb(ret, ret, ret));
                }
            }
 }
复制代码
复制代码

这里提一下,在循环次数控制时尽可能不要用i<curBitmap.Width作循环条件,而是应当将其取出保存到一个变量中,这样循环时不用每次从curBitmp中取Width属性,从而提升性能。性能

尽管如此,直接提取像素法对大像素图片处理力不从心,处理一张1440*900的图片耗时2182ms.本人配置单:测试

处理以前截图:spa


处理后:

 

能够直观地看出用时间2056ms.屡次测试有少量波动。

2.内存拷贝法

内存拷贝法就是采用System.Runtime.InteropServices.Marshal.Copy将图像数据拷贝到数组中,而后进行处理,这不须要直接对指针进行操做,不需采用unsafe,处理速度和指针处理相差不大,处理一副1440*900的图像大约须要34ms。

 
内存拷贝发和指针法都需用到的一个类:BitmapData
 

BitmapData类

 

BitmapData对象指定了位图的属性

 

1.       Height属性:被锁定位图的高度.

 

2.       Width属性:被锁定位图的高度.

 

3.       PixelFormat属性:数据的实际像素格式.

 

4.       Scan0属性:被锁定数组的首字节地址,若是整个图像被锁定,则是图像的第一个字节地址.

 

5.       Stride属性:步幅,也称为扫描宽度.

如上图所示,数组的长度并不必定等于图像像素数组的长度,还有一部分未用区域,这涉及到位图的数据结构,系统要保证每行的字节数必须为4的倍数.

假设有一张图片宽度为6,由于是Format24bppRgb格式(每像素3字节。在如下的讨论中,除非特别说明,不然Bitmap都被认为是24位RGB)的,显然,每一行须要6*3=18个字节存储。对于Bitmap就是如此。但对于BitmapData,虽然BitmapData.Width仍是等于Bitmap.Width,但大概是出于显示性能的考虑,每行的实际的字节数将变成大于等于它的那个离它最近的4的整倍数,此时的实际字节数就是Stride。就此例而言,18不是4的整倍数,而比18大的离18最近的4的倍数是20,因此这个BitmapData.Stride = 20。显然,当宽度自己就是4的倍数时,BitmapData.Stride = Bitmap.Width * 3。画个图可能更好理解(此图仅表明PixelFormat= PixelFormat. Format24bppRgb时适用,每一个像素占3个字节共24位)。R、G、B 分别表明3个原色份量字节,BGR就表示一个像素。为了看起来方便我在每一个像素之间插了个空格,其实是没有的。X表示补足4的倍数而自动插入的字节。为了符合人类的阅读习惯我分行了,其实在计算机内存中应该当作连续的一大段。Scan0||-------Stride-----------||-------Width---------|  |BGR BGR BGR BGR BGR BGR XXBGR BGR BGR BGR BGR BGR XXBGR BGR BGR BGR BGR BGR XX.则对于Format24bppRgb格式,知足:

BitmapData.Width*3 + 每行未使用空间(上图的XX)=BitmapData.Stride

 

同理,很容易推倒对于Format32bppRgb或Format32bppPArgb格式,知足:

BitmapData.Width*4 + 每行未使用空间(上图的XX)=BitmapData.Stride

复制代码
复制代码
  /// <summary>
        /// 内存拷贝法
        /// </summary>
        /// <param name="curBitmap"></param>
        private unsafe void MemoryCopy(Bitmap curBitmap)
        {
            int width = curBitmap.Width;
            int height = curBitmap.Height;

            Rectangle rect = new Rectangle(0, 0, curBitmap.Width, curBitmap.Height);
            System.Drawing.Imaging.BitmapData bmpData = curBitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,PixelFormat.Format24bppRgb);//curBitmap.PixelFormat

            IntPtr ptr = bmpData.Scan0;
            int bytesCount = bmpData.Stride * bmpData.Height;
            byte[] arrDst = new byte[bytesCount];
            Marshal.Copy(ptr, arrDst, 0, bytesCount);
         
            for (int i = 0; i < bytesCount; i+=3)
            {
                byte colorTemp = (byte)(arrDst[i + 2] * 0.299 + arrDst[i + 1] * 0.587 + arrDst[i] * 0.114);
                arrDst[i] = arrDst[i + 1] = arrDst[i + 2] = (byte)colorTemp;

            }
            Marshal.Copy(arrDst, 0, ptr, bytesCount);
            curBitmap.UnlockBits(bmpData);
        }
复制代码
复制代码

 3.指针法

指针在c#中属于unsafe操做,须要用unsafe括起来进行处理,速度最快,处理一副180*180的图像大约须要18ms。

 

采用byte* ptr = (byte*)(bmpData.Scan0); 获取图像数据根位置的指针,而后用bmpData.Scan0获取图像的扫描宽度,就能够进行指针操做了。

 

复制代码
复制代码
    /// <summary>
        /// 指针法
        /// </summary>
        /// <param name="curBitmap"></param>
        private unsafe void PointerFun(Bitmap curBitmap)
        {
            int width = curBitmap.Width; 
            int height = curBitmap.Height;

            Rectangle rect = new Rectangle(0, 0, curBitmap.Width, curBitmap.Height);
            System.Drawing.Imaging.BitmapData bmpData = curBitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,PixelFormat.Format24bppRgb );//curBitmap.PixelFormat
            byte temp = 0;
                int w = bmpData.Width;
                int h = bmpData.Height;
                byte* ptr = (byte*)(bmpData.Scan0);
                for (int i = 0; i < h; i++)
                {
                    for (int j = 0; j <w; j++)
                    {
                       temp = (byte)(0.299 * ptr[2] + 0.587 * ptr[1] + 0.114 * ptr[0]);
                        ptr[0] = ptr[1] = ptr[2] = temp;
                        ptr +=3; //Format24bppRgb格式每一个像素占3字节
                    }
                    ptr += bmpData.Stride - bmpData.Width * 3 ;//每行读取到最后“有用”数据时,跳过未使用空间XX
                }
            curBitmap.UnlockBits(bmpData);    
        }
复制代码
复制代码
如下是多组测试数据:
 
1920*1080
1440*900
1208*800
1024*768
500*544
200*169
直接提取像素法
1705ms
1051ms
1710ms
1340ms
450ms
32ms
内存拷贝法
54ms
33ms
26ms
20ms
7ms
0ms
指针法
28ms
17ms
14ms
10ms
3ms
0ms
 
因而可知,指针法与直接提取像素法效率竟隔两个数量级!

 

比较以上方法优缺点:

1.整体上性能 指针法略强于内存拷贝法,直接提取像素法性能最低;

2.对大图片处理指针法和内存拷贝法性能提高明显,对小图片都比较快;

3.直接提取像素法简单易用,并且没必要关注图片像素格式(PixelFormat),为安全代码;内存拷贝法和指针法若是不改变原图片像素格式要针对不一样的像素格式作不一样的处理,且为不安全代码。

相关文章
相关标签/搜索