在圖像處理領域,二值圖像運算量小,並且能夠體現圖像的關鍵特徵,因此被廣泛使用。將灰度圖像變爲二值圖像的常用方法是選定閾值,然後將待處理圖像的每個像素點進行單點處理,即將其灰度值與所設置的門限進行比對,從而得到二值化的黑白圖。這樣一種方式因爲其直觀性以及易於實現,已經在圖像分割領域處於中心地位。本文主要對最近一段時間作者所學習的閾值化圖像分割算法進行總結,全文描述了作者對每種算法的理解,並基於OpenCV和VC6.0對這些算法進行了實現。最終將源代碼公開,希望大家一起進步。(本文的代碼暫時沒有考慮執行效率問題)
首先給出待分割的圖像如下:
1、Otsu法(最大類間方差法)
該算法是日本人Otsu提出的一種動態閾值分割算法。它的主要思想是按照灰度特性將圖像劃分爲背景和目標2部分,劃分依據爲選取門限值,使得背景和目標之間的方差最大。(背景和目標之間的類間方差越大,說明這兩部分的差別越大,當部分目標被錯劃分爲背景或部分背景錯劃分爲目標都會導致這兩部分差別變小。因此,使用類間方差最大的分割意味着錯分概率最小。)這是該方法的主要思路。其主要的實現原理爲如下:
1)建立圖像灰度直方圖(共有L個灰度級,每個出現概率爲p)
2)計算背景和目標的出現概率,計算方法如下:
上式中假設t爲所選定的閾值,A代表背景(灰度級爲0~N),根據直方圖中的元素可知,Pa爲背景出現的概率,同理B爲目標,Pb爲目標出現的概率。
3)計算A和B兩個區域的類間方差如下:
第一個表達式分別計算A和B區域的平均灰度值;
第二個表達式計算灰度圖像全局的灰度平均值;
第三個表達式計算A、B兩個區域的類間方差。
4)以上幾個步驟計算出了單個灰度值上的類間方差,因此最佳分割門限值應該是圖像中能夠使得A與B的類間灰度方差最大的灰度值。在程序中需要對每個出現的灰度值據此進行尋優。
本人的VC實現代碼如下。
- /*****************************************************************************
- *
- * \函數名稱:
- * OneDimentionOtsu()
- *
- * \輸入參數:
- * pGrayMat: 二值圖像數據
- * width: 圖形尺寸寬度
- * height: 圖形尺寸高度
- * nTlreshold: 經過算法處理得到的二值化分割閾值
- * \返回值:
- * 無
- * \函數說明:實現灰度圖的二值化分割——最大類間方差法(Otsu算法,俗稱大津算法)
- *
- ****************************************************************************/
- void CBinarizationDlg::OneDimentionOtsu(CvMat *pGrayMat, int width, int height, BYTE &nThreshold)
- {
- double nHistogram[256]; //灰度直方圖
- double dVariance[256]; //類間方差
- int N = height*width; //總像素數
- for(int i=0; i<256; i++)
- {
- nHistogram[i] = 0.0;
- dVariance[i] = 0.0;
- }
- for(i=0; i<height; i++)
- {
- for(int j=0; j<width; j++)
- {
- unsigned char nData = (unsigned char)cvmGet(pGrayMat, i, j);
- nHistogram[nData]++; //建立直方圖
- }
- }
- double Pa=0.0; //背景出現概率
- double Pb=0.0; //目標出現概率
- double Wa=0.0; //背景平均灰度值
- double Wb=0.0; //目標平均灰度值
- double W0=0.0; //全局平均灰度值
- double dData1=0.0, dData2=0.0;
- for(i=0; i<256; i++) //計算全局平均灰度
- {
- nHistogram[i] /= N;
- W0 += i*nHistogram[i];
- }
- for(i=0; i<256; i++) //對每個灰度值計算類間方差
- {
- Pa += nHistogram[i];
- Pb = 1-Pa;
- dData1 += i*nHistogram[i];
- dData2 = W0-dData1;
- Wa = dData1/Pa;
- Wb = dData2/Pb;
- dVariance[i] = (Pa*Pb* pow((Wb-Wa), 2));
- }
- //遍歷每個方差,求取類間最大方差所對應的灰度值
- double temp=0.0;
- for(i=0; i<256; i++)
- {
- if(dVariance[i]>temp)
- {
- temp = dVariance[i];
- nThreshold = i;
- }
- }
- }
2、一維交叉熵值法
這種方法與類間最大方差很相似,是由Li和Lee應用了信息論中熵理論發展而來。首先簡要介紹交叉熵的概念。
對於兩個分佈P和Q,定義其信息交叉熵D如下:
這代表的物理意義是兩個分佈之間信息理論距離,另外一種理解是,將分佈P變爲Q後所帶來的信息變化。那麼對於圖像分割來說,如果要用分割圖像來替換原來的圖像,最優的分割依據應該就是使得兩幅圖像之間的交叉熵最小。以下對最小交叉熵法的過程進行簡要總結。
可以假設上文的P爲源圖像的灰度分佈,Q爲所得到的分割圖像的灰度分佈,其中:
上式中H爲統計直方圖;
N爲圖像總的像素點數;
L爲源圖像總的灰度級數;
P代表源圖像,其每個元素代表每個灰度級上的灰度分佈(平均灰度值);
Q爲分割後的二值圖像,兩個u分別代表兩個分割後的區域的平均灰度值,其中t爲分割圖像所採用的閾值。
根據以上定義,以每個灰度級上的灰度和爲計算量,可以很容易根據交叉熵的公式,推導出P和Q之間的交叉熵定量表達式:
根據上文所述思路,使得D最小的t即爲最小交叉熵意義下的最優閾值。
作者VC實現代碼如下。
- /*****************************************************************************
- *
- * \函數名稱:
- * MiniCross()
- *
- * \輸入參數:
- * pGrayMat: 二值圖像數據
- * width: 圖形尺寸寬度
- * height: 圖形尺寸高度
- * nTlreshold: 經過算法處理得到的二值化分割閾值
- * \返回值:
- * 無
- * \函數說明:實現灰度圖的二值化分割——最小交叉熵算法
- *
- ****************************************************************************/
- void CBinarizationDlg::MiniCross(CvMat *pGrayMat, int width, int height, BYTE &nThreshold)
- {
- double dHistogram[256]; //灰度直方圖
- double dEntropy[256]; //每個像素的交叉熵
- int N = height*width; //總像素數
- for(int i=0; i<256; i++)
- {
- dHistogram[i] = 0.0;
- dEntropy[i] = 0.0;
- }
- for(i=0; i<height; i++)
- {
- for(int j=0; j<width; j++)
- {
- unsigned char nData = (unsigned char)cvmGet(pGrayMat, i, j);
- dHistogram[nData]++; //建立直方圖
- }
- }
- double Pa=0.0; //區域1平均灰度值
- double Pb=0.0; //區域2平均灰度值
- double P0=0.0; //全局平均灰度值
- double Wa=0.0; //第一部分熵
- double Wb=0.0; //第二部分的熵
- double dData1=0.0, dData2=0.0; //中間值
- double dData3=0.0, dData4=0.0; //中間值
- for(i=0; i<256; i++) //計算全局平均灰度
- {
- dHistogram[i] /= N;
- P0 += i*dHistogram[i];
- }
- for(i=0; i<256; i++)
- {
- Wa=Wb=dData1=dData2=dData3=dData4=Pa=Pb=0.0;
- for(int j=0; j<256; j++)
- {
- if(j<=i)
- {
- dData1 += dHistogram[j];
- dData2 += j*dHistogram[j];
- }
- else
- {
- dData3 += dHistogram[j];
- dData4 += j*dHistogram[j];
- }
- }
- Pa = dData2/dData1;
- Pb = dData4/dData3;
- for(j=0; j<256; j++)
- {
- if(j<=i)
- {
- if((Pa!=0)&&(dHistogram[j]!=0))
- {
- double d1 = log(dHistogram[j]/Pa);
- Wa += j*dHistogram[j]*d1/log(2);
- }
- }
- else
- {
- if((Pb!=0)&&(dHistogram[j]!=0))
- {
- double d2 = log(dHistogram[j]/Pb);
- Wb += j*dHistogram[j]*d2/log(2);
- }
- }
- }
- dEntropy[i] = Wa+Wb;
- }
- //遍歷熵值,求取最小交叉熵所對應的灰度值
- double temp=dEntropy[0];
- for(i=1; i<256; i++)
- {
- if(dEntropy[i]<temp)
- {
- temp = dEntropy[i];
- nThreshold = i;
- }
- }
- }
閾值分割結果如下圖,求解所得的閾值爲106.
3、二維OTSU法
這種方法是對類間最大方差法的擴展,將其從求兩個一維分佈最大類間方差擴充爲求解類間離散度矩陣的跡的最大值,考慮像素點灰度級的基礎上增加了對像素點鄰域平均像素值的考慮。
以下按照本人的理解對該方法的思路以及推倒過程進行分析:
1)首先需要建立二維的灰度統計直方圖P(f, g);
圖像的灰度級爲L級,那麼其每個像素點的8鄰域灰度平均值的灰度級也爲L級,據此來構建直方圖P。二維統計直方圖的橫軸爲每個像素點的灰度值f(i, j),縱座標爲同一個點對應的鄰域平均值g(i, j) 其中(0≤i<height, 0≤j<width, 0≤f(i, j)<L),而所對應的P(f,g)整幅圖像中灰度值爲f,鄰域灰度均值爲g的點的統計值佔總像素點的比例(即爲灰度值出現的聯合概率密度)。其中P的每個元素滿足如下公式:
n爲整幅圖像中灰度值爲f,鄰域灰度均值爲g的點的統計值;
N爲圖像總的像素點個數;
2)對於下圖所示的二維統計直方圖,t代表橫座標(灰度值),s代表縱座標(像素點鄰域的灰度均值)
對已圖像中的閾值點(t,s)來說,其灰度值t和其鄰域內的灰度均值s不應該相差太多,如果t比s大很多(點位於上圖中的II區域),說明像素的灰度值遠遠大於其臨域的灰度均值,故而該點很可能是噪聲點,反之如果t比s小很多(點位於途中的IV區域),即該點的像素值比其臨域均值小很多,則說明是一個邊緣點。據此我們在進行背景前景分割的時候忽略這些干擾因素,認爲這兩個區域內Pi,j=0。剩下的I區域和III區域則分別代表了前景和背景。以下據此來推導對於選定的閾值(t, s),進行離散度判據的最優推導。
3)推導閾值(t, s)點處的離散度矩陣判據
根據上文分析可知,由閾值(t, s)所分割的前景和背景出現的概率如下:
定義兩個中間變量,方便下面推導:
據此,這兩部分的灰度均值向量可以推導如下(兩個分量分別根據灰度值以及每個點的灰度均值計算):
整幅圖像的灰度均值向量爲:
與一維的大津法一樣的思路,推導類間方差,這裏是二維因此要用矩陣形式。參考一維法,可同樣定義類間“方差”矩陣如下:
爲了在實現的時候,容易判斷出這樣一個矩陣的“最大值”,因此數學中採用矩陣的跡(對角線之和)來衡量矩陣的“大小”。因此以該矩陣的跡作爲離散度測度,推導如下:
這樣就可以通過求解使得這個參數最大時的(t,s)即爲所求得的最佳閾值組合。
以下爲具體算法實現過程:
1)建立二維直方圖
2)對直方圖進行遍歷,計算每個(t,s)組合所得到的矩陣離散度,也就是一維大津法中所謂的最大類間方差。
3)求得使“類間方差”最大的(t,s),由於t代表灰度值,s代表改點在其鄰域內的灰度均值,因此本人認爲在選擇閾值時可以選擇s爲最佳,當然用t也可以,因爲從求解結果可以看出,這這個數值往往很接近。
具體的實現代碼如下:
- /*****************************************************************************
- *
- * \函數名稱:
- * TwoDimentionOtsu()
- *
- * \輸入參數:
- * pGrayMat: 二值圖像數據
- * width: 圖形尺寸寬度
- * height: 圖形尺寸高度
- * nTlreshold: 經過算法處理得到的二值化分割閾值
- * \返回值:
- * 無
- * \函數說明:實現灰度圖的二值化分割——最大類間方差法(二維Otsu算法)
- * \備註:在構建二維直方圖的時候,採用灰度點的3*3鄰域均值
- ******************************************************************************/
- void CBinarizationDlg::TwoDimentionOtsu(CvMat *pGrayMat, int width, int height, BYTE &nThreshold)
- {
- double dHistogram[256][256]; //建立二維灰度直方圖
- double dTrMatrix = 0.0; //離散矩陣的跡
- int N = height*width; //總像素數
- for(int i=0; i<256; i++)
- {
- for(int j=0; j<256; j++)
- dHistogram[i][j] = 0.0; //初始化變量
- }
- for(i=0; i<height; i++)
- {
- for(int j=0; j<width; j++)
- {
- unsigned char nData1 = (unsigned char)cvmGet(pGrayMat, i, j); //當前的灰度值
- unsigned char nData2 = 0;
- int nData3 = 0; //注意9個值相加可能超過一個字節
- for(int m=i-1; m<=i+1; m++)
- {
- for(int n=j-1; n<=j+1; n++)
- {
- if((m>=0)&&(m<height)&&(n>=0)&&(n<width))
- nData3 += (unsigned char)cvmGet(pGrayMat, m, n); //當前的灰度值
- }
- }
- nData2 = (unsigned char)(nData3/9); //對於越界的索引值進行補零,鄰域均值
- dHistogram[nData1][nData2]++;
- }
- }
- for(i=0; i<256; i++)
- for(int j=0; j<256; j++)
- dHistogram[i][j] /= N; //得到歸一化的概率分佈
- double Pai = 0.0; //目標區均值矢量i分量
- double Paj = 0.0; //目標區均值矢量j分量
- double Pbi = 0.0; //背景區均值矢量i分量
- double Pbj = 0.0; //背景區均值矢量j分量
- double Pti = 0.0; //全局均值矢量i分量
- double Ptj = 0.0; //全局均值矢量j分量
- double W0 = 0.0; //目標區的聯合概率密度
- double W1 = 0.0; //背景區的聯合概率密度
- double dData1 = 0.0;
- double dData2 = 0.0;
- double dData3 = 0.0;
- double dData4 = 0.0; //中間變量
- int nThreshold_s = 0;
- int nThreshold_t = 0;
- double temp = 0.0; //尋求最大值
- for(i=0; i<256; i++)
- {
- for(int j=0; j<256; j++)
- {
- Pti += i*dHistogram[i][j];
- Ptj += j*dHistogram[i][j];
- }
- }
- for(i=0; i<256; i++)
- {
- for(int j=0; j<256; j++)
- {
- W0 += dHistogram[i][j];
- dData1 += i*dHistogram[i][j];
- dData2 += j*dHistogram[i][j];
- W1 = 1-W0;
- dData3 = Pti-dData1;
- dData4 = Ptj-dData2;
- /* W1=dData3=dData4=0.0; //對內循環的數據進行初始化
- for(int s=i+1; s<256; s++)
- {
- for(int t=j+1; t<256; t++)
- {
- W1 += dHistogram[s][t];
- dData3 += s*dHistogram[s][t]; //方法2
- dData4 += t*dHistogram[s][t]; //也可以增加循環進行計算
- }
- }*/
- Pai = dData1/W0;
- Paj = dData2/W0;
- Pbi = dData3/W1;
- Pbj = dData4/W1; // 得到兩個均值向量,用4個分量表示
- dTrMatrix = ((W0*Pti-dData1)*(W0*Pti-dData1)+(W0*Ptj-dData1)*(W0*Ptj-dData2))/(W0*W1);
- if(dTrMatrix > temp)
- {
- temp = dTrMatrix;
- nThreshold_s = i;
- nThreshold_t = j;
- }
- }
- }
- nThreshold = nThreshold_t; //返回結果中的灰度值
- //nThreshold = 100;
- }
閾值分割結果如下圖,求解所得的閾值爲114.(s=114,t=117)
參考文獻
[1] Nobuyuki Otsu. A Threshold SelectionMethod from Gray-Level Histograms.
[2] C. H. Li and C. K. Lee. Minimum CrossEntropy Thresholding
[3] Jian Gong, LiYuan Liand WeiNan Chen. Fast Recursive Algorithms For Two-Dimensional Thresholding.
歡迎登陸我的個人主頁,hello2019,查看原文:http://richardliu.cn/