关于Two-Pass标记连通域个数
关于Two-Pass标记连通域个数
背景
在完成图像的一系列处理后,得到二值图,一般会统计目标数量,即是获取连通域个数,这里采用TwoPass的方法。
基本思想
在Two-pass连通域标记中,第一次标记(first pass)时从左向右,从上向下扫描,会将各个有效像素置一个label值,判断规则如下(以4邻域为例):
- 当该像素的左邻像素和上邻像素为无效值时,给该像素置一个新的label值,label ++;
- 该像素的左邻像素或者上邻像素有一个为有效值时,将有效值像素的label赋给该像素的label值;
- 当该像素的左邻像素和上邻像素都为有效值时,选取其中较小的label值赋给该像素的label值。
此时,还需维护一个关系表,记录哪些label值属于同一个连通域。这个关系表通常用union-find数据结构来实现。
原文在这
还有个图:

原文说用union-find结构保存连通域,没仔细看,于是自己用了个数组保存,类似于hash...实现起来也并不是很麻烦,大概这样,在开始建立一个map数组固定大小,一般而言应该与用于标记的最大数一致(注意,这里是标记数,是小于连通域数的),这里取uint16最大值,65535,这样也就意味着连通域数不能太大,估计最大能达到万把个吧,因为不同的图标记数和连通域数关系是不一样的。
在FirstPass时,取周围点的标记最小值,map[标记]=min,这样就构成了map数组,然后根据map数据进行第二次标记。
实现
int ImageAlgorithm::TwoPassConnetedDomin(Mat image) {
Mat imageFlag;
imageFlag.create(image.rows, image.cols, CV_16UC1);
for (int k = 0; k < image.rows; ++k) {
for (int i = 0; i < image.cols; ++i) {
imageFlag.at<ushort>(k, i) = UINT16_MAX;
}
}
uint16_t mapp[UINT16_MAX];
memset(mapp, UINT16_MAX, sizeof(uint16_t));
//第一次扫描,完成ImageFlag中的标记
int num = 0;
for (int i = 0; i < image.rows; ++i) {
for (int j = 0; j < image.cols; ++j) {
auto &cu = image.at<Vec3b>(i, j);
if (cu[0] == 255) {
uint16_t pos[4];
auto &up = pos[0];
auto &left = pos[1];
auto &ul = pos[2];
if (i > 0)
up = imageFlag.at<ushort>(i - 1, j);
if (j > 0)
left = imageFlag.at<ushort>(i, j - 1);
if (i > 0 && j > 0)
ul = imageFlag.at<ushort>(i - 1, j - 1);
uint16_t min = pos[0];
for (int m = 1; m < 3; m++)
if (min > pos[m])
min = pos[m];
for (int m = 0; m < 3; m++) {
if (mapp[pos[m]] > min)
mapp[pos[m]] = min;
}
if (min == UINT16_MAX) {
imageFlag.at<ushort>(i, j) = num;
mapp[num] = num;
num++;
if(num>=UINT16_MAX)
return -1;
} else {
imageFlag.at<ushort>(i, j) = min;
}
}
}
}
//第二次扫描,进行标记,以及修改map
map<ushort, uint32_t> colorMap;
int total = 0;
for (int n = 0; n < num; ++n) {
if (mapp[n] == n) {
total++;
uint32_t t = 0;
for (int i = 0; i < 3; ++i) {
t += (uint32_t) (rand() / (RAND_MAX + 0.0) * 255) << (i * 8);
}
colorMap[n] = t;
} else
mapp[n] = mapp[mapp[n]];
}
for (int i = 0; i < imageFlag.rows; ++i) {
for (int j = 0; j < imageFlag.cols; ++j) {
auto t = imageFlag.at<ushort>(i, j);
if (t != UINT16_MAX) {
uint32_t c = colorMap[mapp[t]];
image.at<Vec3b>(i, j)[0] = (uchar) c;
image.at<Vec3b>(i, j)[1] = (uchar) (c >> 8);
image.at<Vec3b>(i, j)[2] = (uchar) (c >> 16);
}
}
}
return total;
}
关于Two-Pass标记连通域个数的更多相关文章
- two Pass方法连通域检测
原理: Two-Pass方法检测连通域的原理可参见这篇博客:http://blog.csdn.net/lichengyu/article/details/13986521. 参考下面动图,一目了然. ...
- [代码片段]YEAH!连通域标记和计数
//标记的连通域存储在buff[]里 //返回值为连通域个数 int LinkBlob(unsigned char **imagedata,unsigned char buff[], int heig ...
- Java 对二值化图片识别连通域
用Java 对 已经 二值化了的图片 标记连通域 每块的连通域都标记不一样的数字 public static void main(String [] args) throws IOException ...
- CDOJ 1965 连通域统计【DFS】
求连通域个数,简单题 #include <bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; typedef l ...
- nyoj 86 --位标记
nyoj 86 --位标记 点击打开题目链接 : 找球号(一) 这道题目很多解法,其他解法请参考 http://www.cnblogs.com/play ...
- 使用OpenCV进行相机标定
1. 使用OpenCV进行标定 相机已经有很长一段历史了.但是,伴随着20世纪后期的廉价针孔照相机的问世,它们已经变成我们日常生活的一种常见的存在.不幸的是,这种廉价是由代价的:显著的变形.幸运的是, ...
- 使用OpenCV进行标定(转载)
转载自牛猫靖 http://www.cnblogs.com/2008nmj/p/6278076.html 使用OpenCV进行相机标定 1. 使用OpenCV进行标定 相机已经有很长一段历史了.但是 ...
- python之路之线程,进程,协程2
一.线程 1.创建线程 2.主线程是否等待子线程 t.setDaemon(Ture/False):默认是false,等待子线程完成,ture,表示不等待子线程结束 3.主线程等待,子线程执行 join ...
- 剖析虚幻渲染体系(12)- 移动端专题Part 1(UE移动端渲染分析)
目录 12.1 本篇概述 12.1.1 移动设备的特点 12.2 UE移动端渲染特性 12.2.1 Feature Level 12.2.2 Deferred Shading 12.2.3 Groun ...
随机推荐
- mac下安装配置java jdk,maven,resin
mac jdk安装及环境变量配置 安装http://www.ifunmac.com/2013/04/mac-jdk-7/ jdk安装目录:/Library/Java/JavaVirtualMachin ...
- Javascript多线程引擎(九)
Javascript多线程引擎(九)--垃圾回收 垃圾回收这个话题对Programer来说是非常老旧的话题, 从手动的malloc/free 到半自动的 引用计数 再到全自动的 mark-sweep ...
- ftp服务器的搭建
/etc/vsftpd.conf :ftp的配置文件 anonymous_enable=YES:是否支持匿名登录 local_enable=YES:是否支持本地登录 /etc/ftpusers:ftp ...
- hdu 1239 Calling Extraterrestrial Intelligence Again (暴力枚举)
Calling Extraterrestrial Intelligence Again Time Limit: 2000/1000 MS (Java/Others) Memory Limit: ...
- Aforge.net 一个专门为开发者和研究者基于C#框架设计
时间过得真快啊,转眼今年就要过去了,大半年都没有写博客了,要说时间嘛,花在泡妹子和搞英语去了,哈哈...前几天老大问我 怎么这么长时间都没写博客了,好吧,继续坚持,继续分享我的心得体会. 这个系列我们 ...
- c++类的构造函数与析构函数
为什么用构造函数与析构函数 构造函数: c++目标是让使用类对象就像使用标准类型一样,但是常规化的初始化句法不适用与类类型. ; //基本类型 struct thing { char *pn; int ...
- ecexl操作
/// <summary> /// 写入ecexl /// </summary> /// <param name="dt"></param ...
- CKeditor 集成 CKFinder
之前照着网上的做,遇到了一些问题,经过多次实验修改最后算是成功了,下面进行详细讲解. 一.CKeditor的配置(附件中已有最新版CKeditor和CKFinder) 1.需要下载ckeditor, ...
- 解决jqplot与jquery-ui导入必要包时的冲突
解决jqplot与jquery-ui导入必要包时的冲突 对于一个网页中,即要有jqplot的画图,又要有jquery-ui的风格显示! 但在导入必要的包时,出现了问题! 先导入jqplot的必要包: ...
- rsync 文件校验及同步原理
rsync 文件校验及同步原理 参考:http://rsync.samba.org/how-rsync-works.html 我们关注的是其发送与接收校验文件的算法,这里附上原文和我老婆(^_^)的翻 ...