连通域分析其实是一个路径搜索问题,搜索方式就看联通的规则(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; } }