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中元素打上对应的标签。
bwlabel函数的c++实现的更多相关文章
- Python实现MATLAB中的 bwlabel函数
最近做验证码识别,原本用MATLAB已经实现的整个识别模型,不过代码要部署在Linux服务器上还是需要用另外的语言实现,于是决定用Python + OpenCV来实现. bwlabel函数的作用是检测 ...
- Matlab图像处理函数:regionprops
本篇文章为转载,仅为方便学术讨论所用,不用于商业用途.由于时间较久,原作者以及原始链接暂时无法找到,如有侵权以及其他任何事宜欢迎跟我联系,如有侵扰,在此提前表示歉意.----------------- ...
- matlab函数大全
Matlab 图像处理相关函数命令大全 一.通用函数: colorbar 显示彩色条 语法:colorbar \ colorbar('vert') \ colorbar('horiz') \ col ...
- matlab boundaries和fchcode函数无法执行的解决办法 未定义与 'double' 类型的输入参数相对应的函数 'boundaries'
在测试代码时发现,自己的matlab无法执行Freeman链码函数: boundaries和fchcode函数都无法正常运行: 需要在自己的工作目录中添加如下函数: boundaries fchc ...
- MATLAB中图像处理的函数
表1 图像显示 函数名 功能说明 函数名 功能说明 colorbar 颜色条显示 montage 按矩形剪辑方式显示多帧图像 getimage 从坐标系中获取图像数据 immovie 从多帧索引图像中 ...
- Python 小而美的函数
python提供了一些有趣且实用的函数,如any all zip,这些函数能够大幅简化我们得代码,可以更优雅的处理可迭代的对象,同时使用的时候也得注意一些情况 any any(iterable) ...
- 探究javascript对象和数组的异同,及函数变量缓存技巧
javascript中最经典也最受非议的一句话就是:javascript中一切皆是对象.这篇重点要提到的,就是任何jser都不陌生的Object和Array. 有段时间曾经很诧异,到底两种数据类型用来 ...
- JavaScript权威指南 - 函数
函数本身就是一段JavaScript代码,定义一次但可能被调用任意次.如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法.用于初始化一个新创建的对象的函数被称作构造函数. 相对 ...
- C++对C的函数拓展
一,内联函数 1.内联函数的概念 C++中的const常量可以用来代替宏常数的定义,例如:用const int a = 10来替换# define a 10.那么C++中是否有什么解决方案来替代宏代码 ...
随机推荐
- redis 学习笔记二 (简单动态字符串)
redis的基本数据结构是动态数组 一.c语言动态数组 先看下一般的动态数组结构 struct MyData { int nLen; char data[0]; }; 这是个广泛使用的常见技巧,常用来 ...
- MVC MVC 路由详解
在项目中我们引用了System.Web.Routing; Routing的作用: 确定Controller 确定Action 确定其他参数 根据识别出来的数据, 将请求传递给Controller和 ...
- 原生javascript 改写的tab选项卡
<!--css部分--> <style> *{ margin: 0; padding: 0; } ul,li{ list-style: none } .tabbox{ widt ...
- 如何安装CocoaPods
转自 http://www.99css.com/1321/ 在 iOS 项目开发中,经常会用到第三方的源代码,CocoaPods 就是为了方便管理这些源码的工具. 在官方教程里面,安装看起来非常简单 ...
- MySQL具体解释(7)-----------MySQL线程池总结(一)
线程池是Mysql5.6的一个核心功能.对于server应用而言,不管是web应用服务还是DB服务,高并发请求始终是一个绕不开的话题.当有大量请求并发訪问时,一定伴随着资源的不断创建和释放.导致资源利 ...
- Android 打造形形色色的进度条 实现可以如此简单
转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/43371299 ,本文出自:[张鸿洋的博客] 1.概述 最近需要用进度条,秉着不重 ...
- 浪潮服务器通过ipmitool获取mac地址
一.GPU服务器 #配置两个主板集成千兆四个外插PCI万兆网卡# 板载网卡可以使用命令获取到:RAW 0X30 0X21 就可以读取到第一块网卡的MAC,就是以下返回值的后6位. 0c,c4,7a,5 ...
- RMAN-configure命令
在Oracle 10g中的配置情况 使用RMAN>show all; 可以显示出RMAN 配置参数为: CONFIGURE RETENTION POLICY TO REDUNDANCY 1; # ...
- 针对淡入淡出的定时轮播效果js
如果不使用jquery的fadeIn和fadeOut的接口和不适用animate情况下,如果要做用js实现淡入淡出轮播效果,我所想到的办法就是使用css3新特性transition(注意好兼容性). ...
- 《第一行代码》学习笔记15-UI(4)
1.ListView允许用户通过手指上下滑动的方式将屏幕外的数据滚动到屏幕内,同时屏幕上原有的数据则会滚动出屏幕. 2.数组中的数据无法直接传递给ListView,得借助适配器来完成.此处使用Arra ...