给定0-1矩阵求连通域_xunan003的博客-CSDN博客_连通域
求0-1矩阵连通域这篇文章方法讲的算是比较详细,但是程序由于我没学过C++,再加上编程经验很少,这篇博文给出的程序一致没怎么看懂,啃了两天终于有点眉目了,在此做一个总结,也跟小白们分享一下,希望也能对你们有帮助。
求连通域的方法分为三步
第一步:(打开冰箱)对一个二维矩阵,先求出每一行白色团的起始和终止坐标(列),并给每个团标记序列
第二步:合并密接团的标签,鉴别密接的方式是通过轨迹(列)重叠的方式,轨迹重叠,标签归化,若某团与两类不同的团发生密接,代表这两类团等价(初筛阶段以2个为标准),需要统一隔离。
第三步:对等价团进行汇总筛选,凡有交叠的统一归为一类,以此分为不同的感染群类。
给出了三段程序,分别对应上述的三个步骤。
void fillRunVectors(const Mat& bwImage, int& NumberOfRuns, vector<int>& stRun, vector<int>& enRun, vector<int>& rowRun)
void firstPass(vector<int>& stRun, vector<int>& enRun, vector<int>& rowRun, int NumberOfRuns,
vector<int>& runLabels, vector<pair<int, int>>& equivalences, int offset)
void replaceSameLabel(vector<int>& runLabels, vector<pair<int, int>>&
equivalence)
阅读程序的步骤
第一段程序
一、理解程序目的和实现方式
拿到每一段程序一定要完全搞清楚程序做了什么事情,是怎么实现的,如果没有详细说明怎么实现的,就需要猜测验证,否则程序会读的很痛苦,就像一篇不认识单词也不知所云的阅读理解。
第一段程序完成每一行白色团的起始和终止坐标,并给每个团标记序列。
1、如何标记坐标
第i行的第j1个点为白色团的起始坐标j2个点为终止坐标,就需要将j1压入start队列中,j2压入end队列中,并对应处其属于第i行。
2、如何分辨起始和终止坐标
起始点(1、第一列为白色像素;2、此列为白色像素,同时左列为黑色像素)
终止点(1、此列为白色色像素,右列为黑色像素;2、最后一列为白色像素)
二、理解程序中由大到小的结构对应完成的功能(结构自大到小)
主体有两个for循环,每个for循环是在循环什么?
i循环行,j循环列,找到起始和终止点的坐标
条件语句是做什么判断(看小括号的内容)?选择后实现什么功能(看大括号最后的结论)?
判断是否为白像素,分别压入了坐标值
三、根据变量的行为,对应目标功能就可以猜测出每个变量的指代和含义
NumberofRuns代表白团的数目,rowrun对应每个白团所属的行数
strun代表每个白团的起始坐标,enrun代表每个白团的终止坐标
四、最后综合复盘程序
void fillRunVectors(const Mat& bwImage, int& NumberOfRuns, vector<int>& stRun, vector<int>& enRun, vector<int>& rowRun)
{
for (int i = 0; i < bwImage.rows; i++)
{
const uchar* rowData = bwImage.ptr<uchar>(i);
if (rowData[0] == 255)
{
NumberOfRuns++;
stRun.push_back(0);
rowRun.push_back(i);
}
for (int j = 1; j < bwImage.cols; j++)
{
if (rowData[j - 1] == 0 && rowData[j] == 255)
{
NumberOfRuns++;
stRun.push_back(j);
rowRun.push_back(i);
}
else if (rowData[j - 1] == 255 && rowData[j] == 0)
{
enRun.push_back(j - 1);
}
}
if (rowData[bwImage.cols - 1])
{
enRun.push_back(bwImage.cols - 1);
}
}
}
第二段程序
依旧是按照上面的四个步骤
一、firstPass函数完成团的标记与等价对列表的生成
a、第一行的团,分别给出标号;
b1、对于除了第一行外的所有行里的团,如果它与前一行中的所有团都没有重合区域,则给它一个新的标号;
b2、如果它仅与上一行中一个团有重合区域,则将上一行的那个团的标号赋给它;
b3、上一行的2个以上的团有重叠区域,则给当前团赋一个相连团的最小标号,并将上一行的这几个团的标记分别写入等价对,说明它们属于一类(等价对是两个团之间的)
1、如何判定相邻行的两个团有重合?
根据两个团的起始和终点列标重合情况进行判断,curstart>preend&curend<prestart
二、依旧存在两个嵌套的for循环
for循环实现遍历的功能,根据循环的上限可以提示循环的对象
这一段功能需要遍历所有的团,读取每个团的起始和终止坐标
需要遍历上一行所有的团,读取上一行每个团的起始和终止坐标
将二者信息比对进行重叠判断
第一个条件句实现递归内容的更新替换(上一行内容更新)
第二个条件句实现重复判定,并对团进行标号,生成等价对
三、大胆猜测小心求证每个变量的含义
strun代表团的起始坐标,enrun代表终止坐标,rowrun代表团对应的行数,currowidx代表当前判断行的行数,runlabels代表团的标记,equivalence存储等价对
void firstPass(vector<int>& stRun, vector<int>& enRun, vector<int>& rowRun, int NumberOfRuns,
vector<int>& runLabels, vector<pair<int, int>>& equivalences, int offset)
{
runLabels.assign(NumberOfRuns, 0);
int idxLabel = 1;
int curRowIdx = 0;
int firstRunOnCur = 0;
int firstRunOnPre = 0;
int lastRunOnPre = -1;
for (int i = 0; i < NumberOfRuns; i++)
{
if (rowRun[i] != curRowIdx)
{
curRowIdx = rowRun[i];
firstRunOnPre = firstRunOnCur;
lastRunOnPre = i - 1;
firstRunOnCur = i;
}
for (int j = firstRunOnPre; j <= lastRunOnPre; j++)
{
if (stRun[i] <= enRun[j] + offset && enRun[i] >= stRun[j] - offset && rowRun[i] == rowRun[j] + 1)
{
if (runLabels[i] == 0) // 没有被标号过
runLabels[i] = runLabels[j];
else if (runLabels[i] != runLabels[j])// 已经被标号
equivalences.push_back(make_pair(runLabels[i], runLabels[j])); // 保存等价对
}
}
if (runLabels[i] == 0) // 没有与前一列的任何run重合
{
runLabels[i] = idxLabel++;
}
}
}
第三段程序
一、对等价对的处理,需要将它转化为若干个等价序列
比如有如下等价对:
(1,2),(1,6),(3,7),(9-3),(8,1),(8,10),(11,5),(11,8),(11,12),(11,13),(11,14),(15,11)
我们需要得到最终序列是:
1-2-5-6-8-10-11-12-13-14-15
3-7-9
4
采用方法是图像深入优先遍历的原理,进行等价序列的查找。
不要着急读程序,先把方法对应的原理吃透,否则读程序会读得很痛苦,看似已经开始了,其实磕磕绊绊地读不下去,理解不了,也不要一个字一个字的读程序(就像英语阅读理解一样),要按照结构和功能读程序。
图像深入优先遍历原理
找到所有团V对应等价(有链接关系)的团W,以及V的相对于W的下一个邻接点。
参考文献:
图的遍历:深度优先遍历(DFS)_Uncertainty!!的博客-CSDN博客_深度优先遍历
二、根据深入优先遍历原理,寻找连通域的过程就需要进行这样几个嵌套的遍历过程。
首先需要遍历所有团(矩阵列自上到下);需要遍历与某个团等价的团(矩阵行自左到右);还需要遍历与这个等价团邻接的等价团(矩阵列自上到下)
分别对应i<maxlabel(团的序号),j<tempList(暂时的等价序列),k<eqTab[].size()=maxLabel
三、猜测每个变量的含义
maxLabel代表图数,eqTab代表等价对矩阵,vecPairIt代表循环器,作用是遍历整个eqTab
labelFlag代表团所属与的类,tempList代表暂时一类的等价序列,equaList代表最终的等价序列
void replaceSameLabel(vector<int>& runLabels, vector<pair<int, int>>&
equivalence)
{
int maxLabel = *max_element(runLabels.begin(), runLabels.end());
vector<vector<bool>> eqTab(maxLabel, vector<bool>(maxLabel, false));
vector<pair<int, int>>::iterator vecPairIt = equivalence.begin();
while (vecPairIt != equivalence.end())
{
eqTab[vecPairIt->first - 1][vecPairIt->second - 1] = true;
eqTab[vecPairIt->second - 1][vecPairIt->first - 1] = true;
vecPairIt++;
}
vector<int> labelFlag(maxLabel, 0);
vector<vector<int>> equaList;
vector<int> tempList;
cout << maxLabel << endl;
for (int i = 1; i <= maxLabel; i++)
{
if (labelFlag[i - 1])
{
continue;
}
labelFlag[i - 1] = equaList.size() + 1;
tempList.push_back(i);
for (vector<int>::size_type j = 0; j < tempList.size(); j++)
{
for (vector<bool>::size_type k = 0; k != eqTab[tempList[j] - 1].size(); k++)
{
if (eqTab[tempList[j] - 1][k] && !labelFlag[k])
{
tempList.push_back(k + 1);
labelFlag[k] = equaList.size() + 1;
}
}
}
equaList.push_back(tempList);
tempList.clear();
}
cout << equaList.size() << endl;
for (vector<int>::size_type i = 0; i != runLabels.size(); i++)
{
runLabels[i] = labelFlag[runLabels[i] - 1];
}
}
PS:程序理解的一些小问题记录
1、if(a);如果a为真就继续,这里的真包括非0数字和字符。
2、vector<bool>(maxLabel, false) std :: vector <bool> a(b,false); 在C#中-789找 (789zhao.com)
3、iterator vecPairIt C++迭代器iterator详解_C 语言_脚本之家 (jb51.net)
4、vecPairIt-> (1条消息) C中的->透析_范红康的博客-CSDN博客
5、eqTab[vecPairIt->first - 1][vecPairIt->second - 1](1条消息) c++ 里面的map容器的迭代器 first second_imik的博客-CSDN博客_map的first和second
6、 vector<vector<bool>> 用vector实现二维向量 - 百度文库 (baidu.com)
7、"&&"、"||"和"!"。a && b,一假必假,结合性从左至右。||是逻辑或运算符,a || b,一真必真,结合性从左至右,&&!是要&&左边成立且右边不成立才成立;
结束,为什么感觉这么小的程序就好难,5555,开始怀疑适不适合干图像算法,加油呀小猴!
最后感谢在解决问题过程中各位大佬的帮助,如果我的回答帮助到你,还麻烦给我点赞加油呀~~