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

一、前言

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

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

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

image

二、连通域

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

image        image

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

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

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

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

image

三、连通区域的标记

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

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

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

四、基于行程的标记

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

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

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

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

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

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

6,结束。

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

image

第一行,我们得到两个团:[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
  1. void bwLabelFunc(Mat bwFrame)  
  2. {  
  3.   int width = bwFrame.cols;  
  4.   int height= bwFrame.rows;  
  5.   vector<int> stRun, endRun, rowRun, runLabelInit;  
  6.   int totalRuns = 0;  
  7.   int lastLineStIdx = 0, lastLineEndIdx = 0;  
  8.   vector<pair<int,int>> equivalences;  
  9.   
  10.     
  11.   lastLineStIdx  = 0;  
  12.   lastLineEndIdx = 0;  
  13.   
  14.   for (int i=0; i<height; i++)  
  15.   {  
  16.     for (int j=0; j<width; j++)  
  17.     {  
  18.       uchar pelVal = bwFrame.at<uchar>(i,j);  
  19.       uchar pelValPre, pelValNext; // Get the Pre and Next Pixel Value.  
  20.       if (j==0) pelValPre = 255;  
  21.       else      pelValPre = bwFrame.at<uchar>(i,j-1);  
  22.   
  23.       if (j<width-1) pelValNext = bwFrame.at<uchar>(i,j+1);  
  24.       else           pelValNext = 255;  
  25.   
  26.       if (pelValPre==255 && pelVal==0) // start Valid.  
  27.       {  
  28.         stRun.push_back(j);  
  29.         rowRun.push_back(i);  
  30.       }  
  31.   
  32.       if (pelVal==0 && pelValNext==255) // End Valid.  
  33.       {  
  34.         endRun.push_back(j);  
  35.   
  36.         // Get Label.  
  37.         int curLabel = -1;  
  38.         for (int m=lastLineStIdx; m<lastLineEndIdx; m++) // Last Line Valid.   
  39.         {  
  40.           int startRunLastLine = stRun[m];  
  41.           int endRunLastLine   = endRun[m];  
  42.   
  43.           int startRunCur  = stRun[stRun.size()-1];  
  44.           int endRunCur    = endRun[stRun.size()-1];  
  45.             
  46.           if (startRunLastLine<=endRunCur+1 && endRunLastLine+1>=startRunCur) // Connection Label.  
  47.           {  
  48.             if (curLabel==-1)  
  49.             {  
  50.               curLabel = runLabelInit[m];  
  51.             }  
  52.             else // Set Connection Equal Label.  
  53.             {  
  54.               equivalences.push_back(make_pair(curLabel, runLabelInit[m]));  
  55.             }  
  56.           }  
  57.         }  
  58.         if (curLabel==-1)  
  59.         {  
  60.           curLabel = totalRuns;  
  61.           totalRuns++;  
  62.         }  
  63.   
  64.         runLabelInit.push_back(curLabel);  
  65.       }  
  66.     }  
  67.     lastLineStIdx  = lastLineEndIdx; // Update Last Line's Start/End Run.  
  68.     lastLineEndIdx = endRun.size();  
  69.   }  
  70.   
  71.   // DAG; Find the Same Connection Label.  
  72.   int maxLabel = *max_element(runLabelInit.begin(), runLabelInit.end());  
  73.   vector<vector<bool>> eqTab(totalRuns, vector<bool>(totalRuns, false)); // graph Init.  
  74.   // Construct Graph.  
  75.   vector<pair<intint>>::iterator vecPairIt = equivalences.begin();  
  76.   while (vecPairIt != equivalences.end())  
  77.   {  
  78.     eqTab[vecPairIt->first][vecPairIt->second] = true;  
  79.     eqTab[vecPairIt->second][vecPairIt->first] = true;  
  80.     vecPairIt++;  
  81.   }  
  82.   
  83.   vector<int> labelFlag(totalRuns, 0);  
  84.   vector<vector<int>> equaList;  
  85.   vector<int> tempList;  
  86.   // cout << maxLabel << endl;  
  87.   for (int i = 0; i <= maxLabel; i++)  
  88.   {  
  89.     if (labelFlag[i])  
  90.     {  
  91.       continue;  
  92.     }  
  93.     labelFlag[i] = equaList.size() + 1;  
  94.     tempList.push_back(i);  
  95.   
  96.     // BFS Search Algorithm.  
  97.     for (vector<int>::size_type j = 0; j < tempList.size(); j++)  
  98.     {  
  99.       for (vector<bool>::size_type k = 0; k != eqTab[tempList[j]].size(); k++)  
  100.       {  
  101.         if (eqTab[tempList[j]][k] && !labelFlag[k])  
  102.         {  
  103.           tempList.push_back(k);  
  104.           labelFlag[k] = equaList.size() + 1;  
  105.         }  
  106.       }  
  107.     }  
  108.     equaList.push_back(tempList);  
  109.     tempList.clear();  
  110.   }  
  111.   /*cout << equaList.size() << endl; 
  112.   for (vector<int>::size_type i = 0; i != runLabels.size(); i++) 
  113.   { 
  114.     runLabels[i] = labelFlag[runLabels[i] - 1]; 
  115.   }*/  
  116. }  

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值