C程式碼二值影象連通區域標記
阿新 • • 發佈:2019-01-29
之前寫過一個C++版本的二值影象連通區域標記函式,當時的直觀結果沒有問題,我也使用了很久,後來才發現其結果是錯的,I'm so sorry!
這裡貼出的是一個經過改進的二值影象連通區域標記函式,目前只支援4連通區域標記,要想做到8連通標記的話,最簡單的方法是先用[1 1 1]的核對輸入影象(的每一行)進行dilate。
較前一個版本的改進:(1)函式經過嚴格的測試,通過與Matlab連通區域標記結果的比較,確信可以輸出正確的結果;(2)中間記憶體不再使用std::vector來動態申請,而是使用預申請的陣列空間,效率更高(但是所需要的記憶體大小往往更大);(3)輸出的標籤影象中的數值是連續的(前一版本的標籤資料是不連續的),可以很方便地進行後續操作,比如統計每個連通區域的面積。
#include <memory> //Labling connected components in an image, where non-zero pixels are // deemed as foreground, and will be labeled with an positive integer // while background pixels will be labled with zeros. //Input and output are 2D matrices of size h-by-w. //Return maxLabel. Output labels are continuously ranged between [0,maxLabel). //Assume each pixel has 4 neighbors. //yuxianguo, 2018/3/27 int bwLabel(const unsigned char *bw, int *label, int h, int w) { memset(label, 0, h * w << 2); //link[i]: //(1) link label value "i" to its connected component (another label value); //(2) if link[i] == i, then it is a root. int maxComponents = (h * w >> 1) + 1; //max possible connected components int * const link = new int[maxComponents]; int lb = 1, x, y, a, b, t, *p = label; link[0] = 0; //first row if(bw[0]) { p[0] = lb; link[lb] = lb; lb++; } for(x = 1; x < w; x++) if(bw[x]) { if(p[x - 1]) p[x] = p[x - 1]; else { p[x] = lb; link[lb] = lb; lb++; } } bw += w, p += w; //rest rows for(y = 1; y < h; y++, bw += w, p += w) { if(bw[0]) { if(p[-w]) p[0] = p[-w]; else { p[0] = lb; link[lb] = lb; lb++; } } for(x = 1; x < w; x++) if(bw[x]) { a = p[x - 1], b = p[x - w]; //left & top if(a) { if(a == b) p[x] = a; else { //find root of a t = a; while(a != link[a]) a = link[a]; p[x] = link[t] = a; if(b) { //find root of b t = b; while(b != link[b]) b = link[b]; link[t] = b; //link b to a or link a to b, both fine if(a < b) link[b] = a; else link[a] = b; } } } else if(b) { //find root of b t = b; while(b != link[b]) b = link[b]; p[x] = link[t] = b; } else { //generate a new component p[x] = lb; link[lb] = lb; lb++; } } } //Rearrange the labels with continuous numbers t = 1; for(x = 1; x < lb; x++) if(x == link[x]) { link[x] = -t; //using negative values to denote roots t++; } for(x = 1; x < lb; x++) { //find the root of x y = x; while(link[y] >= 0) y = link[y]; //set the value of label x link[x] = link[y]; } //Negative to positive for(x = 1; x < lb; x++) link[x] = -link[x]; //Replace existing label values by the corresponding root label values p = label; for(y = 0; y < h; y++, p += w) for(x = 0; x < w; x++) p[x] = link[p[x]]; delete[] link; return t; //num components (maxLabel + 1) }
可以藉助Matlab和下面這段程式碼來測試以上函式實現的正確性:
for n = 1:15 h = randi(400) + 50; w = randi(400) + 50; I = uint8(rand(h, w) * 255); I(rand(h, w) < 0.5) = 0; % extreme case #1: %I(:) = 0; I(1:2:end,2:2:end) = 1; I(2:2:end,1:2:end) = 1; % extreme case #2: %I(:) = 0; I(1:2:end,1:2:end) = 1; I(2:2:end,2:2:end) = 1; yusave(I,'I'); % save data to local file % call c++ program A = ld('A'); % load result from local file L1 = bwlabel(I,4); L2 = bwlabel(A,4); S1 = regionprops(L1,'area'); S1 = sort([S1.Area]); m = max(L2(:)); S2 = zeros(1,m); for k = 1:m S2(k) = nnz(A==k); end S2 = sort(S2); assert(isequal(S1,S2)); end
其中yusave和ld是我自己寫的檔案讀寫函式,你們可以用自己的方法去做資料交流。我驗證了多次,結果沒有問題。