C代碼二值圖像連通區域標記

之前寫過一個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是我自己寫的文件讀寫函數,你們可以用自己的方法去做數據交流。我驗證了多次,結果沒有問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章