兩種連通區域標記演算法
一、 One-Pass對應的標記演算法(Label.h)
使用:
unsigned char label = (unsigned char )fspace_2d(imgMask2.row,imgMask2.col,sizeof(unsigned char));
std::vector shapecenterpoint;
int ll = Label::CutAndLable(pTemp,label,imgMask2.row,imgMask2.col,shapecenterpoint);
512X512影象,耗時60ms
256X256影象,耗時20ms。
程式碼分析:
1. 輸入待標記影象bitmap,初始化一個與輸入影象同樣尺寸的標記矩陣labelmap,一個佇列queue以及標記計數labelIndex;
2. 按從左至右、從上至下的順序掃描bitmap,當掃描到一個未被標記的前景畫素p時,labelIndex加1,並在labelmap中標記p(相應點的值賦為labelIndex),同時,掃描p的八鄰域點,若存在未被標記的前景畫素,則在labelmap中進行標記,並放入queue中,作為區域生長的種子;
3. 當queue不為空時,從queue中取出一個生長種子點p1,掃描p1的八鄰域點,若存在未被標記過的前景畫素,則在labelmap中進行標記,並放入queue中;
4. 重複3直至queue為空,一個連通區標記完成;
5. 轉到2,直至整幅影象被掃描完畢,得到標記矩陣labelmap和連通區的個數labelIndex。
// Label.h: interface for the Label class.
//
//////////////////////////////////////////////////////////////////////
#if !defined(AFX_LABEL_H__BD38D587_97F3_4B51_9BB6_C0F044167FBA__INCLUDED_)
#define AFX_LABEL_H__BD38D587_97F3_4B51_9BB6_C0F044167FBA__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <vector>
#ifndef _POINT4D_
#define _POINT4D_
struct Point4D
{
int r; //目標點區域的質心位置
int c;
double dr; //目標點區域的質心位置的精確值
double dc;
int s; //目標點區域的大小
double g; //目標點區域中最大灰度值
Point4D& operator=(const Point4D &other)
{
if (&other==this)
return *this;
r = other.r;
c = other.c;
dr = other.dr;
dc = other.dc;
s = other.s;
g = other.g;
return *this;
};
};
#endif
class Label
{
public:
Label();
virtual ~Label();
static int CutAndLable(unsigned char **inImg, unsigned char **outImg,int nRow, int nCol, std::vector<Point4D>& points);
static void labelNeighborPoint(unsigned char **pImg,int nRow,int nCol,int i,int j,Point4D &pPStart);
};
#endif // !defined(AFX_LABEL_H__BD38D587_97F3_4B51_9BB6_C0F044167FBA__INCLUDED_)
// Label.cpp: implementation of the Label class.
//
//////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "Label.h"
#include "Comlib.h"
#include <vector>
using namespace std;
Label::Label()
{
}
Label::~Label()
{
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
/************************ label ************************************/
//種子填充標記
//
// 引數:
//1. inImg - 輸入影象
//2. outImg - 輸入影象
//2. nRow, nCol - 影象高和寬
//3. points - 標記點容器
//
// 返回值: - 標記點的個數
/*********************************************************************/
int Label::CutAndLable(unsigned char **inImg, unsigned char **outImg,int nRow, int nCol, std::vector<Point4D>& points)
{
for (int iii=0;iii<nRow;iii++)
for (int jjj=0;jjj<nCol;jjj++)
{
outImg[iii][jjj] = 0;
}
unsigned char **pImg = (unsigned char**)fspace_2d(nRow,nCol,sizeof(unsigned char));
for (int ii=0;ii<nRow;ii++)
for (int jj=0;jj<nRow;jj++)
{
pImg[ii][jj] = inImg[ii][jj];
}
points.clear();
Point4D temp;
int nBlock =1;
for (int i=0;i<nRow;i++)
for (int j=0;j<nCol;j++)
{
if (pImg[i][j] != 0)
{
labelNeighborPoint(pImg,nRow,nCol,i,j,temp);
points.push_back(temp);
//outImg[temp.r][temp.c] = (unsigned char)temp.g;
if (nBlock <=255)
{
outImg[temp.r][temp.c] = nBlock;
}
else
outImg[temp.r][temp.c] = 0;
nBlock++;
}
}
return points.size();
}
void Label::labelNeighborPoint(unsigned char **pImg,int nRow,int nCol,int i,int j,Point4D &pPStart)
{
static int ttt = 1;
int tMark = i * nCol +j;
int endMark = nRow * nCol;
int tTemp;
int maxR=-1, maxC=-1;
double sumR = 0, sumC = 0, sumG = 0, maxG = 0;
int nCount = 0;
int top=nRow,down=0,left=nCol,right=0;
vector<int> pBuffD;
pBuffD.push_back(tMark);
while( pBuffD.size()!=0 )
{
tMark = pBuffD.back();
pBuffD.pop_back();
++nCount;
sumR += tMark/nCol * pImg[tMark/nCol][tMark%nCol];//灰度加權
sumC += tMark%nCol * pImg[tMark/nCol][tMark%nCol];
sumG += pImg[tMark/nCol][tMark%nCol];
if (maxG < pImg[tMark/nCol][tMark%nCol])
{
maxR = tMark/nCol;
maxC = tMark%nCol;
maxG = pImg[tMark/nCol][tMark%nCol];
}
pImg[tMark/nCol][tMark%nCol] = 0;
if (tMark/nCol > down) down = tMark/nCol;
if (tMark%nCol > right) right = tMark%nCol;
if (tMark/nCol < top) top = tMark/nCol;
if (tMark%nCol < left) left = tMark%nCol;
tTemp = tMark-nCol-1; // (i-1,j-1)
if( tTemp>=0 && tTemp<endMark && pImg[tTemp/nCol][tTemp%nCol] != 0 )
pBuffD.push_back(tTemp);
tTemp = tMark-nCol ; // (i-1,j)
if( tTemp>=0 && tTemp<endMark && pImg[tTemp/nCol][tTemp%nCol] != 0 )
pBuffD.push_back(tTemp);
tTemp = tMark-nCol+1; // (i-1,j+1)
if( tTemp>=0 && tTemp<endMark && pImg[tTemp/nCol][tTemp%nCol] != 0 )
pBuffD.push_back(tTemp);
tTemp = tMark -1; // (i,j-1)
if( tTemp>=0 && tTemp<endMark && pImg[tTemp/nCol][tTemp%nCol] != 0 )
pBuffD.push_back(tTemp);
tTemp = tMark +1; // (i,j+1)
if( tTemp>=0 && tTemp<endMark && pImg[tTemp/nCol][tTemp%nCol] != 0 )
pBuffD.push_back(tTemp);
tTemp = tMark+nCol-1; // (i+1,j-1)
if( tTemp>=0 && tTemp<endMark && pImg[tTemp/nCol][tTemp%nCol] != 0 )
pBuffD.push_back(tTemp);
tTemp = tMark+nCol ; // (i+1,j)
if( tTemp>=0 && tTemp<endMark && pImg[tTemp/nCol][tTemp%nCol] != 0 )
pBuffD.push_back(tTemp);
tTemp = tMark+nCol+1; // (i+1,j+1)
if( tTemp>=0 && tTemp<endMark && pImg[tTemp/nCol][tTemp%nCol] != 0 )
pBuffD.push_back(tTemp);
}
if( nCount!=0 )
{
int w = right - left;
int h = down - top;
pPStart.s = w>h ? w+1 : h+1;
pPStart.c = sumC/sumG+0.5;
pPStart.r = sumR/sumG+0.5;
pPStart.dc = nCount;
pPStart.dr = nCount;
// pPStart.c = maxC;
// pPStart.r = maxR;
pPStart.g = ttt;
ttt ++;
}
}
二、 Two_Pass演算法
使用:
// 二值影象標記演算法並獲得聯通區域資訊
int LabelBiImgAndMsg(unsigned char *inBiImg,unsigned char *outLabelImg, int *numCandTarget,
int row, int col, std::vector& points)
其中:
ifndef POINT4D
define POINT4D
struct Point4D
{
int r; //目標點區域的質心位置
int c;
double dr; //目標點區域的質心位置的精確值
double dc;
int s; //目標點區域的大小
double g; //目標點區域中最大灰度值
Point4D& operator=(const Point4D &other)
{
if (&other==this)
return *this;
r = other.r;
c = other.c;
dr = other.dr;
dc = other.dc;
s = other.s;
g = other.g;
return *this;
};
};
endif
原始影象 標記影象
512X512影象,耗時10ms
256X256影象,耗時4ms。
程式碼分析:
1. 生成鄰接表:當前行與相鄰的上一行進行鄰接標記;
2. 由鄰接表生成影象對映表,合併鄰接表生成連通區域;
3. 有對映表生成標記影象,並獲取標記資訊。
#include "StdAfx.h"
#include "subfuction.h"
#include "dibapi.h"
#include "QFileIO.h"
#include "method.h"
//////////////////////////////////////////////////////////////////////////
//lable 2
//標記引數
// 生成鄰接表
int GenerateNeighborTable(unsigned char *inBiImg, unsigned char outNbTab[2 * MAXLINK]
, int *outNbLen, unsigned char *outLabelImg
, int outRegionArea[MAXSUBBLOCK + 1], int *outLabelNum, int row, int col)
{
// 對二值影象亮畫素區域進行初掃描,初步標記,獲取初步標記區域之間的鄰接表
// 返回值,非0 表成功,0 表失敗
// 輸入:二值影象及其行列數
// 輸出:鄰接表outNbTab,它的每一列,第一行和第二行存放連通的兩個區域的初步標記號
// outNbLen,鄰接表的有效長度
// outLabelImg,初步標記影象
// 區域面積陣列outRegionArea,第i個元素表示初步標記號為i的區域的面積,第0個元素無效
// outLabelNum,初步標記的區域數,也是outRegionArea的有效長度
int startPos, endPos, grayNum;
int i, j, k;
static int grayLastLine[MAXSINGLELINK]; // 上一行與當前區域相鄰的區域
for (i = 0; i < MAXSINGLELINK; i++)
{
grayLastLine[i] = 0;
}
// 初步標記影象初始化 ***可以省略***
/*
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
outLabelImg[i*col + j] = 0;
}
}
*/
// 鄰接表初始化
for (i = 0; i < 2 * MAXLINK; i++)
{
outNbTab[i] = 0;
}
// 區域面積初始化
for (i = 0; i < MAXSUBBLOCK; i++)
{
outRegionArea[i] = 0;
}
*outLabelNum = 0; // 初步標記號
*outNbLen = 0; // 鄰接表有效長度
i=0, j=0;
for (i=0; i<row; i++)
{
for (j=0; j<col; j++)
{
if (inBiImg[i*col+j]!=0)
{
// 當前區域,為處於當前行的一線段
startPos = j;
do
{
j++;
if (j==col) break;
} while (inBiImg[i*col+j]!=0); //查詢該行第一個不為0的畫素位置
endPos = j-1;
grayNum = 0;
for (k=startPos-1; k<=endPos+1; k++)
{
if (i-1<0) break; // 影象首行無需察看其上一行
if ((k<0)||(k>=col)) continue;
if (outLabelImg[(i-1)*col+k] != 0) // 檢視上一行與當前區域的相鄰區域
{
if (grayNum>=MAXSINGLELINK)
{
*outNbLen = 0;
*outLabelNum = 0;
return 0;
}
grayLastLine[grayNum] = outLabelImg[(i-1)*col+k];
grayNum++;
while (outLabelImg[(i-1)*col+k]!=0) k++;
}
}
if (grayNum == 0) // 當前區域的上一行不存在其它區域與之相鄰
{
(*outLabelNum)++; // 初步標記號遞增
if (*outLabelNum > MAXSUBBLOCK)
{
*outNbLen = 0;
*outLabelNum = 0;
return 0;
}
if (*outNbLen>=MAXLINK)
{
*outNbLen = 0;
*outLabelNum = 0;
return 0;
}
// 鄰接表中增加新區域對應的項
outNbTab[*outNbLen] = *outLabelNum;
outNbTab[*outNbLen+MAXLINK] = *outLabelNum;
(*outNbLen)++;
// 區域面積陣列中,增加新區域對應的項
outRegionArea[*outLabelNum] = endPos - startPos + 1;
for (k=startPos; k<=endPos; k++)
outLabelImg[i*col+k] = *outLabelNum;
}
else
{
// 當前區域的標記號,與上一行與之相鄰的第一個標記號相同
for (k=startPos; k<=endPos; k++)
outLabelImg[i*col+k] = grayLastLine[0];
outRegionArea[grayLastLine[0]] += endPos - startPos + 1;
// 當前區域和上一行與之相鄰的標記號建立鄰接關係
for (k=1; k<grayNum; k++)
{
if (*outNbLen>=MAXLINK)
{
*outNbLen = 0;
*outLabelNum = 0;
return 0;
}
outNbTab[*outNbLen] = grayLastLine[0];
outNbTab[*outNbLen+MAXLINK] = grayLastLine[k];
(*outNbLen)++;
}
}
}
}
}
return 1;
}
// 生成對映表
void GenerateMap(unsigned char inNbTab[2 * MAXLINK], int nbTabLen
, unsigned char outMap[MAXSUBBLOCK+1], int *outNumTrueObj)
{
// 根據鄰接表生成初步標記號與其真實標記號的對映關係
// 輸入:鄰接表inNbTab 及其有效長度nbTabLen
// 輸出:對映表outMap,第i個元素表示初步標記號為i的區域的真實標記號
// outNumTrueObj,真實目標數,也是真實標記號的最大值
static unsigned char stack[MAXSINGLELINK];
int i, j, StackPtr, TmpValue;
unsigned char *pneighbor0,*pneighbor1,*pneighbor2,*pneighbor3;
for (i = 0; i < MAXSINGLELINK; i++)
{
stack[i] = 0;
}
*outNumTrueObj = 0;
StackPtr = 0;
pneighbor0 = &(inNbTab[0]);
pneighbor1 = &(inNbTab[MAXLINK]);
for (i=0; i<nbTabLen; i++,pneighbor0++,pneighbor1++)
{
if (*pneighbor0!=0) // 鄰接表中新的一項
{
stack[StackPtr] = *pneighbor0;
StackPtr++;
(*outNumTrueObj)++;
*pneighbor0 = 0;
*pneighbor1 = 0;
}
while (StackPtr != 0)
{
// 當前存在於棧中的所有區域,其真實標記號相同
StackPtr--;
TmpValue = stack[StackPtr];
// 初步標記號為TmpValue的區域的真實標記號
outMap[TmpValue] = (*outNumTrueObj);
pneighbor2 = pneighbor0;
pneighbor3 = pneighbor1;
for (j=i; j<nbTabLen; j++, pneighbor2++, pneighbor3++)
{
if ((*pneighbor2 == TmpValue) || (*pneighbor3 == TmpValue))
{
if (*pneighbor2!=*pneighbor3)
{
// 與TmpValue相鄰的其它區域其真實標記號與TmpValue相同,故進棧
stack[StackPtr] = (*pneighbor2!=TmpValue)? *pneighbor2
: *pneighbor3;
StackPtr++;
}
*pneighbor2 = 0;
*pneighbor3 = 0;
}
}
}
}
}
void Map2LabelImg(unsigned char *inoutLabelImg,unsigned char inMap[MAXSUBBLOCK], int row ,int col)
{
// 由對映表生成標記影象
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (inoutLabelImg[i* col + j] > 0)
{
inoutLabelImg[i* col + j] = 255-inMap[inoutLabelImg[i * col + j]];
}
}
}
}
// 二值影象標記演算法
int LabelBiImg(unsigned char *inBiImg,unsigned char *outLabelImg, int *numCandTarget, int row, int col)
{
// 對二值影象進行標記
// 輸出標記後圖像和連通區域數
static unsigned char map[MAXSUBBLOCK+1], neighbor[2*MAXLINK];
static int tmpArea[MAXSUBBLOCK+1];
int tabLen, labelNum; // 鄰接表長度與初步標記區域數
int i;
for (i = 0; i < MAXSUBBLOCK + 1; i++) // 對映表與面積陣列初始化
{
map[i] = 0;
tmpArea[i] = 0;
}
for (i = 0; i < 2 * MAXLINK; i++) // 鄰接表初始化
{
neighbor[i] = 0;
}
for (i = 0; i < row * col; i++) // 標記影象初始化
{
outLabelImg[i] = 0;
}
// 生成鄰接表
i = GenerateNeighborTable(inBiImg,neighbor, &tabLen
, outLabelImg, tmpArea, &labelNum, row, col);
if (i != 0)
{
// 由鄰接表生成對映表
GenerateMap(neighbor, tabLen, map, numCandTarget);
// 由對映表生成標記後圖像
Map2LabelImg(outLabelImg, map, row, col);
if (i == 255)
{
return 255;
}
return 1;
}
else
{
for (i = 0; i < row * col; i++)
{
outLabelImg[i] = 0;
}
*numCandTarget = 0;
return 0;
}
}
void GetImgConnectMsg(unsigned char * LabelImg, int row, int col, int numCandTarget, std::vector<Point4D>& points)
{
if (!points.empty())
{
points.clear();
}
int i,j,nowNum;
Point4D temp;
temp.r = 0;
temp.c = 0;
temp.s = 0;
for (i=0;i<numCandTarget;i++)
{
points.push_back(temp);
}
for (i=0;i<row;i++)
{
for (j=0;j<col;j++)
{
nowNum = LabelImg[i*col + j]-1;
if (nowNum>=0)
{
points[nowNum].r = points[nowNum].r + i;
points[nowNum].c = points[nowNum].c + j;
points[nowNum].s = points[nowNum].s + 1;
}
}
}
for (i=0;i<numCandTarget;i++)
{
points[i].r = points[i].r / points[i].s;
points[i].c = points[i].c / points[i].s;
}
}
// 二值影象標記演算法並獲得聯通區域資訊
int LabelBiImgAndMsg(unsigned char *inBiImg,unsigned char *outLabelImg, int *numCandTarget,
int row, int col, std::vector<Point4D>& points)
{
// 對二值影象進行標記
// 輸出標記後圖像和連通區域數
static unsigned char map[MAXSUBBLOCK+1], neighbor[2*MAXLINK];
static int tmpArea[MAXSUBBLOCK+1];
int tabLen, labelNum; // 鄰接表長度與初步標記區域數
int i;
for (i = 0; i < MAXSUBBLOCK + 1; i++) // 對映表與面積陣列初始化
{
map[i] = 0;
tmpArea[i] = 0;
}
for (i = 0; i < 2 * MAXLINK; i++) // 鄰接表初始化
{
neighbor[i] = 0;
}
for (i = 0; i < row * col; i++) // 標記影象初始化
{
outLabelImg[i] = 0;
}
// 生成鄰接表
i = GenerateNeighborTable(inBiImg,neighbor, &tabLen
, outLabelImg, tmpArea, &labelNum, row, col);
if (i != 0)
{
// 由鄰接表生成對映表
GenerateMap(neighbor, tabLen, map, numCandTarget);
//由標記的影象獲取標記資訊
GetImgConnectMsg(outLabelImg, row, col, *numCandTarget, points);
// 由對映表生成標記後圖像
Map2LabelImg(outLabelImg, map, row, col);
if (i == 255)
{
return 255;
}
return 1;
}
else
{
for (i = 0; i < row * col; i++)
{
outLabelImg[i] = 0;
}
*numCandTarget = 0;
return 0;
}
}
//////////////////////////////////////////////////////////////////////////
三、 DSP上處理:
採用Two_Pass演算法
耗時:1.8ms