C#图片处理常见方法性能比较
2017-05-09 12:55
393 查看
在.NET编程中,由于GDI+的出现,使得对于图像的处理功能大大增强。在文通过一个简单黑白处理实例介绍在.NET中常见的图片处理方法和原理并比较各种方法的性能。
黑白处理原理:彩色图像处理成黑白效果通常有3种算法;
(1).最大值法:使每个像素点的R,G,B值等于原像素点的RGB(颜色值)中最大的一个;
(2).平均值法:使用每个像素点的R,G,B值等于原像素点的RGB值的平均值;
(3).加权平均值法:对每个像素点的R,G,B值进行加权
自认为第三种方法做出来的黑白效果图像最"真实".
这里提一下,在循环次数控制时尽量不要用i<curBitmap.Width做循环条件,而是应当将其取出保存到一个变量中,这样循环时不用每次从curBitmp中取Width属性,从而提高性能。
尽管如此,直接提取像素法对大像素图片处理力不从心,处理一张1440*900的图片耗时2182ms.本人配置单:
处理之前截图:
处理后:
可以直观地看出用时间2056ms.多次测试有少许波动。
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
3.指针法
指针在c#中属于unsafe操作,需要用unsafe括起来进行处理,速度最快,处理一副180*180的图像大约需要18ms。
采用byte*ptr=(byte*)(bmpData.Scan0);获取图像数据根位置的指针,然后用bmpData.Scan0获取图像的扫描宽度,就可以进行指针操作了。
黑白处理原理:彩色图像处理成黑白效果通常有3种
(1).最大值法:使每个像素点的R,G,B值等于原像素点的RGB(颜色值)中最大的一个;
(2).平均值法:使用每个像素点的R,G,B值等于原像素点的RGB值的平均值;
(3).加权平均值法:对每个像素点的R,G,B值进行加权
自认为第三种方法做出来的黑白效果图像最"真实".
1.GetPixel方法
GetPixel(i,j)和SetPixel(i,j,Color)可以直接得到图像的一个像素的Color结构,但是处理速度比较慢.///<summary> ///像素法 ///</summary> ///<paramname="curBitmap"></param> privatevoidPixelFun(BitmapcurBitmap) { intwidth=curBitmap.Width; intheight=curBitmap.Height; for(inti=0;i<width;i++)//这里如果用i<curBitmap.Width做循环对性能有影响 { for(intj=0;j<height;j++) { ColorcurColor=curBitmap.GetPixel(i,j); intret=(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.本人配置单:
处理之前截图:
处理后:
可以直观地看出用时间2056ms.多次
2.内存拷贝法
内存拷贝法就是采用System.Runtime.InteropServices.Marshal.Copy将图像数据拷贝到数组中,然后进行处理,这不需要直接对指针进行操作,不需采用unsafe,处理速度和指针处理相差不大,处理一副1440*900的图像大约需要34ms。
内存拷贝发和指针法都需用到的一个类:BitmapData
BitmapData类
BitmapData对象指定了位图的属性
1.Height属性:被锁定位图的高度.
2.Width属性:被锁定位图的高度.
3.PixelFormat属性:数据的实际像素格式.
4.Scan0属性:被锁定数组的首字节地址,如果整个图像被锁定,则是图像的第一个字节地址.
5.Stride属性:步幅,也称为扫描宽度.
如上图所示,数组的长度并不一定等于图像像素数组的长度,还有一部分未用区域,这涉及到位图的
假设有一张图片宽度为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> ///<paramname="curBitmap"></param> privateunsafevoidMemoryCopy(BitmapcurBitmap) { intwidth=curBitmap.Width; intheight=curBitmap.Height; Rectanglerect=newRectangle(0,0,curBitmap.Width,curBitmap.Height); System.Drawing.Imaging.BitmapDatabmpData=curBitmap.LockBits(rect,System.Drawing.Imaging.ImageLockMode.ReadWrite,PixelFormat.Format24bppRgb);//curBitmap.PixelFormat IntPtrptr=bmpData.Scan0; intbytesCount=bmpData.Stride*bmpData.Height; byte[]arrDst=newbyte[bytesCount]; Marshal.Copy(ptr,arrDst,0,bytesCount); for(inti=0;i<bytesCount;i+=3) { bytecolorTemp=(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> ///<paramname="curBitmap"></param> privateunsafevoidPointerFun(BitmapcurBitmap) { intwidth=curBitmap.Width; intheight=curBitmap.Height; Rectanglerect=newRectangle(0,0,curBitmap.Width,curBitmap.Height); System.Drawing.Imaging.BitmapDatabmpData=curBitmap.LockBits(rect,System.Drawing.Imaging.ImageLockMode.ReadWrite,PixelFormat.Format24bppRgb);//curBitmap.PixelFormat bytetemp=0; intw=bmpData.Width; inth=bmpData.Height; byte*ptr=(byte*)(bmpData.Scan0); for(inti=0;i<h;i++) { for(intj=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),为安全代码;内存拷贝法和指针法如果不改变原图片像素格式要针对不同的像素格式做不同的处理,且为不安全代码。
相关文章推荐
- C#图片处理常见方法性能比较
- C#图片处理常见方法性能比较
- C#图片处理常见方法性能比较
- C#图片处理常见方法性能比较
- 判断一个字符串是否全是数字的多种方法及其性能比较(C#实现)
- C# 三种图像处理方法 耗时比较
- C# 后台处理图片的几种方法
- C#笔记5——C#常见字符串处理方法
- 判断一个字符串是否全是数字的多种方法及其性能比较(C#实现)
- C#字符串数组排序 C#排序算法大全 C#字符串比较方法 一个.NET通用JSON解析/构建类的实现(c#) C#处理Json文件 asp.net使用Jquery+iframe传值问题
- C#中比较常见的类&方法、对应的引用、例子
- android中常见图片处理方法
- c#.net常见字符串处理方法
- C#调用脚本语言(三)-- IronJS 与 IronLua 简单方法性能比较
- Android性能优化——常见的内存泄漏及处理方法
- C#清除数组中数据的几种方法及性能比较
- ArcEngine,C#删除数据几种方法和性能比较
- C#调用脚本语言(三)-- IronJS 与 IronLua 简单方法性能比较
- C#调用脚本语言(三)-- IronJS 与 IronLua 简单方法性能比较
- 判断一个字符串是否全是数字的多种方法及其性能比较(C#实现)--来源CSDN