从R-CNN到Faster R-CNN

Python025

从R-CNN到Faster R-CNN,第1张

论文: 《Rich feature hierarchies for accurate object detection and semantic segmentation》

发表年份:2013

RCNN(Regions with CNN features)是将CNN用到目标检测的一个里程碑,借助CNN良好的特征提取和分类性能,通过RegionProposal方法实现目标检测问题的转化。

Region proposal是一类传统的候选区域生成方法,论文使用 selective search  生成大约2k个候选区域(先用分割手段将图片完全分割成小图,再通过一些合并规则,将小图均匀的合并,经过若干次合并,直到合并成整张原图),然后将proposal的图片进行归一化(大小为217*217)用于CNN的输入。

对每个Region proposal使用CNN提取出一个4096维的特征向量

3.1 CNN提取的特征输入到SVM分类器中,对region proposal进行分类,与 ground-truth box的IoU大于0.5的为正样本,其余为负样本。论文中每个图片正样本个数为32负样本个数为96。得到所有region proposals的对于每一类的分数,再使用贪心的非极大值抑制方法对每一个SVM分类器类去除相交的多余的框。

3.2 使用一个线性回归器对bounding box进行修正,proposal bounding box的大小位置与真实框的转换关系如下:

综上,整个网络结构如下:

R-CNN在当年无论是在学术界还是工业界都是具有创造性的,但是现在来看RCNN主要存在下面三个问题:

1)多个候选区域对应的图像需要预先提取,占用较大的磁盘空间;

2)针对传统CNN需要固定尺寸(217*217)的输入图像,crop/warp(归一化)产生物体截断或拉伸,丧失或者改变了图片本身的信息;

3)每一个ProposalRegion都需要进入CNN网络计算,上千个Region存在大量的范围重叠,重复的CNN特征提取导致巨大的计算浪费。

论文: 《Spatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition》

发表年份:2015

既然CNN特征提取如此耗时,为什么还要对每个Region proposal进行特征提取,而不是整体进行特征提取,然后在分类之前做一次Region的截取呢,于是就诞生了SPP-Net。

1.解决了CNN需要固定大小输入的变换后导致图片信息丢失或者变化的问题

2.对于一张图,只需要进行一次的特征提取运算,避免了R-CNN中特征重复计算的问题

CNN为什么需要固定大小的输入?

R-CNN中会对图片缩放成217*217的固定大小,从而导致物体发生形变(如下图的上部分所示)。与前面不同,SPP-Net是加在最后一个卷积层的输出的后面,使得不同输入尺寸的图像在经过前面的卷积池化过程后,再经过SPP-net,得到相同大小的feature map,最后再经过全连接层进行分类

以AlexNet为例,经CNN得到conv5输出的任意尺寸的feature map,图中256-d是conv5卷积核的数量。将最后一个池化层pool5替换成SPP layer,将feature map划分成不同大小的网格,分别是`4x4`,`2x2`,`1x1`,每个网格中经过max pooling,从而得到4x4+2x2+1x1=21个特征值,最后将这21个特征值平铺成一个特征向量作为全连接层的输入,这种方式就是 空间金字塔池化 。

与R-CNN不同,SPP-Net中是将整张图片进行一次特征提取,得到整张图片的feature map,然后对feature map中的候选区域(RoIs)经过空间金字塔池化,提取出固定长度的特征向量进入全连接层。

原图候选区域与特征图上的RoIs的转换流程:

综上,整个网络结构如下图。SPP-Net相比R-CNN做了很多优化,但现在来看依然存在一些问题,主要如下:

论文: 《Fast R-CNN》

发表年份:2015

Fast R-CNN是对R-CNN的一个提升版本,相比R-CNN,训练速度提升9倍,测试速度提升213倍,mAP由66%提升到66.9%

主要改进点如下:

多任务损失函数(Multi-task Loss):

        Fast R-CNN将分类和边框回归合并,通过多任务Loss层进一步整合深度网络,统一了训练过程。分为两个损失函数:分类损失和回归损失。分类采用softmax代替SVM进行分类,共输出N(类别)+1(背景)类。softmax由于引入了类间竞争,所以分类效果优于SVM,SVM在R-CNN中用于二分类。回归损失输出的是4*N(类别),4表示的是(x,y,w,h分别表示候选框的中心坐标和宽、高)。

SVD对全连接层进行分解:

        由于一张图像约产生2000个RoIs,将近一半多的时间用在全连接层计算,为了提高运算速度,使用 SVD(奇异值分解) 对全连接层进行变换来提高运算速度。一个大的矩阵可以近似分解为三个小矩阵的乘积,分解后的矩阵的元素数目远小于原始矩阵的元素数目,从而达到减少计算量的目的。通过对全连接层的权值矩阵进行SVD分解,使得处理一张图像的速度明显提升。

论文: 《Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks》

发表年份:2016

        SPP-Net和Fast R-CNN都有一个非常耗时的候选框选取的过程,提取候选框最常用的SelectiveSearch方法,提取一副图像大概需要2s的时间,改进的EdgeBoxes算法将效率提高到了0.2s,但是还是比较耗时。

        Fast R-CNN中引入Region Proposal Network(RPN)替代Selective Search,同时引入anchor box应对目标形状的变化问题(anchor就是位置和大小固定的box,可以理解成事先设置好的固定的proposal)

Region Proposal Network:

        RPN的核心思想是候选框的提取不在原图上做,而是在feature map上做,这意味着相比原图更少的计算量。在Faster R-CNN中,RPN是单独的分支,通过RPN提取候选框并合并到深度网络中。

多尺度先验框:

RPN网络的特点在于通过滑动窗口的方式实现候选框的提取,每个滑动窗口位置生成9个候选窗口(不同尺度、不同宽高),提取对应9个候选窗口(anchor)的特征,用于目标分类和边框回归,与FastRCNN类似。

anchor的生成规则有两个:调整宽高比和放大。如下图所示,假设base_size为16,按照1:2,1:1,2:1三种比例进行变换生成下图上部分三种anchor;第二种是将宽高进行三种倍数放大,2^3=8,2^4=16,2^5=32倍的放大,如16x16的区域变成(16*8)*(16*8)=128*128的区域,(16*16)*(16*16)=256*256的区域,(16*32)*(16*32)=512*512的区域。

训练过程中,涉及到的候选框选取,选取依据如下:

从模型训练的角度来看,通过使用共享特征交替训练的方式,达到接近实时的性能,交替训练方式为:

Faster R-CNN实现了端到端的检测,并且几乎达到了效果上的最优,有些基于Faster R-CNN的变种准确度已经刷到了87%以上。速度方面还有优化的余地,比如Yolo系列(Yolo v1/v2/v3/v4)。对于目标检测,仍处于一个探索和高度发展的阶段,还不断有更优的模型产生。

如果按照你的程序的话,根据提示内容,在计算

i1=round(i*cos(a) - j*sin(a)+ n * sin(a))+1

j1=round(i*sin(a) + j*cos(a))

这两个时会出现零值,那么,在MATLAB中索引F矩阵就是错误的了,你可以设置断点,然后单步运行一下看看在哪一步出现的零值,你根据旋转矩阵的计算应该是没有问题的,关键是灰度映射时可能出现零位置,最好在其中加入判断的语句,j1计算会出现0值。

你这样写,是沿坐标轴原点旋转,你说的我原来写的那个是沿固定点旋转,实现都是使用旋转矩阵,沿固定点只是先平移到原点,然后再平移回去,你可以看看,具体的我已经记不太清楚了,呵呵,已经不做图像了。

我写的那个的程序:

function im_final = imrotate_my(im_path,theta,options)

%IM_ROTATE两维图像旋转以及双线性灰度插值算法的实现

%im_path 图像存储路径

%theta旋转角度,正数表示顺时针旋转

%options 可以为circular(超出范围部分,按照周期形式扩展)

% crop(超出部分置零,即全黑)

%Ref. 章毓晋. 图像工程(上册)——图像处理. 清华大学出版社

%Author: lskypDate: 2009.08.12

%Version: V1.2 Original Version: V1.0 im_bilinear.mim_rotate.m

% with the parameter options added

error(nargchk(2,3,nargin,'string'))

if nargin == 2

options = 'circular'

else

if ~ (strcmp(options,'circular') || strcmp(options,'crop'))

error('错误的输出方法')

end

end

im_init = imread(im_path)

im_init = double(im_init)

im_height = size(im_init,1)

im_width = size(im_init,2)

% 分别处理灰度图像和RGB图像

if ndims(im_init) == 3

im_final = zeros(im_height,im_width,3)

R = im_init(:,:,1)

G = im_init(:,:,2)

B = im_init(:,:,3)

R_final = im_final(:,:,1)

G_final = im_final(:,:,2)

B_final = im_final(:,:,3)

else

im_final = zeros(im_height,im_width)

end

rot_matrix = [cos(theta) -sin(theta)sin(theta) cos(theta)]

orig_h = (im_height + 1)/2

orig_w = (im_width + 1)/2

for h = 1:im_height

for w = 1:im_width

% 平移至原点,旋转,然后再平移回去

new_position = rot_matrix*[h - orig_hw - orig_w] + [orig_horig_w]

% 超出范围按周期扩展控制,options参数控制

if strcmp(options,'circular')

new_position(1) = mod(new_position(1),im_height)

new_position(2) = mod(new_position(2),im_width)

if new_position(1) == 0

new_position(1) = im_height

end

if new_position(2) == 0

new_position(2) = im_width

end

end

% 如果新位置为整数,那么直接赋予灰度值或者RGB值,否则,按照双线性插值计算。

% 使用后向映射

if new_position == round(new_position)

if new_position(1) == 0

new_position(1) = 1

end

if new_position(2) == 0

new_position(2) = 1

end

% 超出范围控制,options为crop选项,超出范围置零

if strcmp(options,'crop') &&(new_position(1) >= im_height || ...

new_position(2) >= im_width || new_position(1) <0 || ...

new_position(2) <0)

if ndims(im_init) == 3

R_final(h,w) = 0

G_final(h,w) = 0

B_final(h,w) = 0

else

im_final(h,w) = 0

end

else

if ndims(im_init) == 3

R_final(h,w) = R(new_position(1),new_position(2))

G_final(h,w) = G(new_position(1),new_position(2))

B_final(h,w) = B(new_position(1),new_position(2))

else

im_final(h,w) = im_init(new_position(1),new_position(2))

end

end

else

h_new = floor(new_position(1))

w_new = floor(new_position(2))

if h_new == 0

h_new = 1

end

if w_new == 0

w_new = 1

end

% 超出范围控制,options为crop选项,超出范围置零

if strcmp(options,'crop') &&(h_new >= im_height || ...

w_new >= im_width || h_new <0 || ...

w_new <0)

if ndims(im_init) == 3

R_final(h,w) = 0

G_final(h,w) = 0

B_final(h,w) = 0

else

im_final(h,w) = 0

end

else

% 边界控制

h1 = h_new + 1

w1 = w_new + 1

if h1 >= im_height + 1

h1 = mod(h1,im_height)

end

if w1 >= im_width + 1

w1 = mod(w1,im_width)

end

if ndims(im_init) == 3

% 双线性插值的实现过程

% Ref. 章毓晋. 图像工程(上册)——图像处理. 清华大学出版社

R_temp1 = R(h1,w_new)*(new_position(1) - h_new) + ...

R(h_new,w_new)*(h_new + 1 - new_position(1))

R_temp2 = R(h1,w1)*(new_position(1) - h_new) + ...

R(h_new,w1)*(h_new + 1 - new_position(1))

R_final(h,w) = R_temp1*(w_new + 1 - new_position(2)) + ...

R_temp2*(new_position(2) - w_new)

G_temp1 = G(h1,w_new)*(new_position(1) - h_new) + ...

G(h_new,w_new)*(h_new + 1 - new_position(1))

G_temp2 = G(h1,w1)*(new_position(1) - h_new) + ...

G(h_new,w1)*(h_new + 1 - new_position(1))

G_final(h,w) = G_temp1*(w_new + 1 - new_position(2)) + ...

G_temp2*(new_position(2) - w_new)

B_temp1 = B(h1,w_new)*(new_position(1) - h_new) + ...

B(h_new,w_new)*(h_new + 1 - new_position(1))

B_temp2 = B(h1,w1)*(new_position(1) - h_new) + ...

B(h_new,w1)*(h_new + 1 - new_position(1))

B_final(h,w) = B_temp1*(w_new + 1 - new_position(2)) + ...

B_temp2*(new_position(2) - w_new)

else

gray_temp1 = im_init(h1,w_new)*(new_position(1) - h_new) + ...

im_init(h_new,w_new)*(h_new + 1 - new_position(1))

gray_temp2 = im_init(h1,w1)*(new_position(1) - h_new) + ...

im_init(h_new,w1)*(h_new + 1 - new_position(1))

im_final(h,w) = gray_temp1*(w_new + 1 - new_position(2)) + ...

gray_temp2*(new_position(2) - w_new)

end

end

end

end

end

if ndims(im_init) == 3

im_final(:,:,1) = R_final

im_final(:,:,2) = G_final

im_final(:,:,3) = B_final

end

im_final = im2uint8(mat2gray(im_final))

实现部分主要就在控制边界那个地方,我用的周期扩展,如果为零则认为是边界地方,实现部分你可以看看。

具体的就只有这么多了,呵呵,多谢信任。

声明一下,程序内容如有朋友转,请注明。

这个函数只能帮助理解算法,如果真正用起来,还是使用IPT的imrotate函数。

imrotate是对图像进行旋转操作命令。在matlab命令窗口中键入help imrotate 或 doc imrotate或lookfor imrotate可以获得该函数帮助信息。调用格式:B = imrotate(A,angle)将图像A(图像的数据矩阵)绕图像的中心点旋转angle度, 正数表示逆时针旋转, 负数表示顺时针旋转。返回旋转后的图像矩阵。B = imrotate(A,angle,method)使用method参数可以改变插值算法,method参数可以为下面这三个值:{'nearest'}:最邻近线性插值(Nearest-neighbor interpolation)'bilinear': 双线性插值(Bilinear interpolation)'bicubic': 双三次插值(或叫做双立方插值)(Bicubic interpolation)B = imrotate(A,angle,method,bbox)bbox参数用于指定输出图像属性:'crop': 通过对旋转后的图像B进行裁剪, 保持旋转后输出图像B的尺寸和输入图像A的尺寸一样。{'loose'}: 使输出图像足够大, 以保证源图像旋转后超出图像尺寸范围的像素值没有丢失。 一般上这种格式产生的图像的尺寸都要大于源图像的尺寸。程序示例:下面这个程序演示了怎样使用imrotate函数在matlab中产生一个斜矩形。img_w = 640img_h = img_wimg_oblique_rect = zeros(img_h, img_w)% create a oblique(45) rectangle in the matrixx1 = int32(img_w / 5 * 2)x2 = int32(img_w / 5 * 3)y1 = int32(img_h / 7)y2 = int32(img_h / 7 * 6)% 下面这句代码产生一个常规矩形。img_oblique_rect(y1:y2, x1:x2) = 1% 利用双线性插值算法对图像进行旋转, 产生一个斜矩形img_oblique_rect = imrotate(img_oblique_rect, 45, 'bilinear','crop')img_oblique_rect = imcomplement(img_oblique_rect)figure('Name', '这是一个斜矩形'), imshow(img_oblique_rect)