连通域分析其实是一个路径搜索问题,搜索方式就看联通的规则(4联通:上下左右算是联通,8联通:上下左右还有四个对角)
01矩阵背景是0,黑色的,有色区域是1,白色的,从图像的左上角(最外围的边要去掉)进行遍历,将找到的第一个值为1的像素点作为起点,对他进行连通域搜寻,将搜寻到的整个连通域内的像素点标为2(为了避免与本来的颜色1冲突)
继续搜索像素值为1的点(之前联通域分析过的已经将像素值改为大于1的值,所以像素值为1的就是还没有分析过的),直到整个图像遍历完成
遍历方法:
找到起点后初始化一个栈,存放连通域的点坐标(pair),将起点的横纵坐标压入栈中,连通域的标记值自增(第一个连通域的标记值为2,从2开始,第二个连通域的标记值为3,以此类推)
当栈不为空时,从栈顶取点,读出他的横纵坐标,在Label图中,根据横纵坐标对连通域进行对应标记值的赋值,然后将该点出栈,同时按照联通规则,根据坐标找到它的相邻像素点,看相邻像素点的值是否为1,如果是1,则是连通域,将这个点入栈(判断时记得要加上边界条件,防止点下标溢出,我的代码偷懒了没有加,正常要加上 0<=Row<BinaryImg.rows&&0<=Col<BinaryImg.cols)
代码
void SeedFilling(const Mat &BinaryImg, Mat &LabelImg)
{
if (BinaryImg.empty || BinaryImg.type() != CV_8UC1)
return;
BinaryImg.convertTo(LabelImg, CV_32SC1);
int Label = 1;// 从2开始,防止和二值图像的像素值重合
int Row = BinaryImg.rows - 1;
int Col = BinaryImg.cols - 1;
for (int i = 1; i < Row; ++i)
{
int* data = LabelImg.ptr<int>(i);
for (int j = 1; j < Col - 1; ++j)
{
if (data[j] == 1)
{
stack<pair<int, int>> NeighborPixel;
NeighborPixel.push(pair<int, int>(i, j));
++Label;// 新标签
while (!NeighborPixel.empty)// 栈不为空
{
pair<int, int> CurPixel = NeighborPixel.top();
int CurRow = CurPixel.first;
int CurCol = CurPixel.second;
LabelImg.at<int>(CurRow, CurCol) = Label;
NeighborPixel.pop();// 出栈
if (LabelImg.at<int>(CurRow, CurCol - 1) == 1)
NeighborPixel.push(pair<int, int>(CurRow, CurCol - 1));
if (LabelImg.at<int>(CurRow, CurCol + 1) == 1)
NeighborPixel.push(pair<int, int>(CurRow, CurCol + 1));
if (LabelImg.at<int>(CurRow - 1, CurCol) == 1)
NeighborPixel.push(pair<int, int>(CurRow - 1, CurCol));
if (LabelImg.at<int>(CurRow + 1, CurCol) == 1)
NeighborPixel.push(pair<int, int>(CurRow + 1, CurCol));
}
}
}
return;
}
}
无情的摸鱼机器