C#编程,图像转换灰度图的几种方法

为加快处理速度,在图像处理算法中,往往需要把彩色图像抓换成灰色图像,24位彩色图像每个像素用3个字节表示,每个字节对应着R、G、B分量的亮度(红、绿、蓝)。当R、G、B分量值不同是,表示为彩色图像;当R、G、B分量值相同时,表现为灰度图像,也就是求这个值。

黑白处理原理:彩色图像处理成黑白效果通常有3种算法;
(1).最大值法:使每个像素点的R,G,B值等于原像素点的RGB(颜色值)中最大的一个;
(2).平均值法:使用每个像素点的R,G,B值等于原像素点的RGB值的平均值;
(3).加权平均值法:对每个像素点的R,G,B值进行加权;

公式

一般来说,转换工式有3个。

  • 第一种转换公式为:
    在这里插入图片描述
    其中,Gray(i,j)为转换后的灰度图像在(i,j)点处的灰度值。该方面虽然简单,但人眼对颜色的感应是不同的。
  • 第二种转换公式:
    在这里插入图片描述
    观察上面的公司,发现绿色所占的比重最大,所以转换时可以直接使用G 值作为转换后的灰度:

Bitmap类介绍

Bitmap对象封装了GDI+中的一个位图,该位图由图形图像及其属性的像素数据组成。因此Bitmap是用于处理由像素数据定义的图像的对象。该类的主要方法和属性如下:

GetPixel方法和 SetPixel方法: 获取和设置一个图像的指定像素的颜色。
PixelFormat : 返回图像的像素格式
Palette : 获取或设置图像所使用的颜色调色板
Height 、Width : 返回图像的高度和宽度
LockBits 、UnlockBits : 分别锁定和解锁系统内存中的位图像素。

在基于像素点的图像处理方法中使用LockBits 和 UnlockBits是一个很好的方式,这两种方法可以使我们通过指定像素的范围来控制位图的任意一部分,从而消除了通过循环对位图的像素逐个进行处理的需要。每次调用LockBits之后都应该调用一次UnlockBits。

BitmapData类介绍

BitmapData对象指定了位图的属性:
Height属性,被锁定位图的高度。
Width属性,被锁定位图的宽度。
PixelFormat属性,数据的实际像素格式。
Scan0属性,被锁定数组的首字节地址。
Stride属性,步幅,也称扫描宽度。
在这里插入图片描述
如上图所示,数组的长度并不一定等于图像像素数组的长度,还有一部分未用区域,这涉及到位图的数据结构,系统要保证每行的字节数必须为4的倍数。

假设有一张图片宽度为6,因为是Format24bppRgb格式(每像素3字节。在以下的讨论中,除非特别说明,否则Bitmap都被认为是24位RGB)的,显然,每一行需要63=18个字节存储。
对于Bitmap就是如此。但对于BitmapData,虽然BitmapData.Width还是等于Bitmap.Width,但大概是出于显示性能的考虑,每行的实际的字节数将变成大于等于它的那个离它最近的4的整倍数,此时的实际字节数就是Stride。
就此例而言,18不是4的整倍数,而比18大的离18最近的4的倍数是20,所以这个BitmapData.Stride=20。
显然,当宽度本身就是4的倍数时,BitmapData.Stride=Bitmap.Width
3。

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

图像处理的3种方法

提取像素法

该方法使用的是GDI+中的 Bitmap.GetPixel和 Bitmap.SetPixel方法。为了将位图的颜色设置为灰度或其他颜色,就需要使用GetPixel来读取当前像素的颜色,再计算灰度值,最后使用SetPixel来应用新的颜色。代码如下:

            //加载图像
            var curBitmap = (Bitmap)Image.FromFile(filePath);
            Color curColor;
            int ret;
            //循环读取像素转换灰度值
            for (int i = 0; i < curBitmap.Width; i++)
            {
                for (int j = 0; j < curBitmap.Height ; j++)
                {
                    curColor = curBitmap.GetPixel(i,j);
                    ret = (int)(curColor.R * 0.299 + curColor.G * 0.587 + curColor.B * 0.114);
                    curBitmap.SetPixel(i, j, Color.FromArgb(ret, ret, ret));
                }
            }

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

内存法

该方法就是把图像数据直接复制到内存中,这样就使程序的运行速度大大提高。 代码如下:

   //加载图像
                var curBitmap = (Bitmap)Image.FromFile(filePath);
                Rectangle rect = new Rectangle(0, 0, curBitmap.Width, curBitmap.Height);
                System.Drawing.Imaging.BitmapData bmpData = curBitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, curBitmap.PixelFormat);
                IntPtr ptr = bmpData.Scan0;
                int bytes = curBitmap.Width * curBitmap.Height * 3;
                byte[] rgbValues = new byte[bytes];
                System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
                double colorTemp = 0;
                for (int i = 0; i < rgbValues.Length; i += 3)
                {
                    colorTemp = rgbValues[i + 2] * 0.299 + rgbValues[i + 1] * 0.587 + rgbValues[i] * 0.114;
                    rgbValues[i] = rgbValues[i + 1] = rgbValues[i + 2] = (byte)colorTemp;
                }
                System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
                curBitmap.UnlockBits(bmpData);

指针法

该方法与内存法相似,开始都是通过LockBits方法来获取位图的首地址。但该方法更简洁,直接应用指针对位图进行操作。
为了保持类型安全,在默认情况下,C#是不支持指针运算的,因为使用指针会带来相关的风险。所以C#只允许在特别标记的代码块中使用指针。通过使用unsafe关键字,可以定义可使用指针的不安全上下文。

                //加载图像
                var curBitmap = (Bitmap)Image.FromFile(filePath);
                Rectangle rect = new Rectangle(0, 0, curBitmap.Width, curBitmap.Height);
                System.Drawing.Imaging.BitmapData bmpData = curBitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, curBitmap.PixelFormat);
                byte temp = 0;
                unsafe
                {
                    byte* ptr = (byte*)(bmpData.Scan0);
                    for (int i = 0; i < bmpData.Height; i++)
                    {
                        for (int j = 0; j < bmpData.Width; j++)
                        {
                            temp = (byte)(0.299 * ptr[2] + 0.587 * ptr[1] + 0.114 * ptr[0]);
                            ptr[0] = ptr[1] = ptr[2] = temp;
                            ptr += 3;
                        }
                        ptr += bmpData.Stride - bmpData.Width * 3;
                    }
                }
                curBitmap.UnlockBits(bmpData);

效果预览

总结

内存法和指针法比提取像素法要快得多。提取像素法应用GDI+中的方法,易于理解,方法简单,很适合于C#的初学者使用,但它的运行速度最慢,效率最低。内存法把图像复制到内存中,直接对内存中的数据进行处理,速度明显提高,程序难度也不大。
指针法直接应用指针来对图像进行处理,所以速度最快。但在C#中,是不建议使用指针的,因为使用指针,代码不仅难以编写和调试,而且无法通过 CLR的内存类型安全检查,不能发挥C#的特长。只有对C#和指针有了充分的理解,才能用好该方法。
在这里插入图片描述

究竟要使用哪种方法,还要看具体情况而定。但3种方法都能有效地对图像进行处理。

参考链接:https://www.cnblogs.com/zh7791/p/16194609.html

参考链接:http://www.360doc.com/content/17/0414/20/39573434_645648120.shtml

  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值