基於Simple Image Statistics(簡單圖像統計,SIS)的圖像二值化算法。


  這是個簡單的算法,是全局二值算法的一種,算法執行速度快。

     算法過程簡單描述如下: 

  對於每一個像素,做如下處理 

       1、計算當前像素水平和垂直方向的梯度。 (two gradients are calculated  |I(x + 1, y) - I(x - 1, y)| and |I(x, y + 1) - I(x, y - 1)|);

       2、取兩個梯度的最大值作為權重。(weight is calculated as maximum of two gradients);

       3、更新權重的和。(sum of weights is updated (weightTotal += weight));

       4、更新加權像素之和 (sum of weighted pixel values is updated (total += weight * I(x, y)));

      之后,最終的閾值去加權像素之和和權重之和相除的值。

      這個算法在 Image Processing Lab in c# 的代碼中有相關的說明。 

      從實際的操作上講,我認為二值處理應該只針對灰度圖像進行處理,這樣才意義明確,因此,我在代碼中給出了判斷一副圖像是否是灰度圖像的一個函數:

  private bool IsGrayBitmap(Bitmap Bmp)
    {
        bool IsGray;
        if (Bmp.PixelFormat == PixelFormat.Format8bppIndexed)           // .net中灰度首先必然是索引圖像
        {
            IsGray = true;
            if (Bmp.Palette.Entries.Length != 256)                      // 這個要求其實在PS中是不存在的
                IsGray = false;
            else
            {
                for (int X = 0; X < Bmp.Palette.Entries.Length; X++)    // 看看調色板的每一個分兩值是不是都相等,且必須還要等於其在調色板中出現的順序
                {
                    if (Bmp.Palette.Entries[X].R != X || Bmp.Palette.Entries[X].G != X || Bmp.Palette.Entries[X].B != X)
                    {
                        IsGray = false;
                        break;
                    }
                }
            }
        }
        else
        {
            IsGray = false;
        }
        return IsGray;
    }

  實際上,在PS的概念中,灰度圖像的調色板個數不一定是256,只要調色板的每個元素的分量值都相等,並且都等於其在調色板中出現的順序,PS就認為他是灰度圖像。

     為了處理方便,我加入了一個將其他模式的圖像轉換為灰度模式圖像的函數:

    private Bitmap ConvertToGrayModeBitmap(Bitmap Bmp)
    {
        int X, Y, SrcStride, DestStride, Width, Height;
        byte* SrcData, DestData;
        BitmapData BmpData = Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
        Bitmap GrayBmp = new Bitmap(Bmp.Width, Bmp.Height, PixelFormat.Format8bppIndexed);
        ColorPalette Pal = GrayBmp.Palette;
        for (Y = 0; Y < Pal.Entries.Length; Y++) Pal.Entries[Y] = Color.FromArgb(255, Y, Y, Y);            // 設置灰度圖像的調色板
        GrayBmp.Palette = Pal;

        //  LockBits 在第一個參數和圖像一樣大,以及讀取格式和原始一樣的情況下,調用函數的時間為0,且每次調用后BitmapData的Scan0都相同,而在
        // 其他的大部分情況下同樣參數調用該函數返回的Scan0都不同,這就說明在在程序內部,GDI+為在創建圖像時還是分配了和對應位圖一樣大小內存空間,
        // 這樣我們就可以再加載時調用一次該函數,並記住Scan0的值,然后直接用指針操作這一片區域,就相當於操作了圖像。而不用每次都LOCK和UNLOCK了
        // 從這個層次上說,該函數和GetDibits類似。

        BitmapData GrayBmpData = GrayBmp.LockBits(new Rectangle(0, 0, GrayBmp.Width, GrayBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
        Width = BmpData.Width; Height = BmpData.Height;
        SrcStride = BmpData.Stride; DestStride = GrayBmpData.Stride;           //  這個值並不一定就等於width*height*色深/8
        for (Y = 0; Y < Height; Y++)
        {
            SrcData = (byte*)BmpData.Scan0 + Y * SrcStride;                  // 必須在某個地方開啟unsafe功能,其實C#中的unsafe很safe,搞的好嚇人。
            DestData = (byte*)GrayBmpData.Scan0 + Y * DestStride;
            for (X = 0; X < Width; X++)
            {
                *DestData = (byte)((*SrcData * 7472 + *(SrcData + 1) * 38469 + *(SrcData + 2) * 19595) >> 16);        //這里可以有不同的算法
                SrcData += 3;
                DestData++;
            }
        }
        Bmp.UnlockBits(BmpData);
        GrayBmp.UnlockBits(GrayBmpData);
        return GrayBmp;
    }

      在很多人心目中所謂的灰度圖像就是R=G=B這樣的圖像,只能說這些人還是門外漢,太不專業了。 這樣的圖像只能算是顏色分量相同的彩色圖像罷了,再次予以糾正。

      由於上述所描述的算法涉及到了圖像的四領域,因此我們采用類似PhotoShop算法原理解析系列 - 風格化---》查找邊緣 一文中的哨兵算法,對備份的圖像擴充邊界,擴充部分的數據以原始圖像邊界處的值填充。因為只涉及到了四領域,因此需要在圖像寬度和高度上分別增加2個像素即可。

      關於填充數據,我還是喜歡自己分配內存,而且我更傾向於直接使用API,這個可能與個人習慣有關吧,你們也可以按照自己的方式來處理。 

    private byte GetSimpleStatisticsThreshold(Bitmap GrayBmp)
    {
        int Width, Height, Stride, X, Y;
        int CloneStride, Ex, Ey;
        int Weight = 0;
        long SumWeight = 0;                 //  對於大圖像這個數字會溢出,所以用long類型的變量
        byte* Pointer, Scan0, CloneData;

        BitmapData GrayBmpData = GrayBmp.LockBits(new Rectangle(0, 0, GrayBmp.Width, GrayBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);

        Width = GrayBmp.Width; Height = GrayBmp.Height; Stride = GrayBmpData.Stride; CloneStride = Width + 2; Scan0 = (byte*)GrayBmpData.Scan0;
        CloneData = (byte*)GlobalAlloc(GPTR, CloneStride * (Height * 2));

        for (Y = 0; Y < Height; Y++)
        {
            *(CloneData + (Y + 1) * CloneStride) = *(Scan0 + Y * Stride);                           //  填充左側第一列像素(不包括第一個和最后一個點)
            CopyMemory(CloneData + CloneStride * (Y + 1) + 1, Scan0 + Y * Stride, Width);
            *(CloneData + (Y + 1) * CloneStride + Width + 1) = *(Scan0 + Y * Stride + Width - 1);   //  填充最右側那一列的數據
        }
        CopyMemory(CloneData, CloneData + CloneStride, CloneStride);                                //   第一行
        CopyMemory(CloneData + (Height + 1) * CloneStride, CloneData + Height * CloneStride, CloneStride);    //   最后一行

        for (Y = 0; Y < Height; Y++)
        {
            Pointer = CloneData + (Y + 1) * CloneStride + 1;
            for (X = 0; X < Width; X++)
            {
                Ex = *(Pointer - 1) - *(Pointer + 1);
                if (Ex < 0) Ex = -Ex;
                Ey = *(Pointer - CloneStride) - *(Pointer + CloneStride);
                if (Ey < 0) Ey = -Ey;
                if (Ex > Ey)
                {
                    Weight += Ex;
                    SumWeight += *Pointer * Ex;
                }
                else
                {
                    Weight += Ey;
                    SumWeight += *Pointer * Ey;
                }
                Pointer++;
            }
        }
        GlobalFree((IntPtr)CloneData);
        GrayBmp.UnlockBits(GrayBmpData);
        if (Weight == 0) return *(Scan0);  //  說明所有的顏色值都相同
        return (byte)(SumWeight / Weight);
    }

  一般情況下,為了程序的速度考慮,對於一些小函數我建議直接自己展開,比如上面的ABS函數,直接寫成if (Ex < 0) Ex = -Ex會快一些的。你通過下面的反匯編可以看出不同:

                Ex = Math.Abs(Ex);
00000161  js          00000167 
00000163  mov         eax,esi 
00000165  jmp         0000016E 
00000167  mov         ecx,esi 
00000169  call        638C54E4 
0000016e  mov         esi,eax 
                if (Ex < 0) Ex = -Ex;
00000170  test        eax,eax 
00000172  jge         00000176 
00000174  neg         esi 

  

     分割的效果可能還是要拿具體的圖像說事,這里不做過多評論。 

     工程下載地址:http://files.cnblogs.com/Imageshop/ThresholdUseSIS.rar

 

   博客園的網站分類里居然沒有圖像處理一欄,只有計算機圖形學一項,其實搞這一行的都知道,這兩個是完全不同的行業。希望博客園考慮增加圖像處理一欄。

 

***************************作者: laviewpbt   時間: 2013.7.21    聯系QQ:  33184777  轉載請保留本行信息*************************


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM