void groupRectangles(vector<Rect>& rectList, int groupThreshold, double eps, vector<int>* weights, vector<double>* levelWeights)
1. 调用partition,将所有的矩形框初步分为几类,其中出参labels为每个矩形框对应的类别编号,入参eps为判断两个矩形框是否属于同一类的控制参数。如果两个矩形框的四个相应顶点的差值的绝对值都在deta范围内,则认为属于同一类,否则是不同类。
其中deta = eps*(std::min(r1.width, r2.width) + std::min(r1.height, r2.height))*0.5;
返回值nclasses为类别数目。
int nclasses = partition(rectList, labels, SimilarRects(eps));
2. 计算每一类别的平均矩形框位置,即每一个类别最终对应一个矩形框。
3. 将2中得到的所有矩形框再次过滤
(1)将每一类别中矩形框个数较少的类别过滤掉。每一类中的矩形框个数是由可以由partition分类得出的,即可以得出每一类有多少个矩形框。而步骤2仅仅是将这些个矩形框求其位置平均值。过滤的个数阈值为入参groupThreshold,只有当某一个类别中的矩形框个数大于此阈值,才保留该类别。
(2)将嵌在大矩形框内部的小矩形框过滤掉。最后剩下的矩形框即为聚类的结果。
下面介绍一下partition函数
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
// nodes[i][PARENT]=-1表示无父节点,所有节点初始化为单独的节点
for(i = 0; i < N; i++)
{
nodes[i][PARENT]=-1;
nodes[i][RANK] = 0;
}
// The main O(N^2) pass: merge connected components
//每一个节点都和其他所有的节点比较,看是否是属于同一类
//属于同一类的判断:predicate(vec[i], vec[j]), predicate为传入的SimilarRects
//SimilarRects判断两个矩形框的四个相应顶点的差值的绝对值都在deta范围内,
//则认为属于同一类,否则是不同类
//两层for循环和后面的压缩策略保证了最终形成以很多类,每一类以根节点为中心,、、//其余该类的节点的父坐标都指向根节点
for( i = 0; i < N; i++ )
{
// find root寻找根节点,每次都是和每个节点对应的根节点比较
//如果是单独的节点,根节点就是本身
int root = i;
while( nodes[root][PARENT] >= 0 )
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 )
root2 = nodes[root2][PARENT];
if( root2 != root ) //保证已经连接在同一类的不再连接
{
// unite both trees
//rank表示级别,根节点rank大为0,普通点rank为0,并且根节点的rank
//随着连接同级根节点的次数的增多而增大
int rank = nodes[root][RANK], rank2 = nodes[root2][RANK];
if( rank > rank2 ) //root为根节点,root2是单独点,将root2连接到root上,根节点不变
nodes[root2][PARENT] = root;
else //当root和root2都为根节点时,将root连接到root2上,并将root2对应的rank加1;当root2为根节点,root是单独点,将root连接到root2上。二者都将根节点更新为root2
{
nodes[root][PARENT] = root2;
nodes[root2][RANK] += rank == rank2;
root = root2;
}
assert( nodes[root][PARENT] < 0 );//根节点的parent必须小于0
int k = j, parent;
// compress the path from node2 to root
//下一级节点通过它的根节点连接到上一级根节点时,直接将下一级节点和根节点都连接到上级的根节点,如果是单独的节点连接到某个根节点,循环不改变任何值
while( (parent = nodes[k][PARENT]) >= 0 )
{
nodes[k][PARENT] = root;
k = parent;
}
// compress the path from node to root
//同一级节点通过它的根节点连接到同级的根节点时,直接将该节点和根节点都连接到同级的根节点,如果是单独的节点连接到某个根节点,循环不改变任何值
k = i;
while( (parent = nodes[k][PARENT]) >= 0 )
{
nodes[k][PARENT] = root;
k = parent;
}
}
}
}
// Final O(N) pass: enumerate classes
labels.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 label
if( nodes[root][RANK] >= 0 )//小于0,则已经统计过了
nodes[root][RANK] = ~nclasses++;
labels[i] = ~nodes[root][RANK]; //每个根节点保存着类别ID的非值,其非值小于0
}
return nclasses;
}