• 彩色转灰度算法


    一、基础

      对于彩色转灰度。有一个非常著名的心理学公式:

    Gray = R*0.299 + G*0.587 + B*0.114

    二、整数算法

      而实际应用时,希望避免低速的浮点运算,所以须要整数算法。

      注意到系数都是3位精度的没有。我们能够将它们缩放1000倍来实现整数运算算法:

    Gray = (R*299 + G*587 + B*114 + 500) / 1000

      RGB通常是8位精度。如今缩放1000倍。所以上面的运算是32位整型的运算。

    注意后面那个除法是整数除法,所以须要加上500来实现四舍五入。

      就是因为该算法须要32位运算,所以该公式的还有一个变种非常流行:

    Gray = (R*30 + G*59 + B*11 + 50) / 100

      可是,虽说上一个公式是32位整数运算,可是依据80x86体系的整数乘除指令的特点。是能够用16位整数乘除指令来运算的。并且如今32位早普及了(AMD64都出来了),所以推荐使用上一个公式。

    三、整数移位算法

      上面的整数算法已经非常快了,可是有一点仍制约速度。就是最后的那个除法。

    移位比除法快多了,所以能够将系数缩放成 2的整数幂。

      习惯上使用16位精度,2的16次幂是65536。所以这样计算系数:

    0.299 * 65536 = 19595.264 ≈ 19595

    0.587 * 65536 + (0.264) = 38469.632 + 0.264 = 38469.896 ≈ 38469

    0.114 * 65536 + (0.896) = 7471.104 + 0.896 = 7472

      可能非常多人看见了,我所使用的舍入方式不是四舍五入。四舍五入会有较大的误差,应该将曾经的计算结果的误差一起计算进去。舍入方式是去尾法:

      写成表达式是:

    Gray = (R*19595 + G*38469 + B*7472) >> 16

      2至20位精度的系数:

    Gray = (R*1 + G*2 + B*1) >> 2

    Gray = (R*2 + G*5 + B*1) >> 3

    Gray = (R*4 + G*10 + B*2) >> 4

    Gray = (R*9 + G*19 + B*4) >> 5

    Gray = (R*19 + G*37 + B*8) >> 6

    Gray = (R*38 + G*75 + B*15) >> 7

    Gray = (R*76 + G*150 + B*30) >> 8

    Gray = (R*153 + G*300 + B*59) >> 9

    Gray = (R*306 + G*601 + B*117) >> 10

    Gray = (R*612 + G*1202 + B*234) >> 11

    Gray = (R*1224 + G*2405 + B*467) >> 12

    Gray = (R*2449 + G*4809 + B*934) >> 13

    Gray = (R*4898 + G*9618 + B*1868) >> 14

    Gray = (R*9797 + G*19235 + B*3736) >> 15

    Gray = (R*19595 + G*38469 + B*7472) >> 16

    Gray = (R*39190 + G*76939 + B*14943) >> 17

    Gray = (R*78381 + G*153878 + B*29885) >> 18

    Gray = (R*156762 + G*307757 + B*59769) >> 19

    Gray = (R*313524 + G*615514 + B*119538) >> 20

      细致观察上面的表格,这些精度实际上是一样的:3与4、7与8、10与11、13与14、19与20

      所以16位运算下最好的计算公式是使用7位精度,比先前那个系数缩放100倍的精度高,并且速度快:

    Gray = (R*38 + G*75 + B*15) >> 7

      事实上最有意思的还是那个2位精度的,全然能够移位优化:

    Gray = (R + (WORD)G<<1 + B) >> 2

      因为误差非常大,所以做图像处理绝不用该公式(最经常使用的是16位精度)。但对于游戏编程,场景经常变化,用户一般不可能观察到颜色的细微区别。所以最经常使用的是2位精度。 


    c#代码

            /// <summary>

            /// 彩色图片转换成灰度图片代码

            /// </summary>

            /// <param name="img">源图片</param>

            /// <returns></returns>

            public Bitmap BitmapConvetGray(Bitmap img)

            {

                int h = img.Height;

                int w = img.Width;

                int gray = 0;    //灰度值

                Bitmap bmpOut = new Bitmap(w, h, PixelFormat. Format24bppRgb);    //每像素3字节

                BitmapData dataIn = img.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);

                BitmapData dataOut = bmpOut.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

                unsafe

                {

                    byte* pIn = (byte*)(dataIn.Scan0.ToPointer());      //指向源文件首地址

                    byte* pOut = (byte*)(dataOut.Scan0.ToPointer());  //指向目标文件首地址

                    for (int y = 0; y < dataIn.Height; y++)  //列扫描

                    {

                        for (int x = 0; x < dataIn.Width; x++)   //行扫描

                        {

                            gray = (pIn[0] * 19595 + pIn[1] * 38469 + pIn[2] * 7472) >> 16;  //灰度计算公式

                            pOut[0] = (byte)gray;     //R分量

                            pOut[1] = (byte)gray;     //G分量

                            pOut[2] = (byte)gray;     //B分量

                            pIn += 3; pOut += 3;      //指针后移3个分量位置

                        }

                        pIn += dataIn.Stride - dataIn.Width * 3;

                        pOut += dataOut.Stride - dataOut.Width * 3;

                    }

                }

                bmpOut.UnlockBits(dataOut);

                img.UnlockBits(dataIn);

                return bmpOut;

            }


    注:

    理解Stride:如果有一张图片宽度为6,因为是Format24bppRgb格式(每像素3字节。否则Bitmap默认24位RGB)的,显然,每一行须要6*3=18个字节存储。对于Bitmap就是如此。

    但对于C# BitmapData。尽管BitmapData.Width还是等于Bitmap.Width,但大概是出于显示性能的考虑,每行的实际的字节数将变成大于等于它的那个离它近期的4的整倍数,此时的实际字节数就是Stride.就此例而言,18不是4的整倍数,而比18大的离18近期的4的倍数是20。所以这个BitmapData.Stride = 20.显然。当宽度本身就是4的倍数时。BitmapData.Stride = Bitmap.Width * 3.画个图可能更好理解。

    R、G、B 分别代表3个原色分量字节。BGR就表示一个像素。为了看起来方便在每一个像素之间插了个空格,实际上是没有的。

    X表示补足4的倍数而自己主动插入的字节。

    为了符合人类的阅读习惯分行了。事实上在计算机内存中应该看成连续的一大段。

    该代码在VS2008中编译通过。当使用unsafekeyword时。项目的属性-->生成-->勾选"同意使用不安全代码"

     

     

    delphi7代码 

    procedure Convert2Gray(Cnv: TCanvas);
    var X, Y, jj: Integer;
      Color: LongInt;
      R, G, B, Gr: Byte;
    begin
      with Cnv do
        for X := Cliprect.Left to Cliprect.Right do
          for Y := Cliprect.Top to Cliprect.Bottom do
          begin
            Color := ColorToRGB(Pixels[X, Y]);
            B := (Color and $FF0000) shr 16;
            G := (Color and $FF00) shr 8;
            R := (Color and $FF);
            Gr := HiByte(R * 77 + G * 151 + B * 28);
            jj := gr;
            Gr := Trunc(B * 0.11 + G * 0.59 + R * 0.3);
            Pixels[X, Y] := RGB(Gr, Gr, Gr);
          end;

    end;

    function RGB(R, G, B: Byte): TColor;
    begin
      Result := B shl 16 or G shl 8 or R;
    end;

    procedure TfrmDemo.Button1Click(Sender: TObject);
    begin
      Screen.Cursor := crHourGlass;
      Convert2Gray(Image1.Picture.Bitmap.Canvas);
      Screen.Cursor := crDefault;
    end;

  • 相关阅读:
    为什么 PCB 生产时推荐出 Gerber 给工厂?
    Fedora Redhat Centos 有什么区别和关系?
    【KiCad】 如何给元件给元件的管脚加上划线?
    MCU ADC 进入 PD 模式后出现错误的值?
    FastAdmin 生产环境升级注意
    EMC EMI 自行评估记录
    如何让你的 KiCad 在缩放时不眩晕?
    KiCad 5.1.0 正式版终于发布
    一次单片机 SFR 页引发的“事故”
    java基础之集合
  • 原文地址:https://www.cnblogs.com/wzzkaifa/p/7216011.html
Copyright © 2020-2023  润新知