opencv-NMS方法理解


第一步:将所有矩形框进行初步分类。分类原则是依据矩形框的相似性进行归类。

第二步:计算上步分类后的每一类别的平均矩形框位置,即每一个类别最终对应一个矩形框。

第三部:将第二步得到的矩形框再次进行过滤。过滤原理:1)将每一个类别中矩形框个数较少的类别过滤掉。2)将嵌在大矩形框内部的小矩形框过滤掉。剩下的矩形框为最终融合的结果。

partition函数详解

  1. template<typename _Tp, class _EqPredicate> int  
  2. partition( const std::vector<_Tp>& _vec, std::vector<int>& labels,  
  3.           _EqPredicate predicate=_EqPredicate())  
  4. {  
  5.     int i, j, N = (int)_vec.size();  
  6.     const _Tp* vec = &_vec[0];  
  7.   
  8.     const int PARENT=0;  
  9.     const int RANK=1;  
  10.   
  11.     std::vector<int> _nodes(N*2);  
  12.     int (*nodes)[2] = (int(*)[2])&_nodes[0];  
  13.   
  14.     // The first O(N) pass: create N single-vertex trees  
  15.     // nodes[i][PARENT] = -1表示无父节点,所有节点初始化为单独的节点  
  16.     for(i = 0; i < N; i++)  
  17.     {  
  18.         nodes[i][PARENT]=-1;  
  19.         nodes[i][RANK] = 0;  
  20.     }  
  21.   
  22.     // The main O(N^2) pass: merge connected components  
  23.     // 每一个节点都和其他所有节点比较,看是否属于同一类  
  24.     // 属于同一类的判断 predicate(vec[i], vec[j]),predicate为传入的SimilarRects  
  25.     // SimilarRects判断两个矩形框的四个相应顶点的差值的绝对值都在deta范围内,则认为属于同一类,否则是不同类  
  26.     // 两层for循环和后面的压缩策略保证了最终形成很多类,每一类以根节点为中心,该类的其余节点的父节点指向根节点  
  27.     for( i = 0; i < N; i++ )  
  28.     {  
  29.         int root = i;  
  30.   
  31.         // find root  
  32.         // 寻找根节点,每次都是和每个节点对应的根节点比较,如果是单独的节点,根节点就是本身  
  33.         while( nodes[root][PARENT] >= 0 )  
  34.             root = nodes[root][PARENT];  
  35.   
  36.         for( j = 0; j < N; j++ )  
  37.         {  
  38.             // 同一节点或两个节点的矩形框差距大,则不连接  
  39.             if( i == j || !predicate(vec[i], vec[j]))  
  40.                 continue;  
  41.             int root2 = j;  
  42.   
  43.             // 寻找可以归为同一类节点的根节点,每次都是和对应的根节点先链接  
  44.             // 即比较两个节点的矩形框,连接时,使用两个节点对应的两个根节点  
  45.             // 这样保证了已经连接在同一类的不在连接,不同类的也容易连接  
  46.             while( nodes[root2][PARENT] >= 0 )  
  47.                 root2 = nodes[root2][PARENT];  
  48.             // 保证已经连接在同一类的不再连接  
  49.             if( root2 != root )  
  50.             {  
  51.                 // unite both trees  
  52.                 // rank表示级别,根节点rank大为0,普通点rank为0,并且根节点的rank随着连接同级根节点的次数增多而增大  
  53.                 int rank = nodes[root][RANK], rank2 = nodes[root2][RANK];  
  54.                 // root为根节点,root2为单独节点,将root2连接到root上,根节点不变  
  55.                 if( rank > rank2 )  
  56.                     nodes[root2][PARENT] = root;  
  57.                 // 当root和root2都为根节点,将root连接到root2,并将root2对应的rank加1,root2为根节点,root为单独点,将root连接  
  58.                 // 到root2上。二者都将根节点更新为root2  
  59.                 else  
  60.                 {  
  61.                     nodes[root][PARENT] = root2;  
  62.                     nodes[root2][RANK] += rank == rank2;  
  63.                     root = root2;  
  64.                 }  
  65.                 // 根节点的parent必须小于0  
  66.                 CV_Assert( nodes[root][PARENT] < 0 );  
  67.   
  68.                 int k = j, parent;  
  69.   
  70.                 // compress the path from node2 to root  
  71.                 // 下一级节点通过它的根节点连接到上一级根节点时,直接将下一级节点和根节点都连接到上级的根节点  
  72.                 // 如果是单独的节点连接到某个根节点,循环不改变任何值  
  73.                 while( (parent = nodes[k][PARENT]) >= 0 )  
  74.                 {  
  75.                     nodes[k][PARENT] = root;  
  76.                     k = parent;  
  77.                 }  
  78.   
  79.                 // compress the path from node to root  
  80.                 // 同一级节点通过它的根节点连接到同级的根节点,直接将该节点和根节点都连接到同级的根节点,如果是单独  
  81.                 // 的节点连接到某个根节点,循环不改变任何值  
  82.                 k = i;  
  83.                 while( (parent = nodes[k][PARENT]) >= 0 )  
  84.                 {  
  85.                     nodes[k][PARENT] = root;  
  86.                     k = parent;  
  87.                 }  
  88.             }  
  89.         }  
  90.     }  
  91.   
  92.     // Final O(N) pass: enumerate classes  
  93.     labels.resize(N);  
  94.     // 总分类数  
  95.     int nclasses = 0;  
  96.   
  97.     for( i = 0; i < N; i++ )  
  98.     {  
  99.         int root = i;  
  100.         while( nodes[root][PARENT] >= 0 )  
  101.             root = nodes[root][PARENT];  
  102.         // re-use the rank as the class label  
  103.         // 小于0,则已经统计过  
  104.         if( nodes[root][RANK] >= 0 )  
  105.             nodes[root][RANK] = ~nclasses++;  
  106.         // 每个根节点保存着类别ID的非值,其非值小于0  
  107.         labels[i] = ~nodes[root][RANK];  
  108.     }  
  109.   
  110.     return nclasses;  
  111. }  
  112.   
  113. // cv  

groupRectangle函数实现矩形框聚合。原因:多尺度检测后,获取的矩形之间会存在重合、重叠和包含关系。因尺度缩放,可能导致同一个目标在多个尺度上被检测出来,故有必要进行融合。OpenCV中实现的融合有两种:1)按权重合并;2)使用Meanshift算法进行合并。

下面是简单的合并,其直接按照位置和大小关系进行合并。其实现主要为:1)多所有矩形按照大小位置合并成不同的类别;2)将同类别中的矩形合并成一个矩形,当不满足给出阈值条件时,矩形被舍弃,否则留下。

  1. //函数主要功能为对矩形框进行融合操作。  
  2. void groupRectangles(std::vector<Rect>& rectList, int groupThreshold, double eps,  
  3.                      std::vector<int>* weights, std::vector<double>* levelWeights)  
  4. {  
  5.     if( groupThreshold <= 0 || rectList.empty() )  
  6.     {  
  7.         if( weights )  
  8.         {  
  9.             size_t i, sz = rectList.size();  
  10.             weights->resize(sz);  
  11.             for( i = 0; i < sz; i++ )  
  12.                 (*weights)[i] = 1;  
  13.         }  
  14.         return;  
  15.     }  
  16.   
  17.     std::vector<int> labels;  
  18.     // 调用partition函数,将所有的矩形框初步分为几类,其中labels为每个矩形框对应的类别编号,eps为判断两个矩形框是否属于  
  19.     // 同一类的控制参数。如果两个矩形框的四个相应顶点的差值的绝对值都在deta范围内,则认为属于同一类,否则是不同类。  
  20.     int nclasses = partition(rectList, labels, SimilarRects(eps));  
  21.   
  22.     std::vector<Rect> rrects(nclasses);  
  23.     std::vector<int> rweights(nclasses, 0);  
  24.     std::vector<int> rejectLevels(nclasses, 0);  
  25.     std::vector<double> rejectWeights(nclasses, DBL_MIN);  
  26.     int i, j, nlabels = (int)labels.size();  
  27.     for( i = 0; i < nlabels; i++ )  
  28.     {  
  29.         int cls = labels[i];  
  30.         rrects[cls].x += rectList[i].x;  
  31.         rrects[cls].y += rectList[i].y;  
  32.         rrects[cls].width += rectList[i].width;  
  33.         rrects[cls].height += rectList[i].height;  
  34.         rweights[cls]++;  
  35.     }  
  36.   
  37.     bool useDefaultWeights = false;  
  38.   
  39.     if ( levelWeights && weights && !weights->empty() && !levelWeights->empty() )  
  40.     {  
  41.         for( i = 0; i < nlabels; i++ )  
  42.         {  
  43.             int cls = labels[i];  
  44.             if( (*weights)[i] > rejectLevels[cls] )  
  45.             {  
  46.                 rejectLevels[cls] = (*weights)[i];  
  47.                 rejectWeights[cls] = (*levelWeights)[i];  
  48.             }  
  49.             else if( ( (*weights)[i] == rejectLevels[cls] ) && ( (*levelWeights)[i] > rejectWeights[cls] ) )  
  50.                 rejectWeights[cls] = (*levelWeights)[i];  
  51.         }  
  52.     }  
  53.     else  
  54.         useDefaultWeights = true;  
  55.     // 计算每一类别的平均矩形框位置,即每一个类别最终对应一个矩形框  
  56.     for( i = 0; i < nclasses; i++ )  
  57.     {  
  58.         Rect r = rrects[i];  
  59.         float s = 1.f/rweights[i];  
  60.         rrects[i] = Rect(saturate_cast<int>(r.x*s),  
  61.              saturate_cast<int>(r.y*s),  
  62.              saturate_cast<int>(r.width*s),  
  63.              saturate_cast<int>(r.height*s));  
  64.     }  
  65.   
  66.     rectList.clear();  
  67.     if( weights )  
  68.         weights->clear();  
  69.     if( levelWeights )  
  70.         levelWeights->clear();  
  71.     // 再次过滤上面分类中得到的所有矩形框  
  72.     for( i = 0; i < nclasses; i++ )  
  73.     {  
  74.         Rect r1 = rrects[i];  
  75.         int n1 = rweights[i];  
  76.         double w1 = rejectWeights[i];  
  77.         int l1 = rejectLevels[i];  
  78.   
  79.         // filter out rectangles which don't have enough similar rectangles  
  80.         // 将每一类别中矩形框个数较少的类别过滤掉。  
  81.         if( n1 <= groupThreshold )  
  82.             continue;  
  83.         // filter out small face rectangles inside large rectangles  
  84.         // 将嵌在大矩形框内部的小矩形框过滤掉。最后剩下的矩形框为聚类的结果。  
  85.         for( j = 0; j < nclasses; j++ )  
  86.         {  
  87.             int n2 = rweights[j];  
  88.   
  89.             if( j == i || n2 <= groupThreshold )  
  90.                 continue;  
  91.             Rect r2 = rrects[j];  
  92.   
  93.             int dx = saturate_cast<int>( r2.width * eps );  
  94.             int dy = saturate_cast<int>( r2.height * eps );  
  95.   
  96.             if( i != j &&  
  97.                 r1.x >= r2.x - dx &&  
  98.                 r1.y >= r2.y - dy &&  
  99.                 r1.x + r1.width <= r2.x + r2.width + dx &&  
  100.                 r1.y + r1.height <= r2.y + r2.height + dy &&  
  101.                 (n2 > std::max(3, n1) || n1 < 3) )  
  102.                 break;  
  103.         }  
  104.   
  105.         if( j == nclasses )  
  106.         {  
  107.             rectList.push_back(r1);  
  108.             if( weights )  
  109.                 weights->push_back(useDefaultWeights ? n1 : l1);  
  110.             if( levelWeights )  
  111.                 levelWeights->push_back(w1);  
  112.         }  
  113.     }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值