实验中需要用到区域联通的算法,就是类似于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. c语言筛选质数

    #include <stdio.h> #include <stdlib.h> #include <math.h> int isit(int num) { int i ...

  2. Java之Map

    Map 是一种把键对象和值对象映射的集合,它的每一个元素都包含一对键对象和值对象. Map没有继承于Collection接口 从Map集合中检索元素时,只要给出键对象,就会返回对应的值对象. code ...

  3. 探讨socket引发SIGPIPE信号的问题

    我写socket相关的程序也不是一天两天了,在我的记忆中,只要处理好recv(或read)的返回值中<0,==0,>0三种情况,程序便不会有什么问题.但最近在看公司的源代码时,发现代码中直 ...

  4. <Win32_6>程序员求爱的创意程序^_^

    作为程序员,我们时常被外界误认为很闷.不浪漫.没创意……等等这一类人,这让我们实在有些感伤啊,我得为程序员呐喊一声: 我们也能可以欢快.浪漫.有创意…… 朋友,你向女生表白过吗? …… 这个问题有点儿 ...

  5. Qt on Android:让 Qt Widgets 和 Qt Quick 应用全屏显示

    Android 系统版本号非常多,较新的 4.4 ,较老的 2.3 ,都有人用. Qt on Android 开发的 Android 应用.默认在 Android 设备上是非全屏的. 而有些应用的需求 ...

  6. jquery之批量上传图片

    //var btn; /** * * 获取当前时间 */ ==================================js=================================== ...

  7. mysql常用操作(转自阿铭linux)

    在前面两个章节中已经介绍过MySQL的安装了,但是光会安装还不够,您还需要会一些基本的相关操作.当然了,关于MySQL的内容也是非常多的,只不过对于linux系统管理员来讲,一些基本的操作已经可以应付 ...

  8. MS-SQL数据库备份方法

    一.手动备份 打开企业管理器 --> 右键点击需要备份的数据库 --> 所有任务 --> 备份数据库 或者: 查询分析器: use master  backup database 数 ...

  9. eclipse 汉化

    对于: Eclipse Standard/SDK Version: Luna Release (4.4.0) 对应的网络地址:http://download.eclipse.org/technolog ...

  10. angular 指令梳理 —— checkBox

    checkBox 持久化数据为 逗号分割 /** * 功能说明: * htCheckbox 指令用于收集checkbox数据. * 在页面中使用 * 属性指令:ht-checkbox * 对应的值为s ...