Two-Pass方法计算二值图连通域效率比较低,补充下Seed Filling种子填充法,该方法类似于图的深度搜素。
这里还是参考了这篇文章OpenCV_连通区域分析。
为了提高效率,做了一点修改。
- 针对新标签的第一个像素,其邻域只选取下方和后方未处理的像素;
- 针对处理过的像素添加标识符,下次再碰到该像素时,可直接跳过。
- 判断邻域坐标,防止越界。
具体代码:
bool seedFill(cv::Mat pBinary, int background, int foreground,
int border,cv::Mat& pLabel)
{
// connected component analysis (4- component)
if (pBinary.empty() || pBinary.channels() != 1)
return false;
int width = pBinary.cols;
int height = pBinary.rows;
pLabel.release();
pBinary.convertTo(pLabel, CV_32SC1);
int *data = pLabel.ptr<int>(0);
for (int i = 0; i < width*height; i++)
if (foreground == data[i]) data[i] = 1;
else data[i] = 0;
int label = 1;
Mat marked = pLabel.clone();
for (int i = border; i < height - border; i++)
{
int* curRow = pLabel.ptr<int>(i);
for (int j = border; j < width - border; j++)
{
int* cur_data = curRow + j;
int& marked_status = marked.ptr<int>(i)[j];
if (1 != *cur_data || 0 == marked_status)
continue;
*cur_data = ++label;
marked_status = 0;
stack<pair<int, int>> neighbor_data;
if( j < width -1 )
neighbor_data.push(pair<int, int>(i, j+1)); // right
if( i < height - 1 )
neighbor_data.push(pair<int, int>(i + 1, j)); // down
while (!neighbor_data.empty())
{
pair<int, int> position = neighbor_data.top();
int cur_y = position.first;
int cur_x = position.second;
if(cur_x < 0 ||cur_x >= width || cur_y < 0 || cur_y >= height)
{
neighbor_data.pop(); // 注意弹出旧值
continue;
}
int& label_data = pLabel.ptr<int>(cur_y)[cur_x];
int& stauts = marked.ptr<int>(cur_y)[cur_x];
if (1 != label_data || 0 == stauts)
{
neighbor_data.pop(); // 注意弹出旧值
continue;
}
label_data = label;
stauts = 0;
neighbor_data.pop();
// up
neighbor_data.push(pair<int,int>(cur_y - 1, cur_x) );
// down
neighbor_data.push(pair<int, int>(cur_y+1, cur_x));
// left
neighbor_data.push(pair<int, int>(cur_y, cur_x - 1));
// right
neighbor_data.push(pair<int, int>(cur_y, cur_x + 1));
}
}
}
return true;
}
结果:
完。
Lewis, 2018-06-25