一、算法原理
利用连通区域算法计算图像中的各个轮廓,一般可以分4领域和8领域,如图所示。有两种经典的连通区域分析算法:Two-Pass(两次遍历)和Seed Filling(种子填充)。下面以四连通区域为例,分别介绍两种分析方法的原理。
1. Two-Pass算法
Two-Pass算法,顾名思义需要两次遍历图像,第一次遍历给图像所有的像素设置一个标记,并记录各个标记属于哪个连通域,第二次遍历将每个像素标记为所属的连通域。
具体流程如下:
- 图像二值化。
- 第一次按行扫描图像时,图像中的每个像素值从上到下,从左到右扫描,给每一个有效的像素值一个标签label,规则如下:
(1)如果该像素的4邻域中左边像素值和上边像素值都为0且都没有标签,则给该像素一个新的标签label;
(2)如果该像素的4邻域中左边像素值或上边像素值有一个为1,则该像素的标签是像素值为1的标签;
(3)如果该像素的4邻域中左边像素值和上边像素值都为1,且标签相同,则该像素的标签就是此标签;
(4)如果该像素的4邻域中左边像素值和上边像素值都为1,且标签不同,则把其中较小的标签作为该像素的标签。这样标记完后,会出现一个连通域里有好几个不同标签,将该像素的左边像素的标签和该像素的上边像素的标签记为相等关系。 - 第二次按行扫描图像时,将其中具有相等关系的标签 选择里面最小的标签作为它们的标签,即访问已经标记的标签并合并具有相等关系的标签。
2. 种子填充法(Seed Filling)
- 图像二值化。
- 从上到下,从左到右按行扫描图像,如果像素值为1则执行以下操作:
(1)把它当作一个种子,并赋予一个新的标签,并将此元素压入栈底;
(2)判断栈是否为空,如果不空,给该元素标记为前面(1)中的标签,将栈里的元素取出,然后依次访问该元素四邻域中的4个元素,这4个元素中像素值为1的,压入栈中;
(3)重复步骤3,直到栈为空。
这三个小步骤(1)(2)(3)构成一个连通域。 - 继续扫描图像,重复步骤2。
二、Seed Filling 4连通域的C++程序
int Seed_Filling_4(const cv::Mat& _binImg, cv::Mat& _lableImg)
{
// connected component analysis (4-component)
// use seed filling algorithm
// 1. begin with a foreground pixel and push its foreground neighbors into a stack;
// 2. pop the top pixel on the stack and label it with the same label until the stack is empty
//
// foreground pixel: _binImg(x,y) = 1
// background pixel: _binImg(x,y) = 0
if (_binImg.empty() ||
_binImg.type() != CV_8UC1)
{
return -1;
}
_binImg.convertTo(_lableImg, CV_32SC1);
int label = 1; // start by 2
int rows = _binImg.rows - 1;
int cols = _binImg.cols - 1;
for (int i = 1; i < rows - 1; i++)
{
int* data = _lableImg.ptr<int>(i);
for (int j = 1; j < cols - 1; j++)
{
if (data[j] == 1)
{
stack<std::pair<int, int>> neighborPixels;
neighborPixels.push(std::pair<int, int>(i, j)); // pixel position: <i,j>
++label; // begin with a new label
while (!neighborPixels.empty())
{
// get the top pixel on the stack and label it with the same label
std::pair<int, int> curPixel = neighborPixels.top();
int curX = curPixel.first;
int curY = curPixel.second;
_lableImg.at<int>(curX, curY) = label;
// pop the top pixel
neighborPixels.pop();
// push the 4-neighbors (foreground pixels)
if (curY != 0 && _lableImg.at<int>(curX, curY - 1) == 1)
{
// left pixel
neighborPixels.push(std::pair<int, int>(curX, curY - 1));
}
if (curY != cols && _lableImg.at<int>(curX, curY + 1) == 1)
{
// right pixel
neighborPixels.push(std::pair<int, int>(curX, curY + 1));
}
if (curX != 0 && _lableImg.at<int>(curX - 1