引言
圖像分割領域中一個重要步驟是求取圖像的連通區域,後續圖像輪廓理解的基石。
Matlab圖像工具箱函數bwlabel函數,就是對二值圖像連通區域進行標記的函數。
bwlabel
Label connected components in 2-D binary image。
Syntax
L = bwlabel(BW, n)
[L, num] = bwlabel(BW, n)
Description
L = bwlabel(BW, n) returns a matrix L, of the same size as BW, containing labels for the connected objects in BW. The variable n can have a value of either 4 or 8, where 4 specifies 4-connected objects and 8 specifies 8-connected objects. If the argument is omitted, it defaults to 8.
該函數返回一個與原圖大小一致的標記圖像矩陣。岡薩雷斯書中只介紹了其使用方法,沒有介紹其實現原理等,OpenCV也有connectedComponents函數。有必要造輪子,理解步驟。
Two-pass算法
這裏我實現的是Two Pass算法,四鄰域。該算法對二值圖像掃描兩次:
- 當前像素點若是前景(非零),判斷其已掃描過的鄰接節點(上鄰像素,左鄰像素)有無已標記的。若僅有一個含有標記值,則將該標記賦予該位置,若兩個都含有標記值,將最小標記賦予當前值,並將這兩個標記值合併(Union),歸爲同一類;若無,新增標記賦予當前值,即label++。
- 對上述含有標記的像素,查找其集合的根節點(Find),用根節點對當前值賦值。
執行動態圖如下(該圖是盜網友的,上面有logo的)。
這裏使用了一種並查集的數據結構,可以看作數組方式實現的樹結構。該結構主要含有兩個操作:
- 快速對集合元素合併歸類
- 快速查找一個元素的根節點
這在嚴蔚敏的數據結構書中有介紹過的。看一下下圖,希望能趕緊回憶起來吧。
這個並查集數組,初始化的時候全爲0,即每個都是根節點。然後我們發現有些元素屬於同一類,就將其一個節點作爲另一個節點的孩子,合併爲同一個樹。最後,我們都用每個集合的根節點的作爲該集合的標誌。
代碼
#define MAXLABEL 500
uchar parent[MAXLABEL] = {0};
int Find(uchar x, uchar parent[])
{
int i = x;
while (0 != parent[i])
i = parent[i];
return i;
}
void Union(uchar big, uchar small, uchar parent[])
{
uchar i = big;
uchar j = small;
while (0 != parent[i])
i = parent[i];
while (0 != parent[j])
j = parent[j];
if (i != j)
parent[i] = j;
}
Mat Label(Mat &I)
{
/// first pass
int label = 0;
Mat dst = Mat::zeros(I.size(), I.type());
for (int nY = 0; nY < I.rows; nY++)
{
for (int nX = 0; nX < I.cols;nX++)
{
if (I.at<uchar>(nY,nX) != 0)
{
uchar left = nX - 1<0?0:dst.at<uchar>(nY, nX - 1);
uchar up = nY - 1<0?0:dst.at<uchar>(nY - 1, nX);
if (left != 0 || up != 0)
{
if (left != 0 && up != 0)
{
dst.at<uchar>(nY, nX) = min(left, up);
if(left < up)
Union(up,left,parent);
else if(up<left)
Union(left, up, parent);
}
else
dst.at<uchar>(nY, nX) = max(left, up);
}
else
{
dst.at<uchar>(nY, nX) = ++label;
}
}
}
}
/// second pass
for (int nY = 0; nY < I.rows; nY++)
{
for (int nX = 0; nX < I.cols; nX++)
{
if (I.at<uchar>(nY, nX) == 1)
dst.at<uchar>(nY, nX) = Find(dst.at<uchar>(nY, nX), parent);
}
}
return dst;
}
效果
爲了顯示好看,我將連通區域着色,這裏使用哈希函數,將每個標記乘以一個素數對256求餘,這樣可以將顏色散列開來。
更多閱讀
George Stockman and Linda G. Shapiro. 2001. Computer Vision (1st ed.). Prentice Hall PTR, Upper Saddle River, NJ, USA. Chapter 3.