原地址:https://i-blog.csdnimg.cn/blog_migrate/70d9faa6ed108813037eb8237cfa83d4.png
这篇CVPR2014的论文与之前那篇Boosting Binary Keypoint Detection一样,都是利用二进制来加速计算。不过这篇并不是做特征,而是去找图中的Object。它主要有两大亮点。第一个亮点是发现了在固定窗口的大小下,物体与背景的梯度模式有所不同。如图1所示。图1(a)中绿框代表背景,红框代表物体。如果把这些框都resize成固定大小,比如8X8,然后求出8X8这些块中每个点的梯度(Normed Gradient,本文的结果为[0,255],之前看错成Normalized...估计这里的normed就是指窗口都resize成8X8),可以明显看到物体与背景的梯度模式的差别,如图1(c)所示,物体的梯度分布呈现出较为杂乱的模式,而背景的较为单一和清楚。其实这个道理很浅显,就是图像中背景区域往往呈现出homogeneous的特性,早期的图像区域分割方法就是依靠这种特性来做的。然后我个人觉得这里不一定要用梯度,用其他一些统计特征甚至是图像特征都有可能得到类似的结果。
图(1)
观察到上面这个现象以后,接下来的事情就很好办了:首先找一堆训练图像,每个训练图像采集若干个窗口并resize到8X8的大小,然后将这些8X8的矩阵向量化得到若干个64维的向量,把这些向量扔进一个线性分类器去训练就ok了:
其中sl 是分类器的输出分数(归一化后可理解为含有物体的概率,即这个值越高就越可能含有物体),w 是训练得到的参数,gl 是64维的NG特征,l 这里代表了一个三元组,包括其采样的窗口大小i ,窗口位置x和y。(1)式其实就是w和gl做了点积(所以不知道能否用核函数!)。i 可以理解为在一个保存了不同窗口大小的数组中挑出的第i个,比如有数组{50X50,50X100,50X200,…,200X200,…}等等,i=2就是说挑了50X100大小的窗口。然后在实际应用中,不同的窗口大小含有物体的可能性也是不一样的。比如一个2X2的这么小的窗口就不太可能含有一个物体,即使它的分类器输出分数很高。所以还要对窗口大小、分类器输出分数以及最终窗口是否含有目标这三者的关系进行建模,作者这里又用了一个分类器,如式(3)所示。最后在每种窗口大小的挑若干个分数最高的作为目标框输出就可以了(non-maximal suppression,非最大抑制,一来是加速,二来是提高准确率)。
首先通过上面的训练,我们可以得到分类的模型w,第一个要二值化的目标就是它,二值化的思想可以简单想象成找若干个基向量,并用这些基向量的线性组合来记表示w, 而且这些基向量的每一维只能取1或者-1(二值嘛)。那么假设我们用了Nw个基向量,每个基向量为aj, j = 1,...,Nw,那么就有。具体模型的二值化可以按如下算法1的步骤进行:
算法1的步骤也很明确,每一个都生成一个基向量,此基向量每一维都是由当前残差的符号决定,然后用当前残差减去残差在这基向量的投影(相当于去掉模型在这一维上的分量)。但在计算中因为二进制位只能为0或者1,所以为了处理方便,取,那么就可以将基向量表示为
。
接下来我们还要对NG特征进行二值化,还记得我们刚才将NG归一化到[0,255]之间吧,那么8X8窗口上的每个点的NG特征值就可以用一个byte来存储,也就是每个值我们都可以用一个8位的二进制串来表示。那为什么是8X8而不是3X3或者其他的大小呢?这里其实是一个trick,也就是类似于“火箭助推器的宽度是由马屁股的宽度决定”这一个搞笑的段子。根据我大胆不负责的推测,选择8X8的窗口大小完全是因为现在常用计算机最高也仅是64位寻址!也就是如果将每个byte都看成是8位的二进制串的话,那么我们就有一个8X8X8的三维矩阵,前两维是窗口位置(行,列),第三维是在二进制串中的位置(页)。举个例子,比如窗口中第1行,第2列的NG特征值是192,换成二进制就是1100 0000,那么矩阵的元素(1,2,1) = (1,2,2)= 1,(1,2,3),…,(1,2,8)= 0;那么我们一页一页地将矩阵元素取出来,再将每页8X8的矩阵元素排成一个64位的二进制串并存进一个int64里。既然思路已经有了,做法也就很简单了:对于每一页,将每一行每个元素取出来,不断加入int64中并左移1位,最后得到那个int64每一位对应的元素坐标排列就应该是(1,1)(1,2)(1,3)…(8,8)。然后作者在这里又玩了一个trick,他说你这样每次移动一位不是要循环64次嘛,如果先将8个拼成一组(就是刚才那样左移1次),那么只需要移动8组就好了啊!而且,这样在相邻的窗口中还能重用重叠的部分(在VS2010 的Debug模式下我试了下,1个数“每次左移1位,移动1万次”和“每次左移100位,移动100次”两种情况,的确是后者速度快)。
最后,为了进一步节省存储空间,还可以只取NG值的高位来作二值化。因为比如192和193、194,它们的二进制表达分别是1100 0000, 1100 0001和 1100 0010,要是我只看前面4位,后面4位忽略(取0)的话,那么它们的取值都是192。也就是我们可以用192来约等于193和194,这样我们就不需要用到8位那么多了!写成公式就是式(5)这样,其中Ng 是我们要用的高位的位数(也就是前面说的三维矩阵的页),bk,l就是对应三维矩阵中的第k页(二值)。
然后作者引了另外一篇论文的结论(式(4)),其中二进制点乘可以用与来做,二进制串的模可以通过计算串中1的个数来做。
最后将以上各式代入整理得到最终表达式(6)
也提供了C++代码下载。我自己的话也会抽空写个matlab版本,不过因为matlab对二进制操作不如C++高效,所以肯定就不会有300fps的效果了!(要是有30fps就好了!)