這是個簡單的算法,是全局二值算法的一種,算法執行速度快。
算法過程簡單描述如下:
對於每一個像素,做如下處理
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 轉載請保留本行信息*************************