最近一直在研究二值图像的连通体标记方法,发现<<一种二值图像连通区域标记的新方法>>一篇文章非常好,他的作者是陈柏生老先生(华侨大学计算机科学系,福建)

可以百度到这篇文章,根据他的文章,写了下面的代码,经测试是可以使用的。

//
//
///
// 说明: 本代码的理论依据是<<一种二值图像连通区域标记的新方法>>, 作者: 陈柏生, 华侨大学计算机科学系,福建泉
// 直接可以百度出这篇论文的, 经试验是可以使用的
//
///
//


#include "stdafx.h"
#include <assert.h>
#include <vector>


using namespace std;


#define  BASEVALUE  16


// 用于连通体标记数据
typedef struct 
{
    // 标记点的横向坐标
    long x;
    // 标记点的纵向坐标
    long y;
} ZKPOINT;


///
// 函数功能: 判断种子点的四连通区域是否满足连通性要求
// 函数输入:
//     pImgSrc---原始图像(一定要二值图像)
//     nImgWidth--- 输入图像的宽度
//     oPt---种子点
// 函数返回:
//     true---种子点oPt满足四连通区域连通性要求
//     false---种子点oPt不满足四连通区域连通性要求
///
bool IsFourConnection (unsigned char* pImgSrc, int nImgHeight, int nImgWidth, ZKPOINT& oPt)
{
    bool blRet = true;


    if (oPt.y >= nImgHeight -1) return false;
    if (oPt.x >= nImgWidth) return false;
    if (oPt.x < 0 || oPt.y < 0) return false;


    int nBase = oPt.y * nImgWidth + oPt.x;
    if (pImgSrc[nBase] < BASEVALUE)  return false;


    //  A | B | C
    //  D | * | E
    //  F | G | H
    // 只要E、G之一为非零点, 即可判断满足四连通区域连通性要求
    // E点
    int nIdx = nBase +1;
    bool b1  = (pImgSrc[nIdx] > BASEVALUE);


    // G点
    nIdx = nBase + nImgWidth;
    bool b2  = (pImgSrc[nIdx] > BASEVALUE);


    blRet = (b1 || b2);


    return blRet;
}


///
// 函数功能: 抓取种子点的四连通区域
// 函数输入:
//     pImgSrc---原始图像(一定要二值图像)
//     nImgWidth--- 输入图像的宽度
//     oPt---种子点
// 函数输出:
//     vecPoints---满足条件的点得集合
// 函数返回:
//     void
///
void GetFourConnection (unsigned char* pImgSrc, int nImgHeight, int nImgWidth, ZKPOINT& oPt, vector<ZKPOINT>& vecPoints)
{
    ZKPOINT oTempPoint;


    //  A | B | C
    //  D | * | E
    //  F | G | H
    // 只要G、E之中存在任意一个非零点, 即可判断满足四连通区域连通性要求
    // E点
    oTempPoint.x = oPt.x + 1;
    oTempPoint.y = oPt.y;
    if (IsFourConnection(pImgSrc, nImgHeight, nImgWidth, oTempPoint))
    {
        vecPoints.push_back(oTempPoint);
    }


    // G点
    oTempPoint.x = oPt.x;
    oTempPoint.y = oPt.y + 1;
    if (IsFourConnection(pImgSrc, nImgHeight, nImgWidth, oTempPoint))
    {
        vecPoints.push_back(oTempPoint);
    }


    return;
}


///
// 函数功能: 判断种子点的八连通区域是否满足连通性要求
// 函数输入:
//     pImgSrc---原始图像(一定要二值图像)
//     nImgWidth--- 输入图像的宽度
//     oPt---种子点
// 函数返回:
//     true---种子点oPt满足四连通区域连通性要求
//     false---种子点oPt不满足四连通区域连通性要求
///
bool IsEightConnection (unsigned char* pImgSrc, int nImgHeight, int nImgWidth, ZKPOINT& oPt)
{
    bool blRet = true;


    if (oPt.y >= nImgHeight -1) return false;
    if (oPt.x >= nImgWidth) return false;
    if (oPt.x < 0 || oPt.y < 0) return false;

    int nBase = oPt.y * nImgWidth + oPt.x;
    if (pImgSrc[nBase] < BASEVALUE)  return false;


    //  A | B | C
    //  D | * | E
    //  F | G | H
    // 只要E、F、G、H之一为非零点, 即可判断满足四连通区域连通性要求


    // E点
    int nIdx = nBase +1;
    bool b1  = (pImgSrc[nIdx] > BASEVALUE);


    // F点
    nIdx = nBase + nImgWidth -1;
    bool b2  = (pImgSrc[nIdx] > BASEVALUE);


    // G点
    nIdx = nBase + nImgWidth;
    bool b3  = (pImgSrc[nIdx] > BASEVALUE);


    // H点
    nIdx = nBase + nImgWidth + 1;
    bool b4  = (pImgSrc[nIdx] > BASEVALUE);


    blRet = (b1 || b2 || b3 || b4);


    return blRet;
}




///
// 函数功能: 抓取种子点的八连通区域
// 函数输入:
//     pImgSrc---原始图像(一定要二值图像)
//     nImgWidth--- 输入图像的宽度
//     oPt---种子点
// 函数输出:
//     vecPoints---满足条件的点得集合
// 函数返回:
//     void
///
void GetEightConnection (unsigned char* pImgSrc, int nImgHeight, int nImgWidth, ZKPOINT& oPt, vector<ZKPOINT>& vecPoints)
{
    ZKPOINT oTempPoint;


    //  A | B | C
    //  D | * | E
    //  F | G | H
    // 只要E、F、G、E之中存在任意一个非零点, 即可判断满足四连通区域连通性要求
    // E点
    oTempPoint.x = oPt.x + 1;
    oTempPoint.y = oPt.y;
    if (IsEightConnection(pImgSrc, nImgHeight, nImgWidth, oTempPoint))
    {
        vecPoints.push_back(oTempPoint);
    }


    // F点
    oTempPoint.x = oPt.x - 1;
    oTempPoint.y = oPt.y + 1;
    if (IsEightConnection(pImgSrc, nImgHeight, nImgWidth, oTempPoint))
    {
        vecPoints.push_back(oTempPoint);
    }


    // G点
    oTempPoint.x = oPt.x;
    oTempPoint.y = oPt.y + 1;
    if (IsEightConnection(pImgSrc, nImgHeight, nImgWidth, oTempPoint))
    {
        vecPoints.push_back(oTempPoint);
    }


    // H点
    oTempPoint.x = oPt.x + 1;
    oTempPoint.y = oPt.y + 1;
    if (IsEightConnection(pImgSrc, nImgHeight, nImgWidth, oTempPoint))
    {
        vecPoints.push_back(oTempPoint);
    }


    return;
}




///
// 函数功能: 将输入的二值图像进行连通体标记输出, 并报告连通体个数
// 函数输入:
//     pImgSrc---原始图像(一定要二值图像)
//     nImgHeight---输入图像的高度
//     nImgWidth--- 输入图像的宽度
//     nType--------只能为4或者8, 表示4邻域或者8邻域
//
// 函数输出:
//     nCounts----返回找到的连通体的个数
//
// 函数返回:
//    pLabels----为空指针, 输出时为经过初始化了的图像标记, 其高度和宽度跟输入图像的高度和宽度一样
// 函数备注:
//    调用bwlabel_1函数会产生内存重新分配之类的东西, 所以请务必在调用bwlabel_1函数之后手动删除bwlabel_1返回的指针
//    比如: int* pLabel = bwlabel_1(....);
//     do somthing....
//     delete []pLabel; pLabel = NULL;
///
int* bwlabel_1(unsigned char* pImgSrc, int nImgHeight, int nImgWidth, int &nCounts, int nType)
{
    assert (nImgHeight > 3);
    assert (nImgWidth  > 3);


    if ( (4 != nType) || (8 != nType) )
    {
        nType = 4;
    }


    // 原理描述:
    //
    //
    //
    int nSize = nImgHeight * nImgWidth;
    int* pLabels = NULL;
    pLabels = new int[nSize];
    memset (pLabels, 0, nSize * sizeof(int));
    if (NULL == pLabels)  return 0;


    // 用于保存陈柏生老先生所说的种子点
    vector<ZKPOINT> vecLabel;
    vector<ZKPOINT> vecTemp;


    // 原理描述:
    // 首先, 对输入的二值图像施行逐行扫描, 找到一个未标记区域的第一点(该点的选择不影响算法的实现),
    // 标记该点;
    // 检查该点的八邻域点并标记满足连通性要求的, 且尚未被标记的点, 
    // 同时将新增的标记点记录下来作为“区域增长”的种子点。
    // 后续的标记的过程中,不断地从记录种子点的数组中取出一个种子,
    // 施行上述的操作,如此循环,直到记录种子点的数组为空。一个连通区域标记结束。
    // 接着再标记下一个未标记区域,直到输入二值图像的所有连通区域都被标记。


    int nFlags = 1;
    // 算法首先对原始二值图像进行一次逐行扫描
    // 对输入的二值图像施行逐行扫描, 找到一个未标记区域的第一点
    for ( int i = 0; i < nImgHeight -1; i ++ )
    {
        int nIdx1 = i * nImgWidth;
        for ( int j = 0; j < nImgWidth -1; j ++ )
        {
            int nIdx2 = nIdx1 + j;


            // 黑点直接Pass掉
            if (pImgSrc[nIdx2] < BASEVALUE)
            {
                continue;
            }


            // 找到一个未标记区域的第一点
            // 已经被标记的点---无需再费工夫, 直接Pass掉
            if (pLabels[nIdx2] > 0)
            {
                continue;
            }


            // 找到未标记区域的第一个像素点
            // 将该点记录为存入vector
            // 标记之
            ZKPOINT oPt;


            oPt.x = j;
            oPt.y = i;
            pLabels[nIdx2] = nFlags;
            vecLabel.push_back (oPt);


            // 之后便进入了当前连通区域标记的循环
            // 
            while (true)
            {
                vecTemp.clear ();


                // 四连通区域标记
                vector<ZKPOINT>::iterator it = vecLabel.begin();
                for (; it != vecLabel.end(); ++it)
                {
                    // 四连通区域标记
                    if (4 == nType)
                    {
                        GetFourConnection (pImgSrc, nImgHeight, nImgWidth, (*it), vecTemp);
                    }


                    // 八连通区域标记
                    else
                    {
                        GetEightConnection (pImgSrc, nImgHeight, nImgWidth, (*it), vecTemp);
                    }
                }


                // 首先清空已经完成使命的种子点
                vecLabel.clear();


                // 说明已经没有符合连通体条件的点存在了, 也就是说连通体已经被全部标记
                if (0 == vecTemp.size())
                {
                    break;
                }


                // 连通体没有被标记完毕
                else
                {
                    vector<ZKPOINT>::iterator ittemp = vecTemp.begin();
                    for (; ittemp != vecTemp.end(); ++ittemp)
                    {
                        // 标记
                        int nTempIdx = ittemp->y * nImgWidth + ittemp->x;
                        // 杜绝因搜索产生出的黑点
                        if (pImgSrc[nIdx2] < BASEVALUE)  continue;


                        // 对于没有标记的点才可以作为种子点
                        if (pLabels[nTempIdx] == 0)
                        {
                            pLabels[nTempIdx] = nFlags;


                            // 作为下一次标记的种子点
                            vecLabel.push_back( (*ittemp) );
                        }
                    }
                }


                vecTemp.clear ();
            }


            vecTemp.clear ();
            vecLabel.clear ();
            nFlags ++;
        }
    }


    // 按照规范, 在函数结束时候务必来一次临时容器数据的清理
    vecLabel.clear();
    vecTemp.clear ();
    nCounts = nFlags - 1;


    return pLabels;
}


///
// 函数功能: 将输入的二值图像进行连通体标记输出, 并报告连通体个数
// 函数输入:
//     pImgSrc---原始图像(一定要二值图像)
//     nImgHeight---输入图像的高度
//     nImgWidth--- 输入图像的宽度
//     nType--------只能为4或者8, 表示4邻域或者8邻域
//
// 函数输出:
//     pLabels----为空指针, 输出时为经过初始化了的图像标记, 其高度和宽度跟输入图像的高度和宽度一样
//
// 函数返回:
//    返回找到的连通体的个数
// 函数备注:
//    调用bwlabel_1函数会产生内存重新分配之类的东西, 所以请务必在调用bwlabel_1函数之后手动删除bwlabel_1返回的指针
//    比如: 
//     int* pLabel = NULL;
//     bwlabel_2 (...., pLabel);
//     do somthing....
//     delete []pLabel; pLabel = NULL;
///
int bwlabel_2(unsigned char* pImgSrc, int nImgHeight, int nImgWidth, int nType, int*& pLabels)
{
    int nCounts = 0;


    assert (nImgHeight > 3);
    assert (nImgWidth  > 3);


    if ( (4 != nType) || (8 != nType) )
    {
        nType = 4;
    }


    // 原理描述:
    //
    //
    //
    int nSize = nImgHeight * nImgWidth;
    pLabels = NULL;
    pLabels = new int[nSize];
    memset (pLabels, 0, nSize * sizeof(int));
    if (NULL == pLabels)  return 0;


    // 用于保存陈柏生老先生所说的种子点
    vector<ZKPOINT> vecLabel;
    vector<ZKPOINT> vecTemp;


    // 原理描述:
    // 首先, 对输入的二值图像施行逐行扫描, 找到一个未标记区域的第一点(该点的选择不影响算法的实现),
    // 标记该点;
    // 检查该点的八邻域点并标记满足连通性要求的, 且尚未被标记的点, 
    // 同时将新增的标记点记录下来作为“区域增长”的种子点。
    // 后续的标记的过程中,不断地从记录种子点的数组中取出一个种子,
    // 施行上述的操作,如此循环,直到记录种子点的数组为空。一个连通区域标记结束。
    // 接着再标记下一个未标记区域,直到输入二值图像的所有连通区域都被标记。


    int nFlags = 1;
    // 算法首先对原始二值图像进行一次逐行扫描
    // 对输入的二值图像施行逐行扫描, 找到一个未标记区域的第一点
    for ( int i = 0; i < nImgHeight -1; i ++ )
    {
        int nIdx1 = i * nImgWidth;
        for ( int j = 0; j < nImgWidth -1; j ++ )
        {
            int nIdx2 = nIdx1 + j;


            // 黑点直接Pass掉
            if (pImgSrc[nIdx2] < BASEVALUE)
            {
                continue;
            }


            // 找到一个未标记区域的第一点
            // 已经被标记的点---无需再费工夫, 直接Pass掉
            if (pLabels[nIdx2] > 0)
            {
                continue;
            }


            // 找到未标记区域的第一个像素点
            // 将该点记录为存入vector
            // 标记之
            ZKPOINT oPt;


            oPt.x = j;
            oPt.y = i;
            pLabels[nIdx2] = nFlags;
            vecLabel.push_back (oPt);


            // 之后便进入了当前连通区域标记的循环
            // 
            while (true)
            {
                vecTemp.clear ();


                // 四连通区域标记
                vector<ZKPOINT>::iterator it = vecLabel.begin();
                for (; it != vecLabel.end(); ++it)
                {
                    // 四连通区域标记
                    if (4 == nType)
                    {
                        GetFourConnection (pImgSrc, nImgHeight, nImgWidth, (*it), vecTemp);
                    }


                    // 八连通区域标记
                    else
                    {
                        GetEightConnection (pImgSrc, nImgHeight, nImgWidth, (*it), vecTemp);
                    }
                }


                // 首先清空已经完成使命的种子点
                vecLabel.clear();


                // 说明已经没有符合连通体条件的点存在了, 也就是说连通体已经被全部标记
                if (0 == vecTemp.size())
                {
                    break;
                }


                // 连通体没有被标记完毕
                else
                {
                    vector<ZKPOINT>::iterator ittemp = vecTemp.begin();
                    for (; ittemp != vecTemp.end(); ++ittemp)
                    {
                        // 标记
                        int nTempIdx = ittemp->y * nImgWidth + ittemp->x;


                        // 杜绝因搜索产生出的黑点
                        if (pImgSrc[nIdx2] < BASEVALUE)  continue;


                        // 对于没有标记的点才可以作为种子点
                        if (pLabels[nTempIdx] == 0)
                        {
                            pLabels[nTempIdx] = nFlags;


                            // 作为下一次标记的种子点
                            vecLabel.push_back( (*ittemp) );
                        }
                    }
                }


                vecTemp.clear ();
            }


            vecTemp.clear ();
            vecLabel.clear ();
            nFlags ++;
        }
    }


    // 按照规范, 在函数结束时候务必来一次临时容器数据的清理
    vecLabel.clear();
    vecTemp.clear ();


    return (nFlags -1);
}
经长时间检验是工业级可用的。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐