一幅圖像二值化處理后往往包含多個區域,需要通過標記把它們分別提取出來。標記分割后圖像中各區域的簡單而有效的方法是檢查各像素與其相鄰像素的連通性。
在二值圖像中,背景區像素的值為0,目標區域的像素值為1。假設對一幅圖像從左向右,從上向下進行掃描,要標記當前正被掃描的像素需要檢查它與在它之前被掃描到的若干個近鄰像素的連通性。
考慮4連通的情形。對圖像進行逐像素掃描。
假如當前像素值為0,就移動到下一個掃描的位置。
假如當前像素值為1,檢查它左邊和上邊的兩個鄰接像素(這兩個像素一定會在當前像素之前被掃描到)。這兩個像素值和標記的組合有四種情況要考慮。
-
他們的像素值都為0。此時給該像素一個新的標記(表示一個新的連通域的開始)。
-
它們中間只有一個像素值為1。此時當前像素的標記=為1的像素值的標記。
-
它們的像素值都為1且標記相同。此時當前像素的標記=該標記。
-
它們的像素值為1且標記不同。將其中的較小的值賦給當前像素。之后從另一邊回溯到區域的開始像素為止。每次回溯再分別執行上述四個判斷步驟。
這樣即可保證所有的連通域都被標記出來。之后再通過對不同的標記賦予不同的顏色或將其加上邊框即可完成標記。
1 /// <summary> 2 /// 回溯法標記連通域 3 /// </summary> 4 /// <param name="x">該點的橫坐標</param> 5 /// <param name="y">該點的縱坐標</param> 6 /// <param name="isMarked">是否已經被標記過,用於記錄回溯路線。默認值為false,如果該點已經被標記過,則應指定該參數為true。</param> 7 private void Connect(int x, int y, bool isMarked = false) 8 { 9 if (x == 0 && y == 0) //mat[0, 0] 10 { 11 if (f(x, y) == 1) mat[x, y] = mark; // new area 12 } 13 14 else if (x != 0 && y == 0) // First Row 15 { 16 if (f(x, y) == 1) 17 { 18 if (mat[x - 1, y] != 0) 19 { 20 mat[x, y] = mat[x - 1, y]; // left one 21 Connect(x - 1, y, true); 22 } 23 else 24 { 25 if (isMarked == false) 26 mat[x, y] = ++mark; // new area 27 } 28 } 29 } 30 31 else if (x == 0 && y != 0) // First Column 32 { 33 if (f(x, y) == 1) 34 { 35 if (mat[x, y - 1] != 0) 36 { 37 mat[x, y] = mat[x, y - 1]; // up one 38 Connect(x, y - 1, true); 39 } 40 else 41 { 42 if (isMarked == false) 43 mat[x, y] = ++mark; 44 } 45 } 46 } 47 48 else if (x != 0 && y != 0) // other pixel 49 { 50 if (f(x, y) == 1) 51 { 52 if (mat[x, y - 1] == 0 && mat[x - 1, y] == 0) // new area 53 { 54 if (isMarked == false) 55 mat[x, y] = ++mark; 56 } 57 else if (mat[x, y - 1] == 0 && mat[x - 1, y] != 0) 58 { 59 if (isMarked == false) 60 mat[x, y] = mat[x - 1, y]; 61 else 62 { 63 if (mat[x - 1, y] > mat[x, y]) 64 mat[x - 1, y] = mat[x, y]; 65 Connect(x - 1, y, true); // 沿x方向繼續回溯 66 } 67 } 68 else if (mat[x, y - 1] != 0 && mat[x - 1, y] == 0) 69 { 70 if (isMarked == false) 71 mat[x, y] = mat[x, y - 1]; 72 else 73 { 74 if (mat[x, y - 1] > mat[x, y]) 75 mat[x, y - 1] = mat[x, y]; 76 Connect(x, y - 1, true); // 沿y方向繼續回溯 77 } 78 } 79 else if (mat[x, y - 1] != 0 && mat[x - 1, y] != 0 && mat[x, y - 1] == mat[x - 1, y]) 80 { 81 if (isMarked == false) 82 mat[x, y] = mat[x, y - 1]; 83 else 84 { 85 if (mat[x, y - 1] > mat[x, y]) 86 { 87 mat[x, y - 1] = mat[x - 1, y] = mat[x, y]; 88 Connect(x - 1, y, true); // 遇到上邊和左邊都有已標記像素的情況,兩邊同時回溯 89 Connect(x, y - 1, true); 90 } 91 } 92 93 } 94 else if (mat[x, y - 1] != 0 && mat[x - 1, y] != 0 && mat[x, y - 1] != mat[x - 1, y]) 95 { 96 mat[x, y] = Math.Min(mat[x - 1, y], mat[x, y - 1]); 97 mat[x - 1, y] = mat[x, y - 1] = mat[x, y]; // 直接消除等價類 98 Connect(x - 1, y, true); 99 Connect(x, y - 1, true); 100 } 101 } 102 }
執行效果如下:
二值化后的圖像
標記了連通域后的圖像
當然這個方法的一個問題是執行效率很低,對比較大的圖片需要較長時間才能完成標記步驟。但准確率還是比較高的。
參考文獻:
[1] 章毓晉. 圖像分割. 科學出版社,2001年,pp.63
[2] R.Gonzalez. 數字圖像處理. 電子工業出版社, 2014年, pp.38-40