二值图像连通域标记算法
八邻域标记算法:
1) 判断此点八邻域中的最左,左上,最上,上右点的情况。如果都没有点,则表示一个新的区域的开始。
2) 如果此点八邻域中的最左有点,上右都有点,则标记此点为这两个中的最小的标记点,并修改大标记为小标记。
3) 如果此点八邻域中的左上有点,上右都有点,则标记此点为这两个中的最小的标记点,并修改大标记为小标记。
4) 否则按照最左,左上,最上,上右的顺序,标记此点为四个中的一个。
BOOL CImageColorProcess::ConnectedLabelTwoPass(LPBYTE lpSrc, LPBYTE lpDst, int nSrcCount, int nW, int nH)
{
if (nSrcCount != 24)
{
AfxMessageBox("非rgb图像,不处理!");
return false;
}
LPBYTE m_lpImgBitsMove;
int * m_lpnMark; //标记数组指针
int * m_lpnMarkMove; //标记数组移动指针
int m_nMarkNumbers; //标记的区域个数
m_lpnMark = NULL;
if (m_lpnMark == NULL)
{
m_lpnMark = new int[nW*nH];
ASSERT(m_lpnMark != NULL);
m_lpnMarkMove = m_lpnMark;
}
::memset((LPBYTE)m_lpnMark, 0, nW*nH * 4);
int nMarkValue = 1;//每次标识的值,nMarkValue会在后边递增,来表示不同的区域,从1开始标记。
int nMaxMarkValue = 0; //记录最大的标识的值
int i, j; //循环控制变量
/* 定义存放等价对的链表,其元素是 EqualMark类型,
定义list是为了节约存储空间。要使用Clist,
应该#include <Afxtempl.h>。 */
CList < EqualMark, EqualMark > lEqualMark;
//初始化图像移动指针
m_lpImgBitsMove = lpDst;
/*进行第一次扫描,将所得的等价对(EqualMark类型)加到lEqualMark链表中。
使用nMarkValue来进行每一次新的标记,标记之后将其值加1。
由于版面关系,这部分代码也同样略去不写。作者提出以下几点编程时要注意
的地方。
Note1:图像的四周像素并不会有8个相邻的像素。这时就要根据上、下、左、
右四种不同的情况做不同的寻找等价对的判断。
Note2:可以先对等价对进行排序,每次都保证MarkValue1<MarkValue2,
这样易于管理等价对。
Note3:在实际工作中,连续寻找出的等价对很容易重复,将本次找出的等价对
和链表中保存的最后一个等价对相比较,如果不相等的话再存入等价对链表,
这样可以大大降低链表中等价对的重复。
Note4:第一次扫描之后,nMarkValue-1即为nMaxMarkValue。 */
/************************************************************************/
//下面为补充代码,完成对图象的第一次扫描
//初始化图象数组和标识数组的指针
int nEqualNum = 0;
EqualMark tempEqualMark; //用以暂时存放每次找到的等价关系
m_lpnMarkMove = m_lpnMark;
//m_lpImgBitsMove = m_lpImgBits;
int bObjectGray = 0;
//标记图象的第一行、第一列的象素(只有这一个象素)
if (*m_lpImgBitsMove == bObjectGray)
{
*m_lpnMarkMove = nMarkValue++;
}
m_lpnMarkMove++;
m_lpImgBitsMove++;
//标记图象的第一行,此时不会出现等价的情况
for (i = 1; i <= nW; i++)
{
//需要标记的情况
if (*m_lpImgBitsMove == bObjectGray)
{
//前面没有被标记过,则开始一个新的标记
if (*(m_lpnMarkMove - 1) == 0)
{
*m_lpnMarkMove = nMarkValue++;
}
//前面被标记过,则跟随前一个标记
else
{
*m_lpnMarkMove = *(m_lpnMarkMove - 1);
}
}
m_lpnMarkMove++;
m_lpImgBitsMove++;
}
//除第一行之外的标记,此时会出现等价的关系
for (j = 1; j <= nH; j++)
{
m_lpImgBitsMove = lpDst + j*nW;
m_lpnMarkMove = m_lpnMark + j*nW;
//对每行的第一个点做处理,总体就是对图象的最左列做处理
//只需要检视上,右上两个点
if (*m_lpImgBitsMove == bObjectGray)
{
//<上>位置被标记过
if (*(m_lpnMarkMove - nW) != 0)
{
//跟随<上>标记
*m_lpnMarkMove = *(m_lpnMarkMove - nW);
if (*(m_lpnMarkMove - nW) != *(m_lpnMarkMove - nW + 1) && *(m_lpnMarkMove - nW + 1) != 0)
{
//<上><右上>等价标记
AttachEqualMark(tempEqualMark, *(m_lpnMarkMove - nW),
*(m_lpnMarkMove - nW + 1), nEqualNum, lEqualMark);
}
}
//<上>没有标记,此时一定不会存在等价关系
else
{
if (*(m_lpnMarkMove - nW + 1) != 0)
{
*m_lpnMarkMove = *(m_lpnMarkMove - nW + 1); //跟随<右上>标记
}
//<上>、<右上>都没有标记,则开始新的标记
else
{
*m_lpnMarkMove = nMarkValue++;
}
}
}
m_lpnMarkMove++;
m_lpImgBitsMove++;
//对每行的中间点做标记处理,此时存在<左>、<左上>、<上>、<右上> 4种情况
for (i = 1; i <= nW - 1; i++)
{
//需要标记
if ((*m_lpImgBitsMove) == bObjectGray)
{
//<左>被标记过
if (*(m_lpnMarkMove - 1) != 0)
{
*m_lpnMarkMove = *(m_lpnMarkMove - 1); //跟随<左>
if (*(m_lpnMarkMove - 1) != *(m_lpnMarkMove - nW - 1) && *(m_lpnMarkMove - nW - 1) != 0)
{
//标记<左>、<左上>等价
AttachEqualMark(tempEqualMark, *(m_lpnMarkMove - 1),
*(m_lpnMarkMove - nW - 1), nEqualNum, lEqualMark);
}
if (*(m_lpnMarkMove - 1) != *(m_lpnMarkMove - nW) && *(m_lpnMarkMove - nW) != 0)
{
//标记<左>、<上>等价
AttachEqualMark(tempEqualMark, *(m_lpnMarkMove - 1),
*(m_lpnMarkMove - nW), nEqualNum, lEqualMark);
}
if (*(m_lpnMarkMove - 1) != *(m_lpnMarkMove - nW + 1) && *(m_lpnMarkMove - nW + 1) != 0)
{
//标记<左>、<右上>等价
AttachEqualMark(tempEqualMark, *(m_lpnMarkMove - 1),
*(m_lpnMarkMove - nW + 1), nEqualNum, lEqualMark);
}
}
//<左>未被标记过
else
{
//<左上>被标记过
if (*(m_lpnMarkMove - nW - 1) != 0)
{
*m_lpnMarkMove = *(m_lpnMarkMove - nW - 1);
if (*(m_lpnMarkMove - nW - 1) != *(m_lpnMarkMove - nW) && *(m_lpnMarkMove - nW) != 0)
{
//标记<左上>、<上>等价
AttachEqualMark(tempEqualMark, *(m_lpnMarkMove - nW - 1),
*(m_lpnMarkMove - nW), nEqualNum, lEqualMark);
}
if (*(m_lpnMarkMove - nW - 1) != *(m_lpnMarkMove - nW + 1) && *(m_lpnMarkMove - nW + 1) != 0)
{
//标记<左上>、<右上>等价
AttachEqualMark(tempEqualMark, *(m_lpnMarkMove - nW - 1),
*(m_lpnMarkMove - nW + 1), nEqualNum, lEqualMark);
}
}
//<左>、<左上>未标记过
else
{
if (*(m_lpnMarkMove - nW) != 0)
{
*m_lpnMarkMove = *(m_lpnMarkMove - nW); //跟随<上>标记
if (*(m_lpnMarkMove - nW) != *(m_lpnMarkMove - nW + 1) && *(m_lpnMarkMove - nW + 1) != 0)
{
//标记<上>和<右上>等价
AttachEqualMark(tempEqualMark, *(m_lpnMarkMove - nW),
*(m_lpnMarkMove - nW + 1), nEqualNum, lEqualMark);
}
}
//<左>、<左上>、<上>未标记过,此时不存在等价关系
else
{
if (*(m_lpnMarkMove - nW + 1) != 0)
{
*m_lpnMarkMove = *(m_lpnMarkMove - nW + 1); //跟随<右上>标记
}
//<左>、<左上>、<上>、<右上>未标记过,则开始新的标记值
else
{
*m_lpnMarkMove = nMarkValue++;
}
} //<左>、<左上>、<上>未标记过结束
} //<左>、<左上>未标记过结束
} //<左>未被标记过结束
} // else 不需要标记
m_lpnMarkMove++;
m_lpImgBitsMove++;
} //中间点处理的结束
//对每行的最后一个点做处理,总体就是对图象的最左列做处理
//此时存在<左>、<左上>、<上> 3种情况
//需要标记
if ((*m_lpImgBitsMove) == bObjectGray)
{
//<左>被标记过
if (*(m_lpnMarkMove - 1) != 0)
{
*m_lpnMarkMove = *(m_lpnMarkMove - 1);
if (*(m_lpnMarkMove - 1) != *(m_lpnMarkMove - nW - 1) && *(m_lpnMarkMove - nW - 1) != 0)
{
//标记<左>、<左上>等价
AttachEqualMark(tempEqualMark, *(m_lpnMarkMove - 1),
*(m_lpnMarkMove - nW - 1), nEqualNum, lEqualMark);
}
if (*(m_lpnMarkMove - 1) != *(m_lpnMarkMove - nW) && *(m_lpnMarkMove - nW) != 0)
{
//标记<左>、<上>等价
AttachEqualMark(tempEqualMark, *(m_lpnMarkMove - 1),
*(m_lpnMarkMove - nW), nEqualNum, lEqualMark);
}
}
//<左>未被标记过
else
{
if (*(m_lpnMarkMove - nW - 1) != 0)
{
*m_lpnMarkMove = *(m_lpnMarkMove - nW - 1); //跟随<左上>
if (*(m_lpnMarkMove - nW - 1) != *(m_lpnMarkMove - nW) && *(m_lpnMarkMove - nW) != 0)
{
//标记<左上>、<上>等价
AttachEqualMark(tempEqualMark, *(m_lpnMarkMove - nW - 1),
*(m_lpnMarkMove - nW), nEqualNum, lEqualMark);
}
}
//<左>、<左上>未标记过
else
{
if (*(m_lpnMarkMove - nW) != 0)
{
*m_lpnMarkMove = *(m_lpnMarkMove - nW); //跟随<上>标记
}
//<左>、<左上>、<上>未标记过,则开始新的标记值
else
{
*m_lpnMarkMove = nMarkValue++;
}
}
}
} //对每行的最后一个点做处理,总体就是对图象的最左列做处理
} //"除第一行之外的标记"的结束
//因为在每次标记完之后,nMarkValue都会自动++
//所以要通过(-1)操作来记录所标记的最大的个数
nMaxMarkValue = nMarkValue - 1;
/************************************************************************/
/* 定义双层链表的外层链表,它的元素是一个指向内层链表的指针。
内层链表的型别也是CptrList,其元素是标记值。 */
CPtrList exList;
CPtrList * pInnerList;
POSITION posExElem;
if (lEqualMark.GetCount() != 0)
{
// pInnerListAdd,每次向exList中添加的新元素
CPtrList * pInnerListAdd = new CPtrList;
ASSERT(pInnerListAdd != NULL);
/* 添加第一个等价对到exList的第一个元素所指向的InnerList中。 */
pInnerListAdd->AddTail((void *)lEqualMark.GetHead().MarkValue1);
pInnerListAdd->AddTail((void *)lEqualMark.GetHead().MarkValue2);
exList.AddTail((void *)pInnerListAdd);
lEqualMark.RemoveHead();
/* 定义pFindValue1和pFindValue2, 存放在所有内层链表中找到特定值
的某个内层链表的头指针,也就是外层链表的某个元素值。 */
CPtrList * pFindValue1 = NULL;
CPtrList * pFindValue2 = NULL;
//整理剩余的等价对
while (!lEqualMark.IsEmpty())
{
posExElem = exList.GetHeadPosition();
pFindValue1 = NULL;
pFindValue2 = NULL;
while (posExElem)
{
pInnerList = (CPtrList *)exList.GetAt(posExElem);
if (pInnerList->Find((void *)lEqualMark.GetHead().MarkValue1))
{
pFindValue1 = pInnerList;
}
if (pInnerList->Find((void *)lEqualMark.GetHead().MarkValue2))
{
pFindValue2 = pInnerList;
}
exList.GetNext(posExElem);
}
//该等价对中两个值都在已经整理过的等价关系中
if (pFindValue1 && pFindValue2)
{
//当两个地址不一样时,对链表进行调整
if (pFindValue1 != pFindValue2)
{
pFindValue1->AddTail(pFindValue2);
/* 清除链表元素,通过new得到的CptrList 类型,
必须采用delete进行删除,否则会造成内存泄露。*/
POSITION posDelete = exList.Find((void *)pFindValue2);
pFindValue2->RemoveAll();
delete pFindValue2;
exList.RemoveAt(posDelete);
}
}
/* 只在已经整理过的等价关系中找到Value1,
那么将Vaule2加到Value1所在的链表中。 */
else if (pFindValue1)
{
pFindValue1->AddTail((void *)lEqualMark.GetHead().MarkValue2);
}
else if (pFindValue2)
{
pFindValue2->AddTail((void *)lEqualMark.GetHead().MarkValue1);
}
/* 等价对中两个值在整理过的等价关系中都
没有找到,则在exList中增加新元素。 */
else
{
CPtrList * pInnerListAdd = new CPtrList;
pInnerListAdd->AddTail((void *)lEqualMark.GetHead().MarkValue1);
pInnerListAdd->AddTail((void *)lEqualMark.GetHead().MarkValue2);
exList.AddTail((void *)pInnerListAdd);
}
//去掉此时等价对的头元素
lEqualMark.RemoveHead();
} // while ( !lEqualMark.IsEmpty() )循环结束
} // if ( lEqualMark.GetCount() !=0 )语句结束
/* 等价对链表大小为0,说明第一次扫描之后没有产生等价对,标记已经完成。 */
else
{
return TRUE;
}
/*等价关系整理完成,下面建立第一次扫描的标记值和
第二次扫描的标记值之间的映射关系。*/
int nTotalEqualNum = 0; //列入等价关系的标记个数
int nMarkRegion = 0; //图像中连通区域个数
posExElem = exList.GetHeadPosition();
while (posExElem)
{
pInnerList = (CPtrList *)exList.GetAt(posExElem);
nTotalEqualNum += pInnerList->GetCount();
exList.GetNext(posExElem);
}
nMarkRegion = nMaxMarkValue - nTotalEqualNum + exList.GetCount();
/* 定义第一次扫描和第二次扫描之间的映射向量,要使用vector,
应该#include <vector>并且使用std命名空间。 */
vector<MarkMapping> vMarkMap(nMaxMarkValue);
//初始化映射向量,令其做自身映射
for (i = 0; i < nMaxMarkValue; i++)
{
vMarkMap[i].nOriginalMark = i + 1;
vMarkMap[i].nMappingMark = i + 1;
}
POSITION posInnerElem; //InnerList中元素的位置
int nMin; //InnerList中最小值
int nIndex = 0;
posExElem = exList.GetHeadPosition();
/* while循环实现了如下功能:找到每个等价组中最小的标记值,
然后将映射向量中nMappingMark设定为其所在等价组的最小的标记值。*/
while (posExElem)
{
pInnerList = (CPtrList *)exList.GetAt(posExElem);
nMin = (int)pInnerList->GetHead();
posInnerElem = pInnerList->GetHeadPosition();
pInnerList->GetNext(posInnerElem);
while (posInnerElem)
{
if ((int)pInnerList->GetAt(posInnerElem) < nMin)
{
nMin = (int)pInnerList->GetAt(posInnerElem);
}
pInnerList->GetNext(posInnerElem);
}
/* 根据每组等价关系中的最小的标记值对Mapping向量做出调整。 */
posInnerElem = pInnerList->GetHeadPosition();
while (posInnerElem)
{
nIndex = (int)pInnerList->GetAt(posInnerElem) - 1;
vMarkMap[nIndex].nMappingMark = nMin;
pInnerList->GetNext(posInnerElem);
}
exList.GetNext(posExElem);
}
/* 将映射向量nMappingMark中不重复的部分找出并对其进行排序。
使用find()和sort()这两种泛型算法,应该#include <algorithm>。*/
vector <int> vSortMark(nMarkRegion); //排序向量
nIndex = 0;
for (i = 0; i < nMaxMarkValue; i++)
{
if (find(vSortMark.begin(), vSortMark.end(), vMarkMap[i].nMappingMark)
== vSortMark.end())
{
vSortMark[nIndex++] = vMarkMap[i].nMappingMark;
}
}
sort(vSortMark.begin(), vSortMark.end());
/* 根据排序后的标记在vSortMark向量中的位置,对映射向量做出重新调整。 */
vector<int>::iterator itFind;
vector<int>::iterator itBegin;
itBegin = vSortMark.begin();
for (i = 0; i < nMaxMarkValue; i++)
{
itFind = find(vSortMark.begin(), vSortMark.end(), vMarkMap[i].nMappingMark);
vMarkMap[i].nMappingMark = (itFind - itBegin + 1);
}
//根据映射向量对标记数组进行调整
for (j = 0; j < nH; j++)
{
m_lpnMarkMove = m_lpnMark + j*nW;
for (i = 0; i < nW; i++)
{
if (*m_lpnMarkMove != 0)
{
*m_lpnMarkMove = vMarkMap[*m_lpnMarkMove - 1].nMappingMark;
}
m_lpnMarkMove++;
}
}
//删除链表结构中通过new得到的元素
posExElem = exList.GetHeadPosition();
while (posExElem)
{
pInnerList = (CPtrList *)exList.GetAt(posExElem);
pInnerList->RemoveAll();
delete pInnerList;
exList.GetNext(posExElem);
}
exList.RemoveAll();
//通过类成员变量来记录连通区域的个数
m_nMarkNumbers = nMarkRegion;
CString s;
s.Format("连通区域个数为%d\n", nMarkRegion);
AfxMessageBox(s);
return (long)TRUE;
}
成功识别为6个
def find_connected_regions(A):
'''
A=[[0,0,0,1,1,0,1],
[1,1,1,1,0,0,0],
[0,0,0,0,1,1,0],
[0,0,1,1,1,0,0],
[0,0,0,0,0,0,1],]
'''
num=0
h,w=A.shape
visited=np.zeros(A.shape)
for i in range(h):
for j in range(w):
if A[i][j]==1 and visited[i][j]==0:
visited[i][j]=1
num+=1
q=[(i,j),]
while not len(q)==0:
ii,jj=q[-1]
q=q[:len(q)-1]
if ii-1>=0 and A[ii-1][jj]==1 and visited[ii-1][jj]==0: #up
visited[ii-1][jj]=1
q.append((ii-1,jj))
if jj-1>=0 and A[ii][jj-1]==1 and visited[ii][jj-1]==0: #left
visited[ii][jj-1]=1
q.append((ii,jj-1))
if jj+1<w and A[ii][jj+1]==1 and visited[ii][jj+1]==0: #right
visited[ii][jj+1]=1
q.append((ii,jj+1))
if ii+1<h and A[ii+1][jj]==1 and visited[ii+1][jj]==0: #down
visited[ii+1][jj]=1
q.append((ii+1,jj))
return num
OpenCV_连通区域分析(Connected Component Analysis-Labeling)
【Python】使用skimage完成二值图像连通区域标记及属性提取