關於Two-Pass標記連通域個數
背景
在完成圖像的一系列處理后,得到二值圖,一般會統計目標數量,即是獲取連通域個數,這里采用TwoPass的方法。
基本思想
在Two-pass連通域標記中,第一次標記(first pass)時從左向右,從上向下掃描,會將各個有效像素置一個label值,判斷規則如下(以4鄰域為例):
- 當該像素的左鄰像素和上鄰像素為無效值時,給該像素置一個新的label值,label ++;
- 該像素的左鄰像素或者上鄰像素有一個為有效值時,將有效值像素的label賦給該像素的label值;
- 當該像素的左鄰像素和上鄰像素都為有效值時,選取其中較小的label值賦給該像素的label值。
此時,還需維護一個關系表,記錄哪些label值屬於同一個連通域。這個關系表通常用union-find數據結構來實現。
原文在這
還有個圖:
原文說用union-find結構保存連通域,沒仔細看,於是自己用了個數組保存,類似於hash...實現起來也並不是很麻煩,大概這樣,在開始建立一個map數組固定大小,一般而言應該與用於標記的最大數一致(注意,這里是標記數,是小於連通域數的),這里取uint16最大值,65535,這樣也就意味着連通域數不能太大,估計最大能達到萬把個吧,因為不同的圖標記數和連通域數關系是不一樣的。
在FirstPass時,取周圍點的標記最小值,map[標記]=min,這樣就構成了map數組,然后根據map數據進行第二次標記。
實現
int ImageAlgorithm::TwoPassConnetedDomin(Mat image) {
Mat imageFlag;
imageFlag.create(image.rows, image.cols, CV_16UC1);
for (int k = 0; k < image.rows; ++k) {
for (int i = 0; i < image.cols; ++i) {
imageFlag.at<ushort>(k, i) = UINT16_MAX;
}
}
uint16_t mapp[UINT16_MAX];
memset(mapp, UINT16_MAX, sizeof(uint16_t));
//第一次掃描,完成ImageFlag中的標記
int num = 0;
for (int i = 0; i < image.rows; ++i) {
for (int j = 0; j < image.cols; ++j) {
auto &cu = image.at<Vec3b>(i, j);
if (cu[0] == 255) {
uint16_t pos[4];
auto &up = pos[0];
auto &left = pos[1];
auto &ul = pos[2];
if (i > 0)
up = imageFlag.at<ushort>(i - 1, j);
if (j > 0)
left = imageFlag.at<ushort>(i, j - 1);
if (i > 0 && j > 0)
ul = imageFlag.at<ushort>(i - 1, j - 1);
uint16_t min = pos[0];
for (int m = 1; m < 3; m++)
if (min > pos[m])
min = pos[m];
for (int m = 0; m < 3; m++) {
if (mapp[pos[m]] > min)
mapp[pos[m]] = min;
}
if (min == UINT16_MAX) {
imageFlag.at<ushort>(i, j) = num;
mapp[num] = num;
num++;
if(num>=UINT16_MAX)
return -1;
} else {
imageFlag.at<ushort>(i, j) = min;
}
}
}
}
//第二次掃描,進行標記,以及修改map
map<ushort, uint32_t> colorMap;
int total = 0;
for (int n = 0; n < num; ++n) {
if (mapp[n] == n) {
total++;
uint32_t t = 0;
for (int i = 0; i < 3; ++i) {
t += (uint32_t) (rand() / (RAND_MAX + 0.0) * 255) << (i * 8);
}
colorMap[n] = t;
} else
mapp[n] = mapp[mapp[n]];
}
for (int i = 0; i < imageFlag.rows; ++i) {
for (int j = 0; j < imageFlag.cols; ++j) {
auto t = imageFlag.at<ushort>(i, j);
if (t != UINT16_MAX) {
uint32_t c = colorMap[mapp[t]];
image.at<Vec3b>(i, j)[0] = (uchar) c;
image.at<Vec3b>(i, j)[1] = (uchar) (c >> 8);
image.at<Vec3b>(i, j)[2] = (uchar) (c >> 16);
}
}
}
return total;
}