《Object localization by efficient subwindow search》论文中的Efficient Subwindow Search高效的子窗口搜索算法,
Christoph H.Lampert 提出的efficient sub window search 从另一个角度加速检测效率。作者提出Branch-and-Bound Search,其主要思想便是不应该浪费大量的时间来评估大多数的候选检测区域的quality function(分类器响应),而是应该直接检索那些可能含有最大分数的区域,忽略其它的可能区域。那么如何来实现这样的杰出想法呢?
下面首先描述一下其主要的实现策略。迭代地切分参数空间(图像中所有可能的矩形区域)到离散的子区域,并且同时保持这些子区域拥有最大的质量分数上届。
下面我们详细描述并给出算法框图
首先对矩形使用top, bottom, left 和 right坐标值进行标示。使用坐标区间的形式来表示区域的不确定性
,其中
。也就是用其表示一组矩形集。
对于每个矩形集合,我们计算一个在这个集合中quality function(分类函数)在所有矩形区域上能够取得的最大分数,并将其作为上届。当发现一个仅仅含有一个矩形的矩形集合时,并且其quality function分数至少要和所有其他的候选区域的分数上届一样好,那么ESS算法终止。这就保证了一个全局最大分数被发现。
ESS以一种最佳方式在那些候选集合上进行检索,其总是检索那些看起来有最大quality bound(分类响应分数)的区域。然后,候选集合沿着那个最大的坐标区间分割成两半,形成了两个较小的候选子集。如果那个最有希望的候选子集仅仅有一个矩形区域,那么算法停止,这个矩形区域就是目标区域。见下面的伪代码:
这时有人会说,如果一副图中存在多个目标怎么办呢?一个一个检索,找到一个目标区域后,将其清空,然后寻找第二个。
接下来有人会问了,这个quality bounding function如何可以快速估计啊?如果估计太复杂的化怎么用啊?没错我们接下来,进一步瞧瞧quality function 以及 quality bounding function。
作者给出了quality bounding function的两条特性:
其中是quality function,
是quality bounding function,
表示矩形集合,
表示矩形。
下面我们举一个简单的例子,来看看如何将这种想法应用在实际情况当中,以及瞧瞧如何设置quanlity funciton和 quality bouding function。
在举例子之前我们应该清楚地认识到quality function 以及 quality bounding function 必须能够快速计算。
使用bag of words 进行 non-rigid 目标检测
使用SIFT对来抽取局部图像的描述子,然后进行bag of words 表示,从而产生矩形区域的直方图特征。所使用的分类器是线性SVM。注意,这个特征直方图,其实是由矩形区域中每个特征描述所属的word构造而成。
决策函数是,其中
表示点积操作,
是训练样本的直方图特征,
和
是权向量(weight)和偏执(bias)。接下来我们会产生一个重要的变形,将决策函数写成在矩形区域内的每个特征点的贡献(
)形式
其中表示的矩形区域中的特征点
所属的那个word的索引,
表示在矩形区域内的特征点的个数。
这种形式就允许我们首先产生一副响应分数积分图,然后便可以快速计算任何矩形区域的quality funciton 值。
好了,现在我们需要构建quality bounding function了。首先我们对quality function 进行分解,,其中
表示正训练样本的贡献(注意
的表达形式),
表示负训练样本的贡献(注意的表达形式)。其次,用
表示在矩形集合中最大的矩形,
表示矩形集合中最小的矩形。那么quality bounding function 则表示为
至此我们构建出来了。
Efficient Subwindow Search算法我的理解是将问题的解空间用[T,B,L,R](对应矩形的上下左右4条轴)4 个区间来表示(初始为整个图像),每次选取最长的一个区间分割,并计算解空间的上边界quality bounding function(空间内最大矩形的正值和 + 空间内最小矩形的负值和),并以此进行优先搜索,在这个算法中利用到上边界优先队列,上边界值最大的矩形集合即为最有前景的区域。
还有一个经典搜索算法Selective Search是先分割后逐渐合并找到目标定位,Efficient Subwindow Search是主键分割最有前景的的矩形集合[T,B,L,R]直到不可分割为止找到目标定位。
下面我们来解析算法实现代码
/*
分支限界法核心程序
1、提取最有可能的候选区域
2、如果这个矩形集合内部存在间隔(存在矩形不唯一)则将这个集合按照最大间隔一分为二
3、计算这两个部分的上边界(quality bounding function)f^=weight[i]*(f+(Rmax)+f-(Rmin))
4、将这部分插入进上边界优先队列中,每次提取的队头元素均为上边界最大值的矩形集合
*/
//提取,分割,插入
//参数 pH 上边界优先队列
//返回,收敛为-1
static int extract_split_and_insert(sstate_heap *pH) {
// step 1) find the most promising candidate region 找到最有前景的区域
const sstate* curstate = pH->top(); //队头元素即为上边界最大元素,获取并不出队列
// step 2a) check if the stop criterion is reached核查是否到达停止标准
const int splitindex = curstate->maxindex(); //找到这个区域最大宽度分割点
if (splitindex < 0) //maxindex()返回值为-1,代表curstate集合中只有一个矩形
return -1; // no more splits => convergence //不在分割,收敛
// step 2b) otherwise, create two new states as copies of the old, except for splitindex
//矩形集中不止一个元素,通过分割索引号将矩形集分割成两个
pH->pop(); //出队列,即删除队头元素
sstate* newstate0 = new sstate(*curstate); //从那个具有最大宽度的矩形间隔中分割成两半[L,T,R,B]小矩形变大,大矩阵变小,趋近两者合二为一
newstate0->high[splitindex] = (curstate->low[splitindex] + curstate->high[splitindex])>>1;
sstate* newstate1 = new sstate(*curstate); //小矩形变大
newstate1->low[splitindex] = (curstate->low[splitindex] + curstate->high[splitindex]+1)>>1;
// the old state isn't needed anymore //将老矩形释放
delete curstate; curstate=NULL;
// step 3&4) calculate upper bounds for the parts and reinject them
//计算这 连个分割矩形的上界,并把他们入队列中
if ( newstate0->islegal() ) {//如果是一个矩形
//计算过程是计算矩形集合中每个小矩形的f^再乘上它的权值累加之后得到矩形集合的f^
newstate0->upper = quality_bound.upper_bound(newstate0);
pH->push(newstate0);
}
if ( newstate1->islegal() ) {
newstate1->upper = quality_bound.upper_bound(newstate1);
pH->push(newstate1);
}
return 0;
}
积分图的创建过程
//为正负部分分别创建积分图矩阵,传入参数raw_matrix为整张图像的权值积分图
void BoxQualityFunction::create_integral_matrices(const std::vector<double> &raw_matrix) {
/*for (int k = 0; k<raw_matrix.size(); k++) { //特征点个数循环 共有13498个特征点
std::cout << raw_matrix[k] << " ";
}*/
pos_matrix.clear();//初始化f+矩阵积分图
pos_matrix.resize( raw_matrix.size() );//为f+矩阵分配大小
neg_matrix.clear();
neg_matrix.resize( raw_matrix.size() );
// 将权重正负分入 pos_matrix和neg_matrix中
// split the weight matrix into positive and negative entries
for (unsigned int i=0; i < raw_matrix.size(); i++) {
double val = raw_matrix[i];
if (val > 0.)
pos_matrix[i] = val;
else
neg_matrix[i] = val;
}
// 竖直方向累加
// calculate integral image verically
for (int j=1; j < height; j++) {
for (int i=1; i < width; i++) {
pos_matrix[off(i,j)] += pos_matrix[off(i,j-1)];
neg_matrix[off(i,j)] += neg_matrix[off(i,j-1)];
}
}
// 水平方向累加
// calculate integral image horizontally
for (int j=1; j<height; j++) {
for (int i=1; i<width; i++) {
pos_matrix[off(i,j)] += pos_matrix[off(i-1,j)];
neg_matrix[off(i,j)] += neg_matrix[off(i-1,j)];
}
}
return;
}
由于本程序过于庞大,就不一一写入,详细代码讲解下面将给出链接。
使用的是VS2015+OpenCV2.4.13编写,可以直接运行。内部含有大量代码注释,甚至每一个变量都讲解的非常细致