您的位置:首页 > 其它

【图像处理】二值图像连通域标记-基于行程的标记方法-计算目标中心位置方法1

2018-01-20 20:11 701 查看


一、前言

二值图像,顾名思义就是图像的亮度值只有两个状态:黑(0)和白(255)。二值图像在图像分析与识别中有着举足轻重的地位,因为其模式简单,对像素在空间上的关系有着极强的表现力。在实际应用中,很多图像的分析最终都转换为二值图像的分析,比如:医学图像分析、前景检测、字符识别,形状识别。二值化+数学形态学能解决很多计算机识别工程中目标提取的问题。

二值图像分析最重要的方法就是连通区域标记,它是所有二值图像分析的基础,它通过对二值图像中白色像素(目标)的标记,让每个单独的连通区域形成一个被标识的块,进一步的我们就可以获取这些块的轮廓、外接矩形、质心、不变矩等几何参数。

下面是一个二值图像被标记后,比较形象的显示效果,这就是我们这篇文章的目标。





二、连通域

在我们讨论连通区域标记的算法之前,我们先要明确什么是连通区域,怎样的像素邻接关系构成连通。在图像中,最小的单位是像素,每个像素周围有8个邻接像素,常见的邻接关系有2种:4邻接与8邻接。4邻接一共4个点,即上下左右,如下左图所示。8邻接的点一共有8个,包括了对角线位置的点,如下右图所示。



        



如果像素点A与B邻接,我们称A与B连通,于是我们不加证明的有如下的结论:

如果A与B连通,B与C连通,则A与C连通。

在视觉上看来,彼此连通的点形成了一个区域,而不连通的点形成了不同的区域。这样的一个所有的点彼此连通点构成的集合,我们称为一个连通区域。

下面这符图中,如果考虑4邻接,则有3个连通区域;如果考虑8邻接,则有2个连通区域。(注:图像是被放大的效果,图像正方形实际只有4个像素)。





三、连通区域的标记

连通区域标记算法有很多种,有的算法可以一次遍历图像完成标记,有的则需要2次或更多次遍历图像。这也就造成了不同的算法时间效率的差别,在这里我们介绍2种算法。

第一种算法是现在matlab中连通区域标记函数bwlabel中使的算法,它一次遍历图像,并记下每一行(或列)中连续的团(run)和标记的等价对,然后通过等价对对原来的图像进行重新标记,这个算法是目前我尝试的几个中效率最高的一个,但是算法里用到了稀疏矩阵与Dulmage-Mendelsohn分解算法用来消除等价对,这部分原理比较麻烦,所以本文里将不介绍这个分解算法,取而代这的用图的深度优先遍历来替换等价对。

第二种算法是现在开源库cvBlob中使用的标记算法,它通过定位连通区域的内外轮廓来标记整个图像,这个算法的核心是轮廓的搜索算法,这个我们将在文章中详细介绍。这个算法相比与第一种方法效率上要低一些,但是在连通区域个数在100以内时,两者几乎无差别,当连通区域个数到了103103数量级时,上面的算法会比该算法快10倍以上。


四、基于行程的标记

我们首先给出算法的描述,然后再结合实际图像来说明算法的步骤。

1,逐行扫描图像,我们把每一行中连续的白色像素组成一个序列称为一个团(run),并记下它的起点start、它的终点end以及它所在的行号。

2,对于除了第一行外的所有行里的团,如果它与前一行中的所有团都没有重合区域,则给它一个新的标号;如果它仅与上一行中一个团有重合区域,则将上一行的那个团的标号赋给它;如果它与上一行的2个以上的团有重叠区域,则给当前团赋一个相连团的最小标号,并将上一行的这几个团的标记写入等价对,说明它们属于一类。

3,将等价对转换为等价序列,每一个序列需要给一相同的标号,因为它们都是等价的。从1开始,给每个等价序列一个标号。

4,遍历开始团的标记,查找等价序列,给予它们新的标记。

5,将每个团的标号填入标记图像中。

6,结束。

我们来结合一个三行的图像说明,上面的这些操作。





第一行,我们得到两个团:[2,6]和[10,13],同时给它们标记1和2。

第二行,我们又得到两个团:[6,7]和[9,10],但是它们都和上一行的团有重叠区域,所以用上一行的团标记,即1和2。

第三行,两个:[2,4]和[7,8]。[2,4]这个团与上一行没有重叠的团,所以给它一个新的记号为3;而[2,4]这个团与上一行的两个团都有重叠,所以给它一个两者中最小的标号,即1,然后将(1,2)写入等价对。

全部图像遍历结束,我们得到了很多个团的起始坐标,终止坐标,它们所在的行以及它们的标号。同时我们还得到了一个等价对的列表。

按照此算法写代码如下:

[cpp] view
plain copy

void bwLabelFunc(Mat bwFrame)  

{  

  int width = bwFrame.cols;  

  int height= bwFrame.rows;  

  vector<int> stRun, endRun, rowRun, runLabelInit;  

  int totalRuns = 0;  

  int lastLineStIdx = 0, lastLineEndIdx = 0;  

  vector<pair<int,int>> equivalences;  

  

    

  lastLineStIdx  = 0;  

  lastLineEndIdx = 0;  

  

  for (int i=0; i<height; i++)  

  {  

    for (int j=0; j<width; j++)  

    {  

      uchar pelVal = bwFrame.at<uchar>(i,j);  

      uchar pelValPre, pelValNext; // Get the Pre and Next Pixel Value.  

      if (j==0) pelValPre = 255;  

      else      pelValPre = bwFrame.at<uchar>(i,j-1);  

  

      if (j<width-1) pelValNext = bwFrame.at<uchar>(i,j+1);  

      else           pelValNext = 255;  

  

      if (pelValPre==255 && pelVal==0) // start Valid.  

      {  

        stRun.push_back(j);  

        rowRun.push_back(i);  

      }  

  

      if (pelVal==0 && pelValNext==255) // End Valid.  

      {  

        endRun.push_back(j);  

  

        // Get Label.  

        int curLabel = -1;  

        for (int m=lastLineStIdx; m<lastLineEndIdx; m++) // Last Line Valid.   

        {  

          int startRunLastLine = stRun[m];  

          int endRunLastLine   = endRun[m];  

  

          int startRunCur  = stRun[stRun.size()-1];  

          int endRunCur    = endRun[stRun.size()-1];  

            

          if (startRunLastLine<=endRunCur+1 && endRunLastLine+1>=startRunCur) // Connection Label.  

          {  

            if (curLabel==-1)  

            {  

              curLabel = runLabelInit[m];  

            }  

            else // Set Connection Equal Label.  

            {  

              equivalences.push_back(make_pair(curLabel, runLabelInit[m]));  

            }  

          }  

        }  

        if (curLabel==-1)  

        {  

          curLabel = totalRuns;  

          totalRuns++;  

        }  

  

        runLabelInit.push_back(curLabel);  

      }  

    }  

    lastLineStIdx  = lastLineEndIdx; // Update Last Line's Start/End Run.  

    lastLineEndIdx = endRun.size();  

  }  

  

  // DAG; Find the Same Connection Label.  

  int maxLabel = *max_element(runLabelInit.begin(), runLabelInit.end());  

  vector<vector<bool>> eqTab(totalRuns, vector<bool>(totalRuns, false)); // graph Init.  

  // Construct Graph.  

  vector<pair<int, int>>::iterator vecPairIt = equivalences.begin();  

  while (vecPairIt != equivalences.end())  

  {  

    eqTab[vecPairIt->first][vecPairIt->second] = true;  

    eqTab[vecPairIt->second][vecPairIt->first] = true;  

    vecPairIt++;  

  }  

  

  vector<int> labelFlag(totalRuns, 0);  

  vector<vector<int>> equaList;  

  vector<int> tempList;  

  // cout << maxLabel << endl;  

  for (int i = 0; i <= maxLabel; i++)  

  {  

    if (labelFlag[i])  

    {  

      continue;  

    }  

    labelFlag[i] = equaList.size() + 1;  

    tempList.push_back(i);  

  

    // BFS Search Algorithm.  

    for (vector<int>::size_type j = 0; j < tempList.size(); j++)  

    {  

      for (vector<bool>::size_type k = 0; k != eqTab[tempList[j]].size(); k++)  

      {  

        if (eqTab[tempList[j]][k] && !labelFlag[k])  

        {  

          tempList.push_back(k);  

          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]; 

  }*/  

}  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: