转载:http://www.aichengxu.com/view/1501260

前一篇文章分析了OpenCV级联分类器结构,即“强分类器串联,弱分类器并联”,这一节我们来聊聊一些非常必要但是又容易人忽略的细节:如何利用并查集合并检测结果窗口。

-------------------------------------------
在上一篇文章中,我曾提到:级联分类器通过移动检测窗口寻找图像中不同位置的目标,同时通过放大检测窗口(或缩放被检测图像)寻找图像中大小不同的目标,最终寻找到图像中不同位置、不同的大小的所有目标。那么必然存在这样的情况:一个被检测器检测为目标的窗口,其附近窗口也应该被检测到。例如在图像中的[x,y, w, h]窗口内检测到了人脸,那么[x-1, y-1, w+2, h+2]窗口也有极大的可能性被检测到,毕竟这2个窗口中的图像并没有明显差别(只是多了一个边而已)。
图1展示了使用haarcascade_frontalface_alt2.xml检测一副含有人脸图像的结果,左边为合并检测结果窗口之前的结果,右边为合并之后的结果。从图1左边可以看出,每个目标(人脸)附近都有一组重叠检测结果窗口,除此之外还有零散分布的误检结果窗口。看到这里你应该明白了有必要对重叠的检测结果窗口进行合并,同时剔除零散分布的错误检测窗口。

图1 检测结果合并窗口前后对比图
(一)并查集(Union-Set)[/align]
在了解如何合并窗口前,先来了解一种数据结构——并查集。

为了形象的说明并查集,首先来了解一个例子。江湖上存在各种各样的大侠,他们没什么正当职业,整天背着剑四处游荡,碰到其他大侠就大打出手。俗话说“双拳难敌四手”,这些大侠都会拉帮结派壮大实力。那么为了辨识每个大侠属于哪个帮派,就需要每个帮派都推举一个“老大”。这些大侠只需要知道自己和其他大侠的老大是不是同一个人,就能明白自己和对方是不是一个帮派,从而决定是否动手过招。

图2江湖大侠关系图(箭头表示上一级)
如图2,现在武当派和明教分别推举了张三丰和张无忌作为老大。当帮派很大时组,每个大侠无法完整记住自己所在帮派的组织结构,那么他们只需要记住自己的上一级是谁,一级一级往上问就知道老大是谁了。某日,宋青书和殷梨亭在武当山门口遇到了,那么宋青书问宋远桥后得知自己的老大是张三丰,而殷梨亭的老大也是张三丰,那么他俩肯定是同门,必须不能动手了。而当宋青书遇到陈友谅时,一级一级向上询问后发现老大不是一个人,那就要拔剑分个你死我活。
在武林中,仅仅结成帮派是不够的,还需要通过其他关系组建帮派联盟扩大势力。既然杨不悔嫁给了殷梨亭,不妨直接设将明教老大张无忌的上级设置为武当派老大张三丰,这样就可以将明教和武当组成一个更大的联盟(如图2红色虚线,这里只关心数据的代表,忽略数据的内部结构)。例如,以后当宋青书和范右使相遇,一级一级往上问后发现老大都是张三丰,他俩就知道自己是一伙人了。但是,每次宋青书和范右使相遇,都像这样一级一级往上问速度很慢,而且次数多了上级大侠也会不耐烦。为了解决这个问题,需要压缩路径。当宋青书和范右使知道自己的老大是张三丰时,可以把自己的上司改为张三丰,这样就不必每次去问上级了,同时路径上的上级大侠也都把自己的上级改为老大张三丰。以范右使为例,压缩路径过程如下:

1.范右使问张无忌:我的老大是谁
2.张无忌的上级就是老大张三丰,所以张无忌答复范右使:你的老大是张三丰
3.范右使知道自己的老大是张三丰,然后把自己的上级改为张三丰

宋青书询问老大过程类似。当宋青书与范右使相遇后,问完老大后压缩路径结果如图3,下次他们遇到不用问上级就能就知道知道自己的老大是谁了。

图3压缩路径后的江湖大侠关系图

并查集保持一组不相交的动态集合S={S1,S2,...,Sk},每个动态集合Si通过一个代表ai来识别,代表是集合中的某个元素(ai∈Si)。在某些应用中,哪一个元素被选为代表是无所谓的,我们只关心在不修改动态集合的前提下分别寻找某一集合的代表2次获得的结果相同;在另外一些应用中,如何选择集合的代表可能存在预先说明的规则,如选择集合的最大or最小值作为代表。总之,在并查集中,不改变动态集合S则每个集合Si的代表ai不变。
        不妨设x表示每个结点,p[x]表示x的父结点(即例中的上一级,如图2中p[宋远桥]==张三丰),rank[x]表示x节点的秩(即该节点最长路径中结点个数,如图2中最长路径为:张三丰-张无忌-杨左使-杨不悔,所以rank[张三丰]==4)。并查集伪代码如下:

MAKE-SET(x)
1 p[x] ← x
2 rank[x] ← 0UNION(x, y)
1 LINK(FIND-SET(x), FIND-SET(y))LINK(x, y)
1 if rank[x] < rank[y]
2     p[x] ← y
3 else
4     p[y] ← x
5     if rank[x]==rank[y]
6         rank[x] = rank[x] + 1FIND-SET(x)
1 if x ≠ p[x]
2     p[x] ← FIND-SET(p[x])
3 return p[x]

其中,MAKE-SET函数用于在无序数据中初始化并查集数据结构,将每个结点父结点设为其本身;UNION函数通过调用LINK和FIND-SET实现带压缩路径的并查集合并;LINK函数通过秩进行并查集合并;FIND-SET是带压缩路径的寻找结点代表的函数。
还是以图2为例说明UNION函数。上次直接将明教老大张无忌的上级设置为了武当老大张三丰(如上文蓝色字体),导致了后续宋青书和范右使见面时查询老大路径太长。本着拒绝拖延症的原则,希望在合并明教和武当派时就提前压缩路径,不把问题留给宋青书和范右使。既然上面伪代码中的UNION是带压缩路径的合并结点函数,我们来看看调用UNION(殷梨亭,杨不悔)会发生什么:

1. 调用UNION(殷梨亭,杨不悔)。

在调用LINK(FIND-SET(殷梨亭),FIND-SET(杨不悔))前,首先调用FIND-SET(殷梨亭)和FIND-SET(杨不悔)。

2. 调用FIND-SET(殷梨亭)和FIND-SET(杨不悔)。

FIND-SET压缩路径函数通过递归把查询路径上每个人的上级变为本帮派的老大,如杨不悔的上级被设置为张无忌(由于杨左使和范右使的上级就是老大张无忌,所以其上级不变);最后FIND-SET返回结点的老大,如FIND-SET(殷梨亭)返回张三丰,FIND-SET(杨不悔)返回张无忌。

3. 压缩路径后进行合并,即调用LINK(张三丰,张无忌)。

由于rank[殷梨亭]==rank[杨不悔]==2秩相等,所以设置p[FIND-SET(杨不悔)]←FIND-SET(杨不悔),即设置p[张无忌]←张三丰,所以最终结果图为图4(注意:图3是查询时压缩路径,图4是合并时压缩路径)。

图4

如果还有不明白的地方,建议查阅《算法导论》中的第21章:《用于不相交的数据结构》。

(二)利用并查集合并检测结果窗口

为了将并查集利用到合并窗口中,首先要定义窗口相似函数,即定义当前的2个窗口是不是“一伙人”。在OpenCV中,图像中的矩形窗口一般用Rect结构体表示,其包含x,y,width,height共4个成员变量,分别代表窗口的左上角点x坐标、y坐标、宽度和高度。下面代码定义了窗口相似函数SimilarRects::operator(),当2个窗口r1和r2位置很接近时返回TRUE,通过SimilarRects::operator()就可以将图1那些重叠的窗口合并在“一伙人”中。

class CV_EXPORTS SimilarRects
{
public:SimilarRects(double _eps) : eps(_eps) {}inline bool operator()(const Rect& r1, const Rect& r2) const{double delta = eps*(std::min(r1.width, r2.width) + std::min(r1.height, r2.height))*0.5;return std::abs(r1.x - r2.x) <= delta &&std::abs(r1.y - r2.y) <= delta &&std::abs(r1.x + r1.width - r2.x - r2.width) <= delta &&std::abs(r1.y + r1.height - r2.y - r2.height) <= delta;}double eps;
}

定义好窗口相似性函数后,就可以利用并查集合并窗口函数了,大致过程如下:

1. 首先利用MAKE-SET函数建立Rect对象的并查集初始结构;
2. 然后遍历整个并查集,用SimilarRects::operator()判断每2个窗口相似性,若相似则将这2个窗口合并为“一伙人”;
3. 运行完步骤2后应该出现几个相互间不相似的窗口“团伙”,当“团伙”中的窗口数量小于阈值minNeighbors时,丢弃该“团伙”(认为这是零散分布的误检);
4. 之后剩下若干组由大量重叠窗口组成的大“团伙”,分别求每个“团伙”中的所有窗口位置的平均值作为最终检测结果。

下面的展示了OpenCV实现步骤1-2并查集归类窗口的代码。在之前算法描述中为了清晰简洁,使用递归实现了整个并查集;但在实际中递归需要保存现场并进行压栈,开销极大,所以OpenCV使用循环替代了递归。

// This function splits the input sequence or set into one or more equivalence classes and
// returns the vector of labels - 0-based class indexes for each element.
// predicate(a,b) returns true if the two sequence elements certainly belong to the same class.
//
// The algorithm is described in "Introduction to Algorithms"
// by Cormen, Leiserson and Rivest, the chapter "Data structures for disjoint sets"
template<typename _Tp, class _EqPredicate> int
partition( const vector<_Tp>& _vec, vector<int>& labels,_EqPredicate predicate=_EqPredicate())
{int i, j, N = (int)_vec.size();const _Tp* vec = &_vec[0];const int PARENT=0;const int RANK=1;vector<int> _nodes(N*2);int (*nodes)[2] = (int(*)[2])&_nodes[0];// The first O(N) pass: create N single-vertex trees // 即MAKE-SET,建立初始并查集结构for(i = 0; i < N; i++){nodes[i][PARENT]=-1;nodes[i][RANK] = 0;}// The main O(N^2) pass: merge connected components // 即UNION,合并相似窗口for( i = 0; i < N; i++ ){int root = i;// find rootwhile( nodes[root][PARENT] >= 0 ) // 即FIND-SET(root),寻找root的老大并压缩路径root = nodes[root][PARENT];for( j = 0; j < N; j++ ){if( i == j || !predicate(vec[i], vec[j]))continue;int root2 = j;while( nodes[root2][PARENT] >= 0 ) // 即FIND-SET(root2),寻找root2的老大并压缩路径root2 = nodes[root2][PARENT];if( root2 != root ) // 即LINK(root, root2){// unite both treesint rank = nodes[root][RANK], rank2 = nodes[root2][RANK];if( rank > rank2 )nodes[root2][PARENT] = root;else{nodes[root][PARENT] = root2;nodes[root2][RANK] += rank == rank2;root = root2;}assert( nodes[root][PARENT] < 0 );int k = j, parent;// compress the path from node2 to rootwhile( (parent = nodes[k][PARENT]) >= 0 ){nodes[k][PARENT] = root;k = parent;}// compress the path from node to rootk = i;while( (parent = nodes[k][PARENT]) >= 0 ){nodes[k][PARENT] = root;k = parent;}}}}// Final O(N) pass: enumerate classeslabels.resize(N);int nclasses = 0;for( i = 0; i < N; i++ ){int root = i;while( nodes[root][PARENT] >= 0 )root = nodes[root][PARENT];// re-use the rank as the class labelif( nodes[root][RANK] >= 0 )nodes[root][RANK] = ~nclasses++;labels[i] = ~nodes[root][RANK];}return nclasses;
}

-------------------------------------------
参考文献:

[1] Thomas H.Cormen、Charles E.Leiserson等.《算法导论》
[2] http://blog.csdn.net/dellaserss/article/details/7724401

Haar+Adaboost级联分类器分解(三):利用并查集合并检测结果窗口相关推荐

  1. Haar特征级联分类器的训练与检测

    Haar特征级联分类器的训练与检测 1. 样本的创建 1.1 准备正样本图片集 1.2 截取目标区域 1.3 创建正样本描述文件vec文件 1.4 准备负样本图片集 1.5 创建负样本描述文件 2. ...

  2. 基于OpenCV Haar实战级联分类器的使用

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 近年来,对象检测引起了广泛的关注.从智能手机到交通监控,目标检测已 ...

  3. 照片美妆---基于Haar特征的Adaboost级联人脸检测分类器

    本文转载自张雨石http://blog.csdn.net/stdcoutzyx/article/details/34842233 基于Haar特征的Adaboost级联人脸检测分类器 基于Haar特征 ...

  4. 【youcans 的图像处理学习课】22. Haar 级联分类器

    专栏地址:『youcans 的图像处理学习课』 文章目录:『youcans 的图像处理学习课 - 总目录』 [youcans 的图像处理学习课]22. Haar 级联分类器 3. Haar 特征及其加 ...

  5. Haar级联分类器概述

    Haar级联分类器概述 -- 才疏学浅, 难免有错误和遗漏, 欢迎补充和勘误. Haar级联分类器是基于Haar-like特征,运用积分图加速计算,并用Adaboost训练的强分类器级联的方法来进行人 ...

  6. OpenCV实战5: LBP级联分类器实现人脸检测

    OpenCV中的HAAR与LBP数据 HAAR特征数据   参看 haarcascade_frontalface_alt.xml 各标签     LBP特征数据     参看 lbpcascade_f ...

  7. 【OpenCV】 级联分类器训练模型

    目录 一:OpenCV级联分类器概念 二:OpenCV级联分类器操作步骤 三:样本采集工作原理分析一 四:样本采集工作原理分析二 五:创建自己的级联分类器 5.1 创建自己的级联分类器第一步 5.2 ...

  8. cascade自己训练级联分类器(人脸检测)

    cascade训练简介 后面有详细解释,和步骤代码的实现,可能需要简单修改,建议代码自己先写着试试 训练步骤 下载文件包opencv.这里的opencv是指编译好的.exe文件,不是需要编译的sour ...

  9. OpenCV级联分类器训练与使用实战教程-贾志刚-专题视频课程

    OpenCV级联分类器训练与使用实战教程-1012人已学习 课程介绍         基于OpenCV新版本3.1.0详细讲述了HAAR与LBP级联分类器的基本原理与使用技巧,通过视频中人脸实时检测与 ...

最新文章

  1. 无人机航拍记录生活真爽,包邮送一个!
  2. 【NLP】simhash判断文档相似度
  3. C++ 常见bug记录(持续记录中)
  4. 利用Java的BigDecimal与马青公式精确计算π后10000位,
  5. match与index——vlookup的加强版
  6. NOIP模拟测试15「建造城市city(插板法)·轰炸·石头剪刀布」
  7. 输入一个三位正整数,输出百位数,十位数,个位数
  8. 51nod 1693 水群
  9. 虚拟运营商人工服务器,四大必想之事:倒闭、价格、网络
  10. geohash美团附近酒店搜索-技术方案
  11. Git教程_2 所有操作讲解
  12. Android系统是目前最为流行的手机系统之一
  13. 随便说说,中国开发人员的不同层次和一些思考。
  14. 简单的玻璃材质效果——UnityShader学习笔记
  15. python - 作业13:打地鼠小游戏(附代码)
  16. Codeforces Round #744 (Div. 3)
  17. [033] 微信公众帐号开发教程第9篇-QQ表情的发送与接收
  18. R语言:无法精确计算带连结的p值
  19. 跳槽真的难吗?20节专项课揭秘面试潜规则
  20. Windows vc++运行库安装,Microsoft Visual C++ Build Tools官方工具

热门文章

  1. 国信证券学习系列(8)
  2. 搭建Nginx服务器——详细步骤
  3. 快速高效 | Android身份证识别
  4. 华为升级后打不开htmL文件了,为什么192.168.3.1进不去 华为192.168.3.1登录页面打不开-192路由网...
  5. XnView 1.95.4 正式版
  6. input标签里面type常用属性(注册登录表单常用元素)
  7. 2008“搞笑诺贝尔奖”颁布
  8. Android开发全程记录(九)——使用新浪微博登录第三方应用
  9. (汇编)输出数字、大写、小写字母
  10. cv2.eigen(hessian)