二值图像连通域标记算法
八邻域标记算法:
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个
时间: 2024-10-07 21:16:56