给定0-1矩阵求连通域程序复现理解

给定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,开始怀疑适不适合干图像算法,加油呀小猴!

最后感谢在解决问题过程中各位大佬的帮助,如果我的回答帮助到你,还麻烦给我点赞加油呀~~ 

猜你喜欢

转载自blog.csdn.net/MOZHOUH/article/details/125009011