BWLABEL函数的C++实现

实验中需要用到区域联通的算法,就是类似于matlab中bwlabel的函数。网上找了找c++源码未果,bwlabel-python版用python描述了matlab中的实现方法,但是最后对标签的处理部分并未看明白,故自己用c++实现了一个。先直接看bwlabel函数代码:

cv::Mat bwlabel(const cv::Mat in, int * num, const int mode)
{
    const int num_runs = number_of_runs(in);
    int * sc = new int[num_runs];
    int * ec = new int[num_runs];
    int * r = new int[num_runs];
    int * labels = new int[num_runs];
    memset(labels, 0, sizeof(int)*num_runs);
    fill_run_vectors(in, sc, ec, r);
    first_pass(sc, ec, r, labels, num_runs, mode);
    cv::Mat result = cv::Mat::zeros(in.size(), CV_8UC1);

    int number = 0;
    for(int i = 0; i < num_runs; i++)
    {
        uchar * p_row = result.ptr<uchar>(r[i]);
        for(int j = sc[i]; j <= ec[i]; j++)
            p_row[j] = labels[i];
        if(number < labels[i])
            number = labels[i];
    }
    if(num != NULL)
        *num = number;
    delete [] sc;
    delete [] ec;
    delete [] r;
    delete [] labels;
    return result;
}


bwlabel中要用到三个辅助函数:number_of_runs,fill_run_vectors,first_pass。函数number_of_runs计算每一行中非零像素团的个数并累加起来。

1 1 0 0 0 1 1 1 0 0

比如,上面这一行就有2个非零像素团,我们称这样的像素团为Run,函数number_of_runs实现如下:


int number_of_runs(const cv::Mat in)
{
    const int rows = in.rows;
    const int cols = in.cols;
    int result = 0;
    for(int row = 0; row < rows; row++)
    {
        const uchar * p_row = in.ptr<uchar>(row);
        if(p_row[0] != 0)
            result++;
        for(int col = 1; col < cols; col++)
        {
            if(p_row[col] != 0 && p_row[col-1] == 0)
                result++;
        }
    }
    return result;
}


这个函数算法思想是,扫描每一行,对每一行,如果当前元素非零并且前一元素为零则Run的个数加一。

函数fill_run_vectors的作用是填充三个数据结构:sc[],ec[],r[],它们分别表示开始列标、结束列标和行标,数组长度为由number_of_runs函数得到的Run的个数。函数fill_run_vectors实现如下:

void fill_run_vectors(const cv::Mat in, int sc[], int ec[], int r[])
{
    const int rows = in.rows;
    const int cols = in.cols;
    int idx = 0;
    for(int row = 0; row < rows; row++)
    {
        const uchar * p_row = in.ptr<uchar>(row);
        int prev = 0;
        for(int col = 0; col < cols; col++)
        {
            if(p_row[col] != prev)
            {
                if(prev == 0)
                {
                    sc[idx] = col;
                    r[idx] = row;
                    prev = 1;
                }
                else
                {
                    ec[idx++] = col - 1;
                    prev = 0;
                }
            }
            if(col == cols-1 && prev == 1)
            {
                ec[idx++] = col;
            }
        }
    }
}

算法思想还是遍历每一行,用变量prev保存一行中上一个团是0还是1,如果出现01跳变那么就要记录下新的Run的开始列标和行标,如果出现10跳变(或者这行结束并且prev=1)那么就记录下这个Run的结束列标。

函数first_pass顾名思义,字面上说第一次扫描。因为函数扫描每一个Run块,给它打标签。当出现如下情况时:

1 1 0 0 1 1 1 0 
0 1 1 1 1 0 0 0

函数给第一行第一个Run打上标签1,第二个Run打上标签2,当遍历到第二行时,发现这一行的一个Run与第一行第一个Run相邻,故打上标签1,但当继续遍历时发现这个Run也与第一行第二个Run相邻,但函数并没有改变第一行第二个Run的标签,而是记录下这两个标签其实该一样。遍历完第二行结果为:

1 1 0 0 2 2 2 0 
0 1 1 1 1 0 0 0

遍历完每一个Run过后就是处理刚才未处理的标签了。函数first_pass实现如下:

void first_pass(const int sc[], const int ec[], const int r[],int labels[], const int num_runs, const int mode)
{
    int cur_row = 0;
    int next_label = 1;
    int first_run_on_prev_row = -1;
    int last_run_on_prev_row = -1;
    int first_run_on_this_row = 0;
    int offset = 0;
    int * equal_i = new int[num_runs];
    int * equal_j = new int[num_runs];
    int equal_idx = 0;
    if(mode == 8)
        offset = 1;
    for(int k = 0; k < num_runs; k++)
    {
        if(r[k] == cur_row + 1)
        {
            cur_row += 1;
            first_run_on_prev_row = first_run_on_this_row;
            first_run_on_this_row = k;
            last_run_on_prev_row = k - 1;
        }
        else if(r[k] > cur_row + 1)
        {
            first_run_on_prev_row = -1;
            last_run_on_prev_row = -1;
            first_run_on_this_row = k;
            cur_row = r[k];
        }
        if(first_run_on_prev_row >= 0)
        {
            int p = first_run_on_prev_row;
            while(p <= last_run_on_prev_row && sc[p] <= (ec[k] + offset))
            {
                if(sc[k] <= ec[p] + offset)
                {
                    if(labels[k] == 0)
                        labels[k] = labels[p];
                    else if(labels[k] != labels[p])
                    {
                        //labels[p] = labels[k];
                        equal_i[equal_idx] = labels[k];
                        equal_j[equal_idx] = labels[p];
                        equal_idx += 1;
                    }
                }
                p += 1;
            }
        }
        if(labels[k] == 0)
        {
            labels[k] = next_label++;
        }
    }
    /// process labels
    for(int i = 0; i < equal_idx; i++)
    {
        int max_label = equal_i[i] > equal_j[i] ? equal_i[i] : equal_j[i];
        int min_label = equal_i[i] < equal_j[i] ? equal_i[i] : equal_j[i];
        for(int j = 0; j < num_runs; j++)
        {
            if(labels[j] == max_label)
                labels[j] = min_label;
        }
    }
    delete [] equal_i;
    delete [] equal_j;
    /process ignore labels
    int * hist = new int[next_label];
    int * non_labels = new int[next_label];
    memset(hist, 0, sizeof(int)*next_label);
    int non_num = 0;
    for(int i = 0; i < num_runs; i++)
    {
        hist[labels[i]]++;
    }
    for(int i = 1; i < next_label; i++)
    {
        if(hist[i] == 0)
            non_labels[non_num++] = i;
    }
    for(int j = 0; j < num_runs; j++)
    {
        int k = labels[j];
        for(int i = non_num-1; i >= 0; i--)
        {
            if(k > non_labels[i])
            {
                labels[j] -= (i+1);
                break;
            }
        }
    }
    delete [] hist;
    delete [] non_labels;
}

前面遍历每一个Run分两种情况,上一行有Run和上一行无Run:当上一行无Run时就分配一个新的标签,当上一行有Run时还要考虑是否与上一行Run相邻,若相邻则打上上一行的标签,当出现上面讲到的情况时就保存这两个标签到数组equal_i,equal_j中。

接下来就是处理equal_i和equal_j这两个数组了,要将它们当中相同族的不同标签合并到一起(注释process labels下面代码)。

这样过后还不能完事,有可能出现标签间断的现象(如1,2,4,6),就是还必须把标签(如1,2,4,6)映射到一个连续的空间(1,2,3,4)。参见注释process ignore labels以下代码。

这样过后就差不多了,最后一步是在bwlabel中给返回的Mat中元素打上对应的标签。


  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: bwlabel函数MATLAB中用于对二值图像进行连通区域标记的函数。其实现主要基于连接组件分析算法,通过遍历二值图像中的每个像素,对具有相同像素值的相邻像素进行标记,最终得到每个连通区域的唯一标签。 具体实现过程如下: 1. 创建两个与原二值图像相同大小的矩阵label和eqTable,用于存储标签和等价关系表。 2. 初始化标签为0,等价关系表为空。创建一个变量currLabel,用于记录当前标签值。 3. 遍历二值图像中的每个像素,对于每个像素的位置(i, j): a. 如果该像素为前景像素(值为1): - 获取上方像素的标签值label(i-1, j),若无则获取左方像素的标签值label(i, j-1)。 - 如果上方和左方的像素都没有标签,则为新区域,将currLabel增加1,并将其作为该像素的标签,同时将currLabel与eqTable进行连接。 - 如果上方和左方的像素都有标签,则将它们进行合并,取其中较小的标签值作为该像素的标签,并将对应的等价关系记录到eqTable中。 - 如果只有一个方向有标签,则将该像素的标签赋值为那个标签。 - 如果上方和左方的像素的标签不相等,则将它们的等价关系记录到eqTable中。 b. 如果该像素为背景像素(值为0),则将该像素的标签赋值为0。 4. 根据eqTable对已有的标签进行合并,使相互之间等价的标签标记为同一个值。 5. 统计所有标签的数量,并将每个像素的标签替换为其在标签数量统计中的索引值。 6. 返回标记后的二值图像。 总结起来,bwlabel函数实现主要包括:分配标签、处理等价关系、合并标签和统计并替换标签值等步骤。最终的输出是一个与输入图像相同大小的已经连通区域标记的结果图像。 ### 回答2: bwlabelMatlab中的一个图像分割函数,用于将二值图像中的连通成分标记并分割出来。在实现bwlabel函数时,可以使用C语言编写以下算法: 1. 首先创建两个与输入图像大小相同的数组label和visited,并初始化为0。 2. 遍历输入图像的每个像素点,如果该点为前景像素(像素值为1)且未被访问过(visited为0),则开始标记一个新的连通成分。 3. 从当前像素点出发进行深度优先搜索(DFS),按照八连通的方式搜索相邻的像素点。 4. 如果搜索到的像素点为前景像素且未被访问过,则将其标记为当前连通成分的一部分,并递归调用DFS函数继续搜索相邻的像素点。 5. 在DFS函数的返回之后,当前连通成分的标记完成。将标记值记录到label数组中,并将visited数组中对应的位置标记为已访问(值设为1)。 6. 返回到遍历输入图像的下一个未访问的像素点进行相同的操作,直到所有像素点都被遍历完毕。 7. 最后,返回label数组,其中每个连通成分的像素点将被标记为不同的整数值。 这样就完成了对二值图像的连通成分分割和标记。使用C语言实现bwlabel函数可以通过以上算法进行操作,将输入的二值图像转化为连通图,并将各个连通成分进行标记。 ### 回答3: bwlabelMATLAB中用于二值图像连通区域标记的函数。它将输入的二值图像以连通区域为单位进行标记,并将每个连通区域之间的像素标记为不同的整数。 bwlabel的C实现通常使用图像处理库OpenCV实现。下面是一个简单的bwlabel的C实现示例: #include <stdlib.h> #include <stdio.h> #include <opencv2/opencv.hpp> using namespace cv; void bwlabel(Mat& binaryImage, Mat& labeledImage) { // 初始化标记 int label = 1; // 记录已标记过的像素 std::vector<Point> labeledPixels; for (int y = 0; y < binaryImage.rows; y++) { for (int x = 0; x < binaryImage.cols; x++) { // 获取当前像素的值 int pixelValue = binaryImage.at<uchar>(y, x); // 如果当前像素为前景像素且未标记 if (pixelValue == 255 && labeledImage.at<int>(y, x) == 0) { // 用DFS或BFS进行连通区域标记 std::queue<Point> que; que.push(Point(x, y)); int currentLabel = label; labeledImage.at<int>(y, x) = currentLabel; while (!que.empty()) { Point p = que.front(); que.pop(); // 获取当前像素的八邻域像素 std::vector<Point> neighbours; neighbours.push_back(Point(p.x - 1, p.y - 1)); // ... 添加其它邻域坐标 for (auto& np : neighbours) { // 如果邻域像素为前景像素且未标记 if (binaryImage.at<uchar>(np.y, np.x) == 255 && labeledImage.at<int>(np.y, np.x) == 0) { labeledImage.at<int>(np.y, np.x) = currentLabel; que.push(np); } } } label++; } } } } int main(int argc, char** argv) { // 读取二值图像 Mat binaryImage = imread("binary_image.png", 0); // 创建标记图像,数据类型为int Mat labeledImage(binaryImage.size(), CV_32S, Scalar(0)); // 进行连通区域标记 bwlabel(binaryImage, labeledImage); return 0; } 这是一个基础的bwlabel的C实现示例,具体的实现可根据实际需求进行修改和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值