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

来自:http://www.cnblogs.com/sndnnlfhvk/archive/2012/02/27/2370643.html

 

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

黑白处理原理:彩色图像处理成黑白效果通常有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>         /// <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.本人配置单:

处理之前截图:

处理后:

可以直观地看出用时间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 XX BGR BGR BGR BGR BGR BGR XX BGR 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),为安全代码;内存拷贝法和指针法如果不改变原图片像素格式要针对不同的像素格式做不同的处理,且为不安全代码。

参考资料:http://www.cnblogs.com/Juny/archive/2008/04/08/1143001.html

另外,我正在参考各种资料,不断完善各种图形处理方法和性能,写成内库。

C#图片处理常见方法性能比较的更多相关文章

  1. WEB前端性能优化常见方法

    1.https://segmentfault.com/a/1190000008829958 (WEB前端性能优化常见方法) 2..https://blog.csdn.net/mahoking/arti ...

  2. openlayers3 基础(常见方法,类及实现)

    ol3接口大全1.ol.Map类:(地图容器类) 实现: ol.Map(参数) 参数说明:1.1 target,说明地图所在的html元素. 如果没有指定,必须调用ol.Map类的setTarget( ...

  3. JS数组去重的几种常见方法

    JS数组去重的几种常见方法 一.简单的去重方法 // 最简单数组去重法 /* * 新建一新数组,遍历传入数组,值不在新数组就push进该新数组中 * IE8以下不支持数组的indexOf方法 * */ ...

  4. Java实现HTML转换为PDF的常见方法

    最近在自己的项目中需要动态生成融资单合同,这里需要把对应的html转换为对应的pdf融资合同.因此需要通过Java实现将HTML转PDF.自己之前没有接触过这一块的东西,所以上网查了一下,网上有很多的 ...

  5. Python爬虫突破封禁的6种常见方法

    转 Python爬虫突破封禁的6种常见方法 2016年08月17日 22:36:59 阅读数:37936 在互联网上进行自动数据采集(抓取)这件事和互联网存在的时间差不多一样长.今天大众好像更倾向于用 ...

  6. JS去重的几种常见方法

    JS数组去重的几种常见方法 一.简单的去重方法 // 最简单数组去重法 /* * 新建一新数组,遍历传入数组,值不在新数组就push进该新数组中 * IE8以下不支持数组的indexOf方法 * */ ...

  7. Java日期时间API系列30-----Jdk8中java.time包中的新的日期时间API类,减少时间精度方法性能比较和使用。

    实际使用中,经常需要使用不同精确度的Date,比如保留到天 2020-04-23 00:00:00,保留到小时,保留到分钟,保留到秒等,常见的方法是通过格式化到指定精确度(比如:yyyy-MM-dd) ...

  8. 手把手教你定位常见Java性能问题

    概述 性能优化一向是后端服务优化的重点,但是线上性能故障问题不是经常出现,或者受限于业务产品,根本就没办法出现性能问题,包括笔者自己遇到的性能问题也不多,所以为了提前储备知识,当出现问题的时候不会手忙 ...

  9. 【性能测试】常见的性能问题分析思路(二)案例&技巧

    上一篇介绍了性能问题分析的诊断的基本过程,还没看过的可以先看下[性能测试]常见的性能问题分析思路-道与术,精炼总结下来就是,当遇到性能问题的时候,首先分析现场,然后根据现象去查找对应的可能原因,在通过 ...

随机推荐

  1. exe4j的使用

    下载:http://download.cnet.com/exe4j/3000-2070_4-144405.html 参考:http://blog.chinaunix.net/uid-25749806- ...

  2. Android探索之HttpURLConnection网络请求

    前言: 最近一直想着学习一下比较好的开源网络框架okhttp,想着学习之前还是先总结一下Android原生提供的网络请求.之前一直在使用HttpClient,但是android 6.0(api 23) ...

  3. 应用程序框架实战三十六:CRUD实战演练介绍

    从本篇开始,本系列将进入实战演练阶段. 前面主要介绍了一些应用程序框架的概念和基类,本来想把所有概念介绍完,再把框架内部实现都讲完了,再进入实战,这样可以让初学者基础牢靠.不过我的精力很有限,文章进度 ...

  4. GROUP函数-GROUP_ID,GROUPING,GROUPING_ID

    GROUP_ID 首先我们看看官方的解释: 大意是GROUP_ID用于区分相同分组标准的分组统计结果. 解释起来比较抽象,下面我们来看看具体的案例. 例1:单一分组 SQL> select gr ...

  5. 塞翁失马,焉知非福:由 Styles.Render 所引发 runAllManagedModulesForAllRequests="true" 的思考

    最近在使用 MVC 开发的时候,遇到一个对我来说"奇怪的问题",就是使用 BundleTable 进行 CSS.JS 文件绑定,然后使用 Styles.Render.Scripts ...

  6. .Net处理Oracle中Clob类型字段总结

    最近在做项目中用到Clob这个字段,Clob是存储无限长字符的Oracle字段,用的时候网上找资料找了好久,内容不是很多,大部分都不能用,当然也有可以用的,测试了不同版本,整理了一下,给大家在做项目的 ...

  7. 安装nginx

    [yum安装nginx] yum clean all(这步不执行会出现no more mirrors to try错误) cd /etc/yum.repos.d/ vi nginx.repo 填写 [ ...

  8. Callbacks vs Events

    前言:本文翻译自Dean Edwards的一篇文章,原文地址:http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/. 文章主要指出了 ...

  9. 原创:经验分享:微信小程序外包接单常见问题及流程

    从九月底内测到现在已经三个半月.凌晨一点睡觉已经习以为常,也正是这样,才让无前端经验的我做微信小程序开发并不感到费劲.最近才开始接微信小程序的外包项目,目前已经签下了五份合同,成品出了两个.加上转给朋 ...

  10. Win8.1安装mysql-installer-community-5.6.21.0.mis

    引言 我本机是win8.1,在官网下载的这个安装是32位和64位都能装,安装步骤参考:http://jingyan.baidu.com/article/67662997305dcd54d51b84d4 ...