1. 程式人生 > >兩種連通區域標記演算法

兩種連通區域標記演算法

一、 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