WPF彩色图像转灰度图像

一.基本概念

1.字节,像素,位

这三个概念是非常容易混淆的,必须能够深刻理解这三个概念
首先在内存上:

像素≥字节>位

字节由位组成,一般一个字节是八位
每一位的数只有两种表达方式 0或1
所以每一个字节的可能性有 2^8种,即256
字节的取值为0-255
而像素则是根据不同的像素格式,占有的字节数也不同,
如RGB32格式就是每个像素占4个字节

2.图像格式(piexelFormat)

常见的有RBG32,RGB555等
像素格式表示图像每个像素的数值分布情况。
主要有 R G B 红绿蓝三个颜色通道
在排布的时候是按照BGR的顺序进行排列

如RGB32像素格式:
BGRA BGRA BGRA
A该字节不表示,所以 BGR每个颜色通道各占8个字节,一共有256中可能。

二.算法简介

1.格式转换法

直接利用现有函数对图像的格式进行转换就可以实现彩色转灰色

FormatConvertedBitmap myFormatedConvertedBitmap = new FormatConvertedBitmap();
            myFormatedConvertedBitmap.BeginInit();
            myFormatedConvertedBitmap.Source = bi;
            myFormatedConvertedBitmap.DestinationFormat = PixelFormats.Gray8;
            myFormatedConvertedBitmap.EndInit();

            GreyImageCtr.Source = myFormatedConvertedBitmap;
            TabControl1.SelectedIndex = 1;

FormatConvertedBitmap类是BitmapSource下的子类
图像读入位图中的类BitmapImage也是FormatConvertedBitmap的子类
利用上述方法就可以直接把彩色图像转化为灰色图像

2.通过指针操作的形式

在C#中一般不用指针操作。但是特殊情况需要使用的时候需要用到unsafe
在用unsafe的时候需要先在项目里面设置解决方案的属性,允许使用不安全代码

以下代码看不懂可以别看

protected override void DecodeImage(WriteableBitmap source)
        {
            try
            {
                source.Lock();
                unsafe
                {
                    byte* pBackBuffer = (byte*)source.BackBuffer;
                    int currentPixelIndex = 0; //当前解码像素索引 
                    byte[] r = R;
                    byte[] g = G;
                    byte[] b = B;
                    int tailAddressOffset = GetRowTailAddressOffset();
                    for (int row = 0; row < Rows; row++)
                    {
                        for (int column = 0; column < Columns; column++)
                        {
                            b[currentPixelIndex] = *pBackBuffer++;
                            g[currentPixelIndex] = *pBackBuffer++;
                            r[currentPixelIndex++] = *pBackBuffer++;

                        }
                        /*for (int offset = 0; offset < tailAddressOffset; offset++) pBackBuffer++;*/
                        pBackBuffer = pBackBuffer + tailAddressOffset;
                    }                    
                }
            }
            finally
            {
                source.Unlock();
            }
        }


protected WriteableBitmap Color2Gray(byte[] r, byte[] g, byte[] b, ResultDataTypeEnum resultType)
        {
            WriteableBitmap wb = null;
            switch(resultType)
            {
                case ResultDataTypeEnum.Byte:
                case ResultDataTypeEnum.Default:
                    {
                        byte[] gray = new byte[r.Length];
                        for(int i=0;i<gray.Length;i++)
                        {
                            gray[i] = (byte)RGB2Gray64(r[i], g[i], b[i],0);                            
                        }
                        wb = CreateWB(ImageEncoder.Gray8.Encode(gray), PixelFormats.Gray8);
                        break;
                    }
                    
                case ResultDataTypeEnum.Short:
                    {
                        short[] gray = new short[r.Length];
                        for (int i = 0; i < gray.Length; i++)
                        {
                            gray[i] = (short)RGB2Gray64(r[i], g[i], b[i],8);
                        }
                        wb = CreateWB(ImageEncoder.Gray16.Encode(gray), PixelFormats.Gray16);
                        break;
                    }
                case ResultDataTypeEnum.Int:
                    {
                        int[] gray = new int[r.Length];
                        for (int i = 0; i < gray.Length; i++)
                        {
                            gray[i] = (int)RGB2Gray64(r[i], g[i], b[i],24);
                        }
                        wb = CreateWB(ImageEncoder.Gray32.Encode(gray), PixelFormats.Gray32Float);
                        break;
                    }
            }
            return wb;
        }               

private long RGB2Gray64(long r, long g, long b, int scale)
        {   //gray = r*0.299+g*0.587+b*0.114 系数扩大了16384倍,即左移的14位 
            return ((r << scale) * 4899 + (g << scale) * 9617 + (b << scale) * 1868) >> 14;
        }


protected WriteableBitmap CreateWB(byte[] imageData, PixelFormat pf)
        {
            WriteableBitmap wb = new WriteableBitmap(Columns, Rows, DPIX, DPIY, pf, null);
            Int32Rect rect = new Int32Rect(0, 0, wb.PixelWidth, wb.PixelHeight);
            int stride = wb.PixelWidth * pf.BitsPerPixel / 8;
            wb.WritePixels(rect, imageData, stride, 0);
            return wb;
        }

3.通过提取数组进行操作

在上述我们知道,RGB32格式的像素中数据的排列方式是
BGRA BGRA BGRA ……
要转换为灰度图像,我们需要分别得到B,G,R三个数组,然后利用公式
计算得到一个新的灰色图像数值的数组
计算公式为
r0.299+g0.587+b*0.114

算法的思维导图如下:

在这里插入图片描述

三.效果演示

1.界面

在这里插入图片描述
在这里插入图片描述

2.细节

点击灰色处理会自动跳到灰色图像的TabControl窗口
方法

TabControl1.SelectedIndex = 1

三.关键代码(数组处理)

using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace 数字图像处理1
{
    public class ImageHandler
    {
        public int Rows;
        public int Columns;
        public PixelFormat f;
        public byte[] R;
        public byte[] G;
        public byte[] B;
        public ImageHandler(BitmapImage image)
        {
            Rows = image.PixelWidth;
            Columns = image.PixelHeight;
            f = image.Format;
            int s = Columns * f.BitsPerPixel/8;
            R = new byte[Rows * s/4];
            G = new byte[Rows * s/4];
            B = new byte[Rows * s/4];
        }
        /// <summary>
        /// 获取像素的数组
        /// </summary>
        /// <param name="source"></param>
        public void GetPixelArray(byte[] g)
        {
            int i = 0;
            int bi = 0;
            int gi = 0;
            int ri = 0;
            while (i<g.Length)
            {
                switch (i%4)
                {
                    case 0:
                        B[bi] = g[i];
                        bi++;
                        break;
                    case 1:
                        G[gi] = g[i];
                        gi++;
                        break;
                    case 2:
                        R[ri] = g[i];
                        ri++;
                        break;
                    default:
                        break;
                }
                i++;
            }
        }

        /// <summary>
        /// 彩色转灰色具体的实现函数
        /// </summary>
        /// <param name="source">BitmapImage</param>
        /// <returns></returns>
        public BitmapSource Color2Gray(BitmapImage ima)
        {
            WriteableBitmap source = new WriteableBitmap(ima.PixelWidth,
                                                         ima.PixelHeight,
                                                         ima.DpiX,
                                                         ima.DpiY,
                                                         ima.Format,
                                                         ima.Palette);
            int _pixelWidth = source.PixelWidth;
            int _pixelheight = source.PixelHeight ;
            double _dpiX = source.DpiX;
            double _dpiY = source.DpiY;
            BitmapPalette _p = source.Palette;
            int _stride = source.PixelWidth * source.Format.BitsPerPixel/8;
            //测试
            byte[] g = new byte[ima.PixelHeight * _stride];
            ima.CopyPixels(g, _stride, 0);
            GetPixelArray(g);
            byte[] gray = GetGrayArray();
            return BitmapSource.Create(_pixelWidth,
                                        _pixelheight,
                                        _dpiX,
                                        _dpiY,
                                        PixelFormats.Gray8,
                                        _p,
                                        gray,
                                        _stride/4);
            
        }
        /// <summary>
        /// 获取灰色数组
        /// </summary>
        /// <returns></returns>
        public byte[] GetGrayArray()
        {
            byte[] gray = new byte[B.Length];
            for (int i = 0; i < B.Length; i++)
            {
                gray[i] = (byte)Rgb2Gray(R[i], G[i], B[i]);
            }
            return gray;
        }
        /// <summary>
        /// 灰色转换的具体公式
        /// </summary>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public double Rgb2Gray(long r, long g, long b)
        {
            return (r*0.299+g*0.587+b*0.114); //只针对正常情况
        }

    }
}

GitHub代码

https://github.com/Mushano/WPF-ColoerImageToGray