参考1
参考2
参考3
yolov3 在网络最后的输出中,对于每个grid cell产生3个bounding box,每个bounding box的输出有三类参数:
假如一个图片被分割成 SxS 个grid cell,我们有B个anchor box,也就是说每个grid cell有B个bounding box, 每个bounding box内有4个位置参数,1个置信度,classes个类别概率,那么最终的输出维数是:S∗S∗[B∗(4+1+classes)]
。
下面分别具体介绍这三个参数的意义。
anchor box是从训练集的所有ground truth box中统计(使用k-means)出来的在训练集中最经常出现的几个box形状和尺寸。
anchor box其实就是对预测的对象范围进行约束,并加入了尺寸先验经验,从而实现多尺度学习的目的。
量化anchor box
维度聚类(dimension cluster):使用k-means算法在训练集中所有样本的ground truth box中聚类出具有代表性形状的宽和高。
到底找出几个anchor box算是最佳的具有代表性的形状?
做实验,聚类出多个数量不同anchor box组,分别应用到模型中,最终找出最优的在模型的复杂度和高召回率(high recall)之间折中的那组anchor box。作者在COCO数据集中使用了9个anchor box。
YOLO3为每种FPN预测特征图(13x13,26x26,52x52)设定3种anchor box,总共聚类出9种尺寸的anchor box。在COCO数据集这9个anchor box是:(10x13),(16x30),(33x23),(30x61),(62x45),(59x119),(116x90),(156x198),(373x326)。
分配上,在最小的13x13特征图上由于其感受野最大故应用最大的anchor box (116x90),(156x198),(373x326),(这几个坐标是针对416x416下的,当然要除以32把尺度缩放到13x13下),适合检测较大的目标。
中等的26x26特征图上由于其具有中等感受野故应用中等的anchor box (30x61),(62x45),(59x119),适合检测中等大小的目标。
较大的52x52特征图上由于其具有较小的感受野故应用最小的anchor box(10x13),(16x30),(33x23),适合检测较小的目标。
同Faster-Rcnn一样,特征图的每个像素(即每个grid)都会有对应的三个anchor box,如13x13特征图的每个grid都有三个anchor box (116x90),(156x198),(373x326)(这几个坐标需除以32缩放尺寸)
怎么在实际的模型中加入anchor box的先验经验呢?
YOLO的做法是不让bounding box直接预测实际box的宽和高(w,h),而是将预测的宽和高分别与anchor box的宽和高绑定,这样不管一开始bounding box输出的(w,h)是怎样的,经过转化后都是与anchor box的宽和高相关,这样经过很多次惩罚训练后,每个bounding box就知道自己该负责怎样形状的box预测了。这个绑定的关系是什么?就涉及到了anchor box的计算。
前提需要知道,
c x c_x cx, c y c_y cy的坐标是(0,0) (0,1),(0,2),(0,3)…(0,13)
(1,0),(1,1),(1,2),(1,3)…(1,13)等等
bouding box的输出应当为: t x t_x tx, t y t_y ty, t w t_w tw, t h t_h th
而真实的预测box应当是: b x b_x bx, b y b_y by(中心坐标), b w b_w bw, b h b_h bh(宽高)
还有就是 c x c_x cx, c y c_y cy的每一个都是1,也就是说,每个格子grid cell是以1为一个范围,每个grid cell的大小实际是1∗1
绑定的关系是什么?就是下面这个公式:
b w = a w e t w b_w=a_we^{t_w} bw=awetw
b h = a h e t h b_h=a_he^{t_h} bh=aheth
其中, a w a_w aw, a h a_h ah为anchor box的宽和高,
t w t_w tw, t h t_h th为bounding box直接预测出的宽和高,
b w b_w bw, b h b_h bh为转换后预测的实际宽和高,
前面提到过box中心坐标总是落在相应的grid cell中的,所以bounding box直接预测出的 t x t_x tx, t y t_y ty也是相对grid cell来说的,要想转换成最终输出的绝对坐标,需要下面的转换公式:
b x = σ ( t x ) + c x b_x=\sigma(t_x)+c_x bx=σ(tx)+cx
b y = σ ( t y ) + c y b_y=\sigma(t_y)+c_y by=σ(ty)+cy
其中, σ ( t x ) \sigma(t_x) σ(tx)为sigmoid函数,
c x c_x cx, c y c_y cy分别为grid cell方格左上角点相对整张图片的坐标。
主要是因为在训练时如果没有将 t x t_x tx, t y t_y ty 压缩到(0,1)区间内的话,模型在训练前期很难收敛。用sigmoid将 t x t_x tx, t y t_y ty压缩到[0,1]区间內,可以有效的确保目标中心处于执行预测的网格单元中,防止偏移过多。
网络不会预测边界框中心的确切坐标而是预测与预测目标的grid cell左上角相关的偏移 t x t_x tx, t y t_y ty 。如13∗13的feature map中,某个目标的中心点预测为(0.4,0.7)【都小于1】,它的 c x c_x cx, c y c_y cy,即中心落入的grid cell坐标是(6,6),则该物体的在feature map中的中心实际坐标显然是(6.4,6.7).这种情况没毛病,但若 t x t_x tx, t y t_y ty大于1,比如(1.2,0.7),则该物体在feature map的的中心实际坐标是(7.2,6.7),注意这时候该物体中心在这个物体所属grid cell外面了,但(6,6)这个grid cell却检测出我们这个单元格内含有目标的中心(yolo是采取物体中心归哪个grid cell整个物体就归哪个grid celll了),这样就矛盾了,因为左上角为(6,6)的grid cell负责预测这个物体,这个物体中心必须出现在这个grid cell中而不能出现在它旁边网格中,一旦 t x t_x tx, t y t_y ty 算出来大于1就会引起矛盾,因而必须归一化。
另外如果不是[0,1]区间,yolo的每个bbox的维度都是85,前5个属性是(Cx,Cy,w,h,confidence),后80个是类别概率,如果坐标不归一化,和这些概率值一起训练肯定不收敛啊
最终可以得出实际输出的box参数公式如下,这个也是在推理时将输出转换为最终推理结果的公式:
b x = σ ( t x ) + c x b_x=\sigma(t_x)+c_x bx=σ(tx)+cx
b y = σ ( t y ) + c y b_y=\sigma(t_y)+c_y by=σ(ty)+cy
b w = a w e t w b_w=a_we^{t_w} bw=awetw
b h = a h e t h b_h=a_he^{t_h} bh=aheth
其中,Cx,Cy是feature map中grid cell的左上角坐标,在yolov3中每个grid cell在feature map中的宽和高均为1。如下图1的情形时,这个bbox边界框的中心属于第二行第二列的grid cell,它的左上角坐标为(1,1),故Cx=1,Cy=1.公式中的Pw、Ph是预设的anchor box映射到feature map中的宽和高 (anchor box原本设定是相对于416*416坐标系下的坐标,在yolov3.cfg文件中写明了,代码中是把cfg中读取的坐标除以stride如32映射到feature map坐标系中)。
最终得到的边框坐标值是bx,by,bw,bh即边界框bbox相对于feature map的位置和大小
边框回归最简单的想法就是通过平移加尺度缩放进行微调嘛。
接下来还需要约束bbox的位置预测值到[0,1],会使得模型更容易稳定训练(如果不是[0,1]区间,yolo的每个bbox的维度都是85,前5个属性是(Cx,Cy,w,h,confidence),后80个是类别概率,如果坐标不归一化,和这些概率值一起训练肯定不收敛啊)
b x = ( σ ( t x ) + c x ) / w b_x=(\sigma(t_x)+c_x)/w bx=(σ(tx)+cx)/w
b y = ( σ ( t y ) + c y ) / h b_y=(\sigma(t_y)+c_y)/h by=(σ(ty)+cy)/h
b w = a w e t w / w b_w=a_we^{t_w}/w bw=awetw/w
b h = a h e t h / h b_h=a_he^{t_h}/h bh=aheth/h
其中w,h为feature map的大小
box get_yolo_box(float *x, float *biases, int n, int index, int i, int j, int lw, int lh, int w, int h, int stride)
{
box b;
b.x = (i + x[index + 0*stride]) / lw;
// 此处相当于知道了X的index,要找Y的index,向后偏移l.w*l.h个索引
b.y = (j + x[index + 1*stride]) / lh;
b.w = exp(x[index + 2*stride]) * biases[2*n] / w;
b.h = exp(x[index + 3*stride]) * biases[2*n+1] / h;
return b;
}
float delta_yolo_box(box truth, float *x, float *biases, int n, int index, int i, int j, int lw, int lh, int w, int h, float *delta, float scale, int stride)
{
box pred = get_yolo_box(x, biases, n, index, i, j, lw, lh, w, h, stride);
float iou = box_iou(pred, truth);
float tx = (truth.x*lw - i);
float ty = (truth.y*lh - j);
float tw = log(truth.w*w / biases[2*n]);
float th = log(truth.h*h / biases[2*n + 1]);
scale = 2 - groundtruth.w * groundtruth.h
delta[index + 0*stride] = scale * (tx - x[index + 0*stride]);
delta[index + 1*stride] = scale * (ty - x[index + 1*stride]);
delta[index + 2*stride] = scale * (tw - x[index + 2*stride]);
delta[index + 3*stride] = scale * (th - x[index + 3*stride]);
return iou;
}
得到除以了W,H后的bx,by,bw,bh,如果将这4个值分别乘以输入网络的图片的宽和高(如416x416)就可以得到bbox相对于坐标系(416x416)位置和大小了。但还要将相对于输入网络图片(416x416)的边框属性变换成原图按照纵横比不变进行缩放后的区域的坐标(416x312)。应该将方框的坐标转换为相对于填充后的图片中包含原始图片区域的计算方式。
scaling_factor * img_w
和scaling_factor * img_h
是图片按照纵横比不变进行缩放后的图片,即原图是768x576按照纵横比长边不变缩放到了416x372。
经坐标换算,得到的坐标还是在输入网络的图片(416x416)坐标系下的绝对坐标,但是此时已经是相对于416x372这个区域的坐标了,而不再相对于(0,0)原点。
#scaling_factor*img_w和scaling_factor*img_h是图片按照纵横比不变进行缩放后的图片,即原图是768x576按照纵横比长边不变缩放到了416*372。
#经坐标换算,得到的坐标还是在输入网络的图片(416x416)坐标系下的绝对坐标,但是此时已经是相对于416*372这个区域的坐标了,而不再相对于(0,0)原点。
output[:,[1,3]] -= (inp_dim - scaling_factor*im_dim_list[:,0].view(-1,1))/2#x1=x1−(416−scaling_factor*img_w)/2,x2=x2-(416−scaling_factor*img_w)/2
output[:,[2,4]] -= (inp_dim - scaling_factor*im_dim_list[:,1].view(-1,1))/2#y1=y1-(416−scaling_factor*img_h)/2,y2=y2-(416−scaling_factor*img_h)/2
其实代码的含义就是把y1,y2减去图2灰色部分的一半,y1=y1-(416-416/768576)/2=y1-(416-312)/2,把x1,x2,y1,y2的坐标系换算到了针对实际红框的坐标系(416312)下了。这样保证bbox不会扭曲,
yolov1里作者在loss里对宽高都做了开根号处理,是为了使得大小差别比较大的边框差别减小
而在yolov2和v3里,损失函数进行了改进,不再简单地加根号了,而是用scale = 2 - groundtruth.w * groundtruth.h加大对小框的损失。
这里需要注意的是,虽然输入尺寸是416416,但原图是按照纵横比例缩放至416416的, 取 min(w/img_w, h/img_h)这个比例来缩放,保证长的边缩放为需要的输入尺寸416,而短边按比例缩放不会扭曲,img_w,img_h是原图尺寸768,576, 缩放后的尺寸为new_w, new_h=416,312,需要的输入尺寸是w,h=416*416.如图所示:
剩下的灰色区域用(128,128,128)填充即可构造为416x416。不管训练还是测试时都需要这样操作原图
def letterbox_image(img, inp_dim):
"""
lteerbox_image()将图片按照纵横比进行缩放,将空白部分用(128,128,128)填充,调整图像尺寸
具体而言,此时某个边正好可以等于目标长度,另一边小于等于目标长度
将缩放后的数据拷贝到画布中心,返回完成缩放
"""
img_w, img_h = img.shape[1], img.shape[0]
w, h = inp_dim#inp_dim是需要resize的尺寸(如416*416)
# 取min(w/img_w, h/img_h)这个比例来缩放,缩放后的尺寸为new_w, new_h,即保证较长的边缩放后正好等于目标长度(需要的尺寸),另一边的尺寸缩放后还没有填充满.
new_w = int(img_w * min(w/img_w, h/img_h))
new_h = int(img_h * min(w/img_w, h/img_h))
resized_image = cv2.resize(img, (new_w,new_h), interpolation = cv2.INTER_CUBIC) #将图片按照纵横比不变来缩放为new_w x new_h,768 x 576的图片缩放成416x312.,用了双三次插值
# 创建一个画布, 将resized_image数据拷贝到画布中心。
canvas = np.full((inp_dim[1], inp_dim[0], 3), 128)#生成一个我们最终需要的图片尺寸hxwx3的array,这里生成416x416x3的array,每个元素值为128
# 将wxhx3的array中对应new_wxnew_hx3的部分(这两个部分的中心应该对齐)赋值为刚刚由原图缩放得到的数组,得到最终缩放后图片
canvas[(h-new_h)//2:(h-new_h)//2 + new_h,(w-new_w)//2:(w-new_w)//2 + new_w, :] = resized_image
return canvas
而且我们注意yolov3需要的训练数据的label是根据原图尺寸归一化了的,这样做是因为怕大的边框的影响比小的边框影响大,因此做了归一化的操作,这样大的和小的边框都会被同等看待了,而且训练也容易收敛。
关于box参数的转换还有一点值得一提,作者在训练中并不是将 t x t_x tx, t y t_y ty, t w t_w tw, t h t_h th 转换为 b x b_x bx, b y b_y by, b w b_w bw, b h b_h bh 后与ground truth box的对应参数求误差,而是使用上述公式的逆运算将ground truth box的参数转换为与 t x t_x tx, t y t_y ty, t w t_w tw, t h t_h th 对应的 g x g_x gx, g y g_y gy, g w g_w gw, g h g_h gh ,然后再计算误差。
也就是说,我们训练的输出是: t x t_x tx, t y t_y ty, t w t_w tw, t h t_h th, 那么在计算误差时,也是利用真实框的 t ^ x \hat t_x t^x, t ^ y \hat t_y t^y, t ^ w \hat t_w t^w, t ^ h \hat t_h t^h 这几个值计算误差。
如果预测十分准确,那么 g x g_x gx, g y g_y gy, g w g_w gw, g h g_h gh和 b x b_x bx, b y b_y by, b w b_w bw, b h b_h bh二者完全相等。代入图中的公式可得
g x = σ ( t ^ x ) + c x g_x=\sigma(\hat t_x)+c_x gx=σ(t^x)+cx
g y = σ ( t ^ y ) + c y g_y=\sigma(\hat t_y)+c_y gy=σ(t^y)+cy
g w = a w e t ^ w g_w=a_we^{\hat t_w} gw=awet^w
g h = a h e t ^ h g_h=a_he^{\hat t_h} gh=ahet^h
计算中由于sigmoid函数的反函数难计算,所以并没有计算sigmoid的反函数,而是计算输出对应的sigmoid函数值。
σ ( t ^ x ) = g x − c x \sigma(\hat t_x)=g_x-c_x σ(t^x)=gx−cx
σ ( t ^ y ) = g y − c y \sigma(\hat t_y)=g_y-c_y σ(t^y)=gy−cy
t ^ w = l o g ( g w / a w ) \hat t_w=log(g_w/a_w) t^w=log(gw/aw)
t ^ h = l o g ( g h / a h ) \hat t_h=log(g_h/a_h) t^h=log(gh/ah)
这样,就可以求误差了
一个尺度的feature map有三个anchors,那么对于某个ground truth框,究竟是哪个anchor负责匹配它呢?和YOLOv1一样,对于训练图片中的ground truth,若其中心点落在某个cell内,那么该cell内的3个anchor box负责预测它,具体是哪个anchor box预测它,需要在训练中确定,即由那个与ground truth的IOU最大的anchor box预测它,而剩余的2个anchor box不与该ground truth匹配。YOLOv3需要假定每个cell至多含有一个grounth truth,而在实际上基本不会出现多于1个的情况。与ground truth匹配的anchor box计算坐标误差、置信度误差(此时target为1)以及分类误差,而其它的anchor box只计算置信度误差(此时target为0)。
不管Faster-RCNN还是YOLO,都不是直接回归bounding box的长宽而是尺度缩放到对数空间,是怕训练会带来不稳定的梯度。因为如果不做变换,直接预测相对形变tw,那么要求tw>0,因为你的框的宽高不可能是负数。这样,是在做一个有不等式条件约束的优化问题,没法直接用SGD来做。所以先取一个对数变换,将其不等式约束去掉,就可以了。
还存在一个很关键的问题:在训练中我们挑选哪个bounding box的准则是选择预测的box与ground truth box的IOU最大的bounding box做为最优的box,但是在预测中并没有ground truth box,怎么才能挑选最优的bounding box呢?这就需要另外的参数了,那就是下面要说到的置信度。
置信度是每个bounding box输出的其中一个重要参数,作者对他的作用定义有两重:
以上所述,也就不难理解作者为什么将其称之为置信度了,因为不管哪重含义,都表示一种自信程度:框出的box内确实有物体的自信程度和框出的box将整个物体的所有特征都包括进来的自信程度。经过以上的解释,其实我们也就可以用数学形式表示置信度的定义了:
C i j = P r ( o b j e c t ) ∗ I O U p r e d t r u t h C^j_i=P_r(object)*IOU^{truth}_{pred} Cij=Pr(object)∗IOUpredtruth
其中, C i j C^j_i Cij 表示第 i i i 个grid cell的第 j j j 个bounding box的置信度。
那么如何训练 C i j C^j_i Cij ?
训练中, C i j C^j_i Cij 表示真实值, C i j C^j_i Cij 的取值是由grid cell的bounding box有没有负责预测某个对象决定的。如果负责,那么 C i j = 1 C^j_i=1 Cij=1 否则, C i j = 0 C^j_i=0 Cij=0
下面我们来说明如何确定某个grid cell的bounding box是否负责预测该grid cell中的对象:前面在说明anchor box的时候提到每个bounding box负责预测的形状是依据与其对应的anchor box(bounding box prior)相关的,那这个anchor box与该对象的ground truth box的IOU在所有的anchor box(与一个grid cell中所有bounding box对应,COCO数据集中是9个)与ground truth box的IOU中最大,那它就负责预测这个对象,因为这个形状、尺寸最符合当前这个对象,这时 C i j = 1 C^j_i=1 Cij=1,其他情况下 C i j = 0 C^j_i=0 Cij=0。注意,你没有看错,就是所有anchor box与某个ground truth box的IOU最大的那个anchor box对应的bounding box负责预测该对象,与该bounding box预测的box没有关系。
对象条件类别概率是一组概率的数组,数组的长度为当前模型检测的类别种类数量,它的意义是当bounding box认为当前box中有对象时,要检测的所有类别中每种类别的概率.
其实这个和分类模型最后使用softmax函数输出的一组类别概率是类似的,只是二者存在两点不同: