R-CNN分为三个部分: 1.候选区域生成:对输入图像采用Selective Search的方法生成2K个较为可能是目标的候选区域 2.特征提取: 对每个候选区域用卷积网络提取特征 3.类别判定: 将从卷积网络中读取的特征送入SVM模型中分类 4.位置调整: 使用回归器调整候选区域的位置
R-CNN使用了Selective Search方法来生成候选区域,相对于传统的滑动窗口方法,Selective Search方法预先生成了较为可能是物体的候选区域。
这篇文章的描述我觉得写得非常好:目标检测(1)-Selective Search
由于我们事先不知道需要检测哪个类别,因此第一张图的桌子、瓶子、餐具都是一个个候选目标,而餐具包含在桌子这个目标内,勺子又包含在碗内。这张图展示了目标检测的层级关系以及尺度关系,那我们如何去获得这些可能目标的位置呢。常规方法是通过穷举法,就是在原始图片上进行不同尺度不同大小的滑窗,获取每个可能的位置。而这样做的缺点也显而易见,就是计算量实在是太大了,而且由于不可能每个尺度都兼顾到,因此得到的目标位置也不可能那么准。那么我们能不能通过视觉特征去减少这种分类的可能性并提高精确度呢。这就是本文想做的事情。 可用的特征有很多,到底什么特征是有用的呢?我们看第二副图片的两只猫咪,他们的纹理是一样的,因此纹理特征肯定不行了。而如果通过颜色则能很好区分。但是第三幅图变色龙可就不行了,这时候边缘特征、纹理特征又显得比较有用。而在最后一幅图中,我们很容易把车和轮胎看作是一个整体,但是其实这两者的特征差距真的很明显啊,无论是颜色还是纹理或是边缘都差的太远了。而这这是几种情况,自然图像辣么多,我们通过什么特征去区分?应该区分到什么尺度?
我们无法通过单一的特征去判断一幅图片里的物体类别,selective search的策略是,我们可以先得到小尺度的区域,然后一次次合并得到大的尺寸就好了,并用上我们所知的全部特征。
Selective Search方法希望实现一下三点目标: 1.捕获所有尺寸: 需要考虑到所有对象的比例(使用分层算法) 2.多样性: 采用多种策略去生成候选区域,如前图所示,不用单一的特征去处理 3.速度快: Selective Search方法希望生成一组可能的目标位置,用于实际的目标识别,所以算法应该相对较快。
算法的流程如下所示: Selective Search方法采用了自下而上的方法生成候选区域,首先将输入图像分割成许多小块的图像,采集每个小图像的区域特征,将最相似的区域组合在一起,再迭代的计算该区域与其相邻区域的新相似性,直到整一个图像成为一个单一的区域,其中每次产生的图像和合并后的图像我们都保存下来,这里需要注意,一种更好的组合方式是:以(a-b-c-d-e-f-g)为例,图像应该较为均匀的发生组合,即(ab-cd-ef-g —> abcd-efg),而不是变成一大块后不断组合小的图像块,如(abcde-f-g)。
为了尽量分割所有场景的图像,我们需要保持算法的多样性,即采用不同的特征去计算相似度,这边文章首先将RGB转换到8个种类的色彩空间(1)RGB,(2)灰度I,(3)Lab,(4)rgI(归一化的rg通道加上灰度),(5)HSV,(6)rgb(归一化的RGB),(7)C,(8)H(HSV的H通道)。
这里的 +/-表示部分不变性。分数1/3意味着三个颜色通道中有一个是不变的。
计算颜色的距离: 使用L1-norm归一化获取图像每个颜色通道的25 bins的直方图。这样做的话两个区域合并后的直方图也很好计算,直接通过直方图大小加权区域大小然后除以总区域大小就好了。 计算纹理的距离: 文章使用快速的类似于SIFT的测量方法来表示纹理,具体做法是对每个颜色通道的8个不同方向计算方差σ=1的高斯微分(Gaussian Derivative),使用L1-norm归一化获取图像每个颜色通道的每个方向的10 bins的直方图。
有限组合较小的区域: 鼓励小区域尽早合并。防止一块大的图像不断组合小图像,那么多尺度仅仅运用在了局部而不是全局,这样保证了整个图像的多样性。
区域的适合度距离: 衡量区域ri和rj如何适应彼此,合并后的区域要尽量规范,不能合并后出现断崖的区域,如果区域ri包含在rj内,我们首先应该合并,另一方面,如果ri很难与rj相接,他们之间会形成断崖,不应该合并在一块。
最终度量: 最终度量是将这四种方法组合起来,最简单的方法就是加权相加了。
通过上述的步骤我们能够得到很多很多的区域,但是显然不是每个区域作为目标的可能性都是相同的,因此我们需要衡量这个可能性,这样就可以根据我们的需要筛选区域建议个数啦。
这篇文章做法是,给予最先合并的图片块较大的权重,比如最后一块完整图像权重为1,倒数第二次合并的区域权重为2以此类推。但是当我们策略很多,多样性很多的时候呢,这个权重就会有太多的重合了,排序不好搞啊。文章做法是给他们乘以一个随机数,毕竟3分看运气嘛,然后对于相同的区域多次出现的也叠加下权重,毕竟多个方法都说你是目标,也是有理由的嘛。这样我就得到了所有区域的目标分数,也就可以根据自己的需要选择需要多少个区域了。
R-CNN 抽取了一个 4096 维的特征向量,采用的是 Alexnet,我们将生成的候选区域缩放为227227大小后放入Alexnet进行特征提取。(这边R-CNN采用了非常暴力的手段将候选区域缩放为227227大小)
缩放分为两大类(该部分在原文附录A):
1)各向同性缩放,长宽放缩相同的倍数
tightest square with context: 把region proposal的边界进行扩展延伸成正方形,灰色部分用原始图片中的相应像素填补,如下图(B)所示 tightest square without context: 把region proposal的边界进行扩展延伸成正方形,灰色部分不填补,如下图©所示 2)各向异性缩放, 长宽放缩的倍数不同 不管图片是否扭曲,长宽缩放的比例可能不一样,直接将长宽缩放到227*227,如下图(D)所示
Alexnet结构如图所示: Alexnet特征提取部分包含了5个卷积层、2个全连接层,在Alexnet中p5层神经元个数为9216、 f6、f7的神经元个数都是4096,通过这个网络训练完毕后,最后提取特征每个输入候选框图片都能得到一个4096维的特征向量。
这边使用了迁移学习,将预训练后的Alexnet模型去掉全连接层后的输出当做各个候选区域提取出的特征,进行fine-tuning训练时,网络优化求解时采用随机梯度下降法,学习率大小为0.001。通过候选区域对预训练的CNN模型进行fine-tuning训练,将输出层替换为N+1层(N为需要的分类数,最后加一层背景就是N+1),在每次训练的时候,我们batch size大小选择128,其中32个为正样本、96个为负样本。
此处还需要讨论一个问题,就是怎么判断候选区域是将对象的一部分包括在内还是完全包括了对象,这边作者作者测试了IOU阈值各种方案数值0,0.1,0.2,0.3,0.4,0.5。最后通过训练发现,如果选择IOU阈值为0.3效果最好,通过IOU将候选区域与ground true进行比较,当其重叠区域IOU小于0.3时,将其标注成负样本,IOU大于0.7标注为正样本,这里可能会出现正负样本数量差距过大的问题。
由于设置了IOU阈值不同,数据就不够准确导致softmax效果并不好,所以作者使用了SVM方法来分类,我们为每个类别训练一个二分类的SVM分类器,如判断是汽车还是背景,提取出的2000个候选区域就能得到2000 * 4096大小的特征矩阵,我们将SVM的矩阵大小设置为4096 * N(N为分类数量或者是SVM数量),就能得到所有region proposals的对于这N类的分数。再对这些region proposals进行NMS(非极大值抑制)去除多余的region proposals。
这里是c++代码写的NMS:
struct Nms_Boxs { Rect box; int confident; int index; }; float iou_cal(Rect box1, Rect box2) { float x1 = max(box1.x, box2.x); float y1 = max(box1.y, box2.y); float x2 = min(box1.x + box1.width - 1, box2.x + box2.width - 1); float y2 = min(box1.y + box1.height - 1, box2.y + box2.height - 1); float inter_width, inter_height; inter_width = max(0, x2 - x1 + 1); inter_height = max(0, y2 - y1 + 1); float inter_area, union_area; inter_area = inter_width * inter_height; union_area = box1.height*box1.width + box2.height*box2.width - inter_area; return inter_area / union_area; } void NMS(vector<Rect> boxs, vector<float> confidents, int thre_confident, int thre_iou, vector<int>& indexs ) { Nms_Boxs bbox; vector<Nms_Boxs> bboxs; for (int i = 0; i < boxs.size(); ++i) { bbox.box = boxs[i]; bbox.index = i; bbox.confident = confidents[i]; bboxs.push_back(bbox); } for (int i = 0; i < bboxs.size(); ++i) { if (bboxs[i].confident < thre_confident) continue; int index_temp = bboxs[i].confident; for (int j = i + 1; j < bboxs.size(); ++j) { if (iou_cal(bboxs[i].box, bboxs[j].box) > thre_iou){ if (bboxs[i].confident < bboxs[j].confident) index_temp = bboxs[j].confident; bboxs[j].confident = -1; } } bboxs[i].confident = -1; indexs.push_back(index_temp); } }再对这些框进行canny边缘检测,就可以得到bounding-box。
文章同时使用了一个简单的边界框回归来优化候选框的位置,输入为Alexnet pool5的输出,回归训练器的输入是N对值, 包括了候选区域的框坐标和真实的框坐标,这里选用的Proposal必须和Ground Truth的IoU>0.6才算是正样本,我们在分类之后得到候选区域 使用4个值来表示候选区域的位置,其中 P x P_x Px和 P y P_y Py为候选框的中心点, P w P_w Pw和 P h P_h Ph为候选框的宽高,即x和y表示中心点坐标,w和h表示框的宽高,我们只需要回归出这四个参数即可调整候选框的位置。
我们通过 d x ( P ) d_x(P) dx(P)、 d y ( P ) d_y(P) dy(P)、 d w ( P ) d_w(P) dw(P)、 d h ( P ) d_h(P) dh(P)这四个变量表示 P x P_x Px、 P y P_y Py、 P w P_w Pw、 P h P_h Ph的变换,平移量 x = P w d x ( P ) x=P_wd_x(P) x=Pwdx(P), y = P h d y ( P ) y=P_hd_y(P) y=Phdy(P),再加上原先的量就得到了现在x、y的值。 所以我们学习 d x ( P ) d_x(P) dx(P)、 d y ( P ) d_y(P) dy(P)、 d w ( P ) d_w(P) dw(P)、 d h ( P ) d_h(P) dh(P)这四个变量就可以得到候选框, 这里使用下式来表示 d ∗ ( P ) d_*(P) d∗(P) 上式中 Φ 5 ( P ) \Phi_{5}(P) Φ5(P)为Alexnet pool5输出的特征,所以要求 d x ( P ) d_x(P) dx(P)、 d y ( P ) d_y(P) dy(P)、 d w ( P ) d_w(P) dw(P)、 d h ( P ) d_h(P) dh(P)这四个变换,只需求出 w ∗ T w^{T}_{*} w∗T即可。
这里首先定义了一下 t x t_x tx、 t y t_y ty、 t w t_w tw、 t h t_h th,如下图所示以方便后面计算损失。
这是R-CNN回归器的损失函数,可以看到就是一个平方最小二乘损失+正则化( t ∗ i t_*^i t∗i就是上面的 t x t_x tx、 t y t_y ty、 t w t_w tw、 t h t_h th)。
这就是R-CNN的基本步骤,我们再来回顾一下: 1.输入图像,并通过Selective Search方法得到候选区域。 2.将候选区域调整为227*227大小后放入CNN中提取特征。 3.提取特征后放入SVM中训练,得到各个类别的分数,并对各个类别分别进行NMS。 4.建立回归器,调整候选框的位置。
R-CNN存在以下几个问题: 1.训练分多步。我们知道R-CNN的训练先要fine tuning一个预训练的网络,然后针对每个类别都训练一个SVM分类器,最后还要用regressors对bounding-box进行回归,另外region proposal也要单独用selective search的方式获得,步骤比较繁琐。 2.时间和内存消耗比较大。在训练SVM和回归的时候需要用网络训练的特征作为输入,特征保存在磁盘上再读入的时间消耗还是比较大的。 3.测试的时候也比较慢,每张图片的每个region proposal都要做卷积,重复操作太多。