实验中需要用到区域联通的算法,就是类似于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中元素打上对应的标签。

@waring

bwlabel函数的c++实现的更多相关文章

  1. Python实现MATLAB中的 bwlabel函数

    最近做验证码识别,原本用MATLAB已经实现的整个识别模型,不过代码要部署在Linux服务器上还是需要用另外的语言实现,于是决定用Python + OpenCV来实现. bwlabel函数的作用是检测 ...

  2. Matlab图像处理函数:regionprops

    本篇文章为转载,仅为方便学术讨论所用,不用于商业用途.由于时间较久,原作者以及原始链接暂时无法找到,如有侵权以及其他任何事宜欢迎跟我联系,如有侵扰,在此提前表示歉意.----------------- ...

  3. matlab函数大全

    Matlab 图像处理相关函数命令大全 一.通用函数: colorbar  显示彩色条 语法:colorbar \ colorbar('vert') \ colorbar('horiz') \ col ...

  4. matlab boundaries和fchcode函数无法执行的解决办法 未定义与 'double' 类型的输入参数相对应的函数 'boundaries'

    在测试代码时发现,自己的matlab无法执行Freeman链码函数: boundaries和fchcode函数都无法正常运行: 需要在自己的工作目录中添加如下函数: boundaries   fchc ...

  5. MATLAB中图像处理的函数

    表1 图像显示 函数名 功能说明 函数名 功能说明 colorbar 颜色条显示 montage 按矩形剪辑方式显示多帧图像 getimage 从坐标系中获取图像数据 immovie 从多帧索引图像中 ...

  6. Python 小而美的函数

    python提供了一些有趣且实用的函数,如any all zip,这些函数能够大幅简化我们得代码,可以更优雅的处理可迭代的对象,同时使用的时候也得注意一些情况   any any(iterable) ...

  7. 探究javascript对象和数组的异同,及函数变量缓存技巧

    javascript中最经典也最受非议的一句话就是:javascript中一切皆是对象.这篇重点要提到的,就是任何jser都不陌生的Object和Array. 有段时间曾经很诧异,到底两种数据类型用来 ...

  8. JavaScript权威指南 - 函数

    函数本身就是一段JavaScript代码,定义一次但可能被调用任意次.如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法.用于初始化一个新创建的对象的函数被称作构造函数. 相对 ...

  9. C++对C的函数拓展

    一,内联函数 1.内联函数的概念 C++中的const常量可以用来代替宏常数的定义,例如:用const int a = 10来替换# define a 10.那么C++中是否有什么解决方案来替代宏代码 ...

随机推荐

  1. hdu4641-K-string(后缀自动机)

    Problem Description Given a string S. K-string is the sub-string of S and it appear in the S at leas ...

  2. 用BFS和DFS解决圆盘状态搜索问题

    人工智能课程的实验(我的解法其实更像是算法课程的实验) 用到的算法:深度优先搜索.宽度优先搜索(状态扩展的不同策略) 数据结构:表示状态的结构体.多维数组 (可能是最近做算法竞赛题的影响,这次并不像以 ...

  3. jQuery 遍历 json 方法大全

    1.for循环: var obj = { "status":1, "bkmsg":"\u6210\u529f", "bkdata& ...

  4. html5 手机APP计算高度问题

    安卓手机型号比较多,会出现bottom:0 找不到底部的问题,所以需要计算手机可视区域高度,这样便于使用百分比适配 (function(window,undefined){ /** * js_heig ...

  5. Android开发学习之Intent具体解释

    Intent简单介绍和具体解释:           Intent:协助应用间的交互与通信,Intent负责相应用中一次操作的动作.动作涉及的数据.附加数据进行描写叙述.               ...

  6. hdu4055 dp

    http://acm.hdu.edu.cn/showproblem.php?pid=4055 Problem Description The signature of a permutation is ...

  7. phoneGap开发环境搭建(android)

    1.  首先安装nodejs  (http://nodejs.org/) 2.  然后在命令行输入 npm 回车 假设出现下图: 则表示成功安装 3. 安装 npm install -g cordov ...

  8. Android ActionBar完全解析,使用官方推荐的最佳导航栏(下) .

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/25466665 本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工 ...

  9. 线程异常:undefined reference to &#39;pthread_create&#39; 处理

    源代码: #include <stdio.h> #include <pthread.h> #include <sched.h> void *producter_f ...

  10. java基础2

    //第一个程序 用super访问父类中被隐藏的成员变量和被重写的方法 package foxe; class superClass{ int x; superClass(){ x=4; System. ...