Two-Pass算法——图像连通域分析

9 篇文章 3 订阅
7 篇文章 0 订阅

在处理二值图像,提取感兴趣目标时经常需要通过连通域的大小对候选目标进行初步筛选。OpenCV中findContour 方法可以返回轮廓并能够计算轮廓面积。可其局限性在对于非凸多边形的面积计算是不准确的。 此时,利用连通域计算面积的方法更可靠,然而 findContour方法并不返回连通域结果。

计算连通域基本方法主要有两种:1)Two-Pass法;2)Seed-Filling种子填充法;
参考了这篇文章的实现OpenCV_连通区域分析(Connected Component Analysis/Labeling)

其中,Two-Pass法步骤简单,易于理解。但是该博文的实现算法存在Bug, 会将连通的区域分割成不同标签。测试图如下:
这里写图片描述

使用其连通区域的结果如下:
这里写图片描述

不难发现,第二、四字符的连通区域计算出现了错误。 出错的原因是在于连通域的等价表上面。(equivalences )

其方法利用一维列表结构存储等价关系。例如存在一个具有等价性的标签组:{ 1,3,5,7};
则可以用一维数组表示: a[7] = 5, a[5] = 3, a[3] = 1,a[1] = 1, 或者更高效地是 a[7] = 1, a[5] = 1, a[3] = 1, a[1] = 1。 而前者的方式可以更准确地传递等价性,因为大多数时候 7 与 1 , 5 与1 的关系并不明确,或者很难获知。相对地, 3 与 1, 5 与 3 , 7 与 5的关系 更容易获得。 原因在于其标签值相近,代表空间相近,继而可通过邻域判断连通性。这里理解有困难可看这里Wiki: Connected-component labeling

具体上面提到的参考算法的bug不细表,给出我的实现方法。

  1. 主算法
bool twoPass(cv::Mat pBinary, int background,int foreground, int border, cv::Mat& pLabel)
{
    // connected component analysis (4- component)  
    // use two-pass algorithm  
    // 1. first pass: label each foreground pixel with a label  
    // 2. second pass: visit each labeled pixel and merge neighbor labels  
    if (pBinary.empty() || pBinary.channels() != 1)
        return false;

    int width = pBinary.cols;
    int height = pBinary.rows;

    pLabel.release();
    pBinary.convertTo(pLabel, CV_32SC1);

    int *data = pLabel.ptr<int>(0);
    for (int i = 0; i < width*height; i++)
        if (foreground == data[i]) data[i] = 1;
        else                                    data[i] = 0;

    int label = 1;

    //labelSet 连通域列表 比如连通队列{1,3,5,7} labelSet[7] = 5, labelSet[5] = 3, labelSet[3] = 1, labelSet[1] = 1
    vector<int> labelSet;
    labelSet.push_back(0);
    labelSet.push_back(1);

    border = MAX(1, border);
    for (int i = border; i < height -border; i++)
    {
        int* curRowData = pLabel.ptr<int>(i);
        int* preRowData = pLabel.ptr<int>(i - 1);

        for (int j = border; j < width - border; j++)
        {
            int* cur_data = curRowData + j;
            if (0 == *cur_data)     continue;

            int* left_data = curRowData + j - 1;
            int* up_data = preRowData + j;
            int* right_up_data = preRowData + j + 1;
            int* left_up_data = preRowData + j - 1;

            if (90 == i && 125 == j)
                cout << "stop" << endl;

            vector<int> neighborLabels;
            if (*left_data > 1)
                neighborLabels.push_back(*left_data);
            if (*up_data > 1)
                neighborLabels.push_back(*up_data);
//          if (*right_up_data > 1)
//              neighborLabels.push_back(*right_up_data);
//          if (*left_up_data > 1)
//              neighborLabels.push_back(*left_up_data);

            if ( neighborLabels.empty() )
            {
                labelSet.push_back(++label);
                *cur_data = label;
                labelSet[label] = label;
            }
            else
            {
                sort(neighborLabels.begin(), neighborLabels.end());
                *cur_data = neighborLabels[0];

                for (size_t  k = 1; k < neighborLabels.size();  k++)
                {
                    int tmpLabel = neighborLabels[k];
                    int oldLabel = labelSet[tmpLabel];

                    if (oldLabel > *cur_data)
                    {                   
                        // 这里是关键
                        while ( oldLabel != tmpLabel)
                        {
                            int tmp = tmpLabel;
                            tmpLabel = oldLabel;
                            oldLabel = labelSet[tmpLabel];

                            labelSet[tmp] = *cur_data;
                        }

                        if (tmpLabel > *cur_data)
                            labelSet[tmpLabel] = *cur_data;
                        else
                            labelSet[*cur_data] = tmpLabel;
                    }
                    else if (oldLabel < *cur_data ) // 该有时语句不执行
                    {
                        labelSet[*cur_data] = oldLabel;
                        // 后面的Label是有前面的 data决定的
                        //*cur_data = oldLabel;
                    }
                } 
            } // neighbors
        } // j 
    } // i

    // 2. second pass  
    // input : {1,3,5,7} labelSet[7] = 5, labelSet[5] = 3, labelSet[3] = 1, labelSet[1] = 1
    // out : labelSet[7] = 1, labelSet[5] = 1, labelSet[3] = 1, labelSet[1] = 1
    for (size_t i = 2; i < labelSet.size(); i++)
    {
        if ( i == labelSet[i] )
            continue;

        int cur_label = labelSet[i];
        int pre_label = labelSet[cur_label];

        while (pre_label != cur_label)
        {
            cur_label = pre_label;
            pre_label = labelSet[pre_label];
        }

        labelSet[i] = cur_label;
    }

    cout <<"label : "<< labelSet.size() << endl;

    for (int i = 0; i < height; i++)
    {
        int* data = pLabel.ptr<int>(i);
        for (int j = 0; j < width; j++)
        {
            int& pixelLabel = data[j];
            pixelLabel = labelSet[pixelLabel];
        }
    }

    return true;
}

结果:
这里写图片描述

2、 有条件选择连通域标签

void thresholdLabel(const cv::Mat& _labelImg, vector<int>& _labelList)
{
    if (_labelImg.empty() || _labelImg.type() != CV_32SC1)
        return;

    std::map<int,int> labelCount;

    int rows = _labelImg.rows;
    int cols = _labelImg.cols;

    for (int i = 0; i < rows; i++)
    {
        const int* data_src = (int*)_labelImg.ptr<int>(i);
        for (int j = 0; j < cols; j++)
        {
            int pixelValue = data_src[j];
            if (0 == pixelValue) continue;

            if (labelCount.count(pixelValue) <= 0)
                labelCount[pixelValue] = 1;
            else
                labelCount[pixelValue]++;

        }
    }
    std::map<int, int>::iterator st = labelCount.begin();
    std::map<int, int>::iterator  et = labelCount.end();
    for (; st != et; ++st)
    {
        if (st->second < 100 ) continue;  // 连通域小于100,忽略
        _labelList.push_back(st->first);
        cout << "label " << st->first << ": " << st->second<<endl;
    }
}

3、 随机着色

cv::Scalar randColor()
{
    uchar r = 255 * (rand() / (1.0 + RAND_MAX));
    uchar g = 255 * (rand() / (1.0 + RAND_MAX));
    uchar b = 255 * (rand() / (1.0 + RAND_MAX));
    return cv::Scalar(b, g, r);
}

void getLabelColor(const cv::Mat& _labelImg, vector<int>_labelList, cv::Mat& _colorLabelImg)
{
    if (_labelImg.empty() || _labelImg.type() != CV_32SC1 || _labelList.empty())
        return;

    std::map<int, cv::Scalar> colors;

    int rows = _labelImg.rows;
    int cols = _labelImg.cols;

    _colorLabelImg.release();
    _colorLabelImg.create(rows, cols, CV_8UC3);
    _colorLabelImg = cv::Scalar::all(0);

    for (int i = 0; i < rows; i++)
    {
        const int* data_src = (int*)_labelImg.ptr<int>(i);
        uchar* data_dst = _colorLabelImg.ptr<uchar>(i);
        for (int j = 0; j < cols; j++)
        {
            int pixelValue = data_src[j];

            vector<int>::iterator  it = find(_labelList.begin(), _labelList.end(), pixelValue);
            if (it == _labelList.end())
            {
                data_dst++;
                data_dst++;
                data_dst++;

                continue;
            }

            if (pixelValue > 1)
            {
                if (colors.count(pixelValue) <= 0)
                {
                    colors[pixelValue] = randColor();
                }
                cv::Scalar color = colors[pixelValue];
                *data_dst++ = color[0];
                *data_dst++ = color[1];
                *data_dst++ = color[2];
            }
            else
            {
                data_dst++;
                data_dst++;
                data_dst++;
            }
        }
    }
}

更多结果:
原图
原由

结果:
这里写图片描述

此文仅讨论Two-Pass 实现问题。Two-Pass 方法相较于SeedFill ,运算效率比较低。

完。

                                                        Lewis, 2018-06-24
  • 12
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Two-Pass算法是计算连通的一种基本方法之一\[1\]。它是一种用于图像处理的算法,可以用于标记和计算图像中的连通。在FPGA(现场可编程门阵列)中实现Two-Pass算法可以提高图像处理的效率和速度。 FPGA是一种可编程的硬件设备,可以根据需要重新配置其内部电路,以实现特定的功能。在FPGA中实现Two-Pass算法,可以通过并行处理的方式同时处理多个像素,从而加快图像处理的速度。通过将Two-Pass算法的各个步骤分配到FPGA的不同部分,可以实现高效的图像连通计算。 具体实现Two-Pass算法的FPGA设计需要根据具体的硬件平台和需求进行设计和优化。可以使用硬件描述语言(如VHDL或Verilog)来描述算法的逻辑和功能,并使用FPGA开发工具进行编译和综合。通过合理的设计和优化,可以实现高性能和低功耗的图像处理系统。 总之,通过在FPGA中实现Two-Pass算法,可以提高图像处理的效率和速度,适用于需要高性能图像处理的应用场景。 #### 引用[.reference_title] - *1* *3* [Two-Pass算法——图像连通分析](https://blog.csdn.net/liujiabin076/article/details/80788459)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [图像算法--基于fpga的双边滤波算法](https://blog.csdn.net/qq_40057229/article/details/127517641)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值