深入拆解YOLO_V3

这里拆解的代码主要来自GitHub eriklindernoren/PyTorch-YOLOv3

ultralytics版也很流行,但eriklindernoren版更适合初学者。

1.模型可视化

1.1 yolov3各layer可视化

模型通过netron可视化,并稍作整理后显示如下。

别看很复杂,其实主体部分是backbone darknet_53;

darknet可以理解为一堆res_layer,再加上若干下采样块;

其余部分就是直接为yolo_layer服务的,第一个yolo_layer的输入直接为下采样32倍后的feature map,后两个yolo_layer是上采样2倍的结果与前面相同尺寸特征的融合;这有没有点unet的意思?

在这里插入图片描述
其中,Darknet_53用于目标检测的部分见下图红框,即前52个卷积层:
在这里插入图片描述

此结果和csdn博客yolo系列之yolo v3【深度解析】给出的结构(见下图)完全一致。
在这里插入图片描述
paddlepaddle目标检测文档中给出的更简约,也更直观的图如下:在这里插入图片描述

1.2 yolov3-spp各layer可视化

对比可得,yolov3-spp只是在第一个yolo_layer之前加了个spp单元,同时多了一组conv+LeaklyRelu。
在eriklindernoren的实现版本中,spp由三个maxpool+1个shortcut进行concatenate实现。
三个maxpool分别为:

  • nn.MaxPool2d(kernel_size=5, stride=1, padding=2)
  • nn.MaxPool2d(kernel_size=9, stride=1, padding=4)
  • nn.MaxPool2d(kernel_size=13, stride=1, padding=6)

o = i − k + 2 ∗ p s + 1 o=\cfrac{i-k+2*p}{s}+1 o=sik+2p+1,知:输入输出size不变,可以直接进行spatial concatenate。
在这里插入图片描述

1.3 有趣的话题:YOLO_V3与UNet

UNet,特别是以resnet为backbone的unet,可以说与YOLO_V3是非常相似的。

下图仅为展示,不表示实际的卷积层个数。

Yolo_V3中,下采样完全通过常规卷积实现。

在这里插入图片描述

2.pred_box位置回归为哪般?

在这里插入图片描述

上图来自Christopher Bourez’s blog Bounding box object detectors,此图描绘位置回归,比原文中的图片还要形象。

paper中的公式:

b x = σ ( t x ) + c x b y = σ ( t y ) + c y b w = p w ⋅ e t w b h = p h ⋅ e t h b_x=\sigma(t_x)+c_x\\b_y=\sigma(t_y)+c_y\\b_w=p_w\cdot e^{t_w}\\b_h=p_h \cdot e^{t_h} bx=σ(tx)+cxby=σ(ty)+cybw=pwetwbh=pheth

相应代码:

x = torch.sigmoid(prediction[..., 0])
y = torch.sigmoid(prediction[..., 1])
w = prediction[..., 2]  # Width
h = prediction[..., 3]  # Height

pred_boxes[..., 0] = x.data + self.grid_x  # b_x=/sigma(t_x)+c_x
pred_boxes[..., 1] = y.data + self.grid_y  # b_y=/sigma(t_y)+c_y
# b_w=p_w*\exp(t_w)
pred_boxes[..., 2] = torch.exp(w.data) * self.anchor_w
# b_h=p_h*\exp(t_h)
pred_boxes[..., 3] = torch.exp(h.data) * self.anchor_h

prediction[…, 0]到prediction[…, 4]是模型输出的初始 x 0 , y 0 , w 0 , h 0 x_0,y_0,w_0,h_0 x0,y0,w0,h0。这个信息里还没有anchor的尺寸信息。

回归过程,首先是对 x 0 , y 0 x_0,y_0 x0,y0进行 σ \sigma σ操作,再分别加上 c x , c y c_x,c_y cx,cy,即代码中的self.grid_x,self.grid_y 。

这是啥,其实就是当前进行预测的这个gird的坐标信息。这个grid是指feature_map,如原图416*416,分别缩小了32,16,8倍,则 c x , c y c_x,c_y cx,cy分别为0~13, 0~26 及 0~52。

然后是对 w , h w,h w,h的回归。公式里的 p w , p h p_w,p_h pw,ph,即self.anchor_w,self.anchor_h即所用anchor的w,h信息。 p w , p h p_w,p_h pw,ph也相应的是anchor宽/高经过缩放到当前feature map上的w,h。

下面是一组对比,对于 ( x , y ) (x,y) (x,y)必须首先将它放回到对应的尺寸,这里只对比w,h回归前后pred box的差异,另外这里只显示与target box最匹配grid的三个pred box。
在这里插入图片描述
从上图可以理解下,feature map越小,anchor相对越大,便于检测大目标;feature map越大,anchor相对越小,便于检测小目标。

图片如何画网格,使用的是plt.plot,可参考anchor box之 SSD default boxesGitHub

只显示最佳grid最match的anchor,位置回归后的结果(由于数据预处理及权重随机,所以与上图不要联系在一起),见下图:

在这里插入图片描述

3.使用kmeans聚类生成新的一组anchor

kmeans是一种常见的聚类方法,其原理简介可见一文GET Kmeans、DBSCAN、GMM、谱聚类Spectral clustering 算法

其实现步骤可简单概括为:

  • 1.给定聚类类别个数k;
  • 2.确定初始聚类中心,如从数据中随机选择k个样本作为初始中心;
  • 3.确定某种距离规则,如常用的欧氏距离,按照至k个中心距离最小的原则将所有数据聚成k类;
  • 4.重新计算k类的中心(如取各类样本各维度的均值);
  • 5.重复3~4,直至各类中心不再发生变化或变化极小。

而yolov3作者进行聚类时(见yolov2),没有使用常用的欧氏距离,而是:

d ( b o x , c e n t r o i d ) = 1 − I O U ( b o x , c e n t r o i d ) \qquad\qquad d(box,centroid)=1-IOU(box,centroid) d(box,centroid)=1IOU(box,centroid)

target box的长宽与anchor的长宽越接近,二者IOU越大非常切合。

参考代码见yolov3_anchor kmeans python实现

这里需要理解的一点是:

从label文件夹中获取某图片target box的长和宽要除以该图片的长和宽,得到的长和宽都在(0,1)范围。

然后聚类出来的k个类心也是一样,在(0,1)范围。

最后输出的anchors的长宽,要乘上设置的图片尺寸中长和宽,如(416,416)。这样才得到了我们需要的anchors。

这时就可以替换掉配置文件中的默认anchor,进行训练了。

4.关于在图片显示中加入bbox

bbox显示在图片中是object detection的必备工具。

一种实现方法已经在图像增强 imgaug中介绍过了,使用imgaug确实很赞。

还有一种用cv2实现,收在cv2画图操作中,应该是ultralytics版中get到的。

今天再介绍一种:使用老朋友matplotlib。这在eriklindernoren版detect.py代码中有介绍。

同样还是使用安全帽数据集中的一张图片。
在这里插入图片描述
使用matplotlib显示bbox代码如下:

from PIL import Image
import numpy as np

import matplotlib.pyplot as plt
import matplotlib.patches as patches
from matplotlib.ticker import NullLocator

img_path = r"D:\1.jpg"

boxes = [[60, 66, 910, 1108]]
img = np.array(Image.open(img_path))
plt.figure()
fig, ax = plt.subplots(1)
ax.imshow(img)

for [x1, y1, x2, y2] in boxes:
    box_w = x2 - x1
    box_h = y2 - y1

    # Create a Rectangle patch
    bbox = patches.Rectangle(
        (x1, y1), box_w, box_h, linewidth=1, edgecolor='b', facecolor="none")
    # Add the bbox to the plot
    ax.add_patch(bbox)
    # Add label
    plt.text(
        x1,
        y1,
        s='hat',
        color="white",
        verticalalignment="top",
        bbox={
    
    "color": "black", "pad": 0},
    )

# Save generated image with detections
plt.axis("off")
plt.gca().xaxis.set_major_locator(NullLocator())
plt.gca().yaxis.set_major_locator(NullLocator())
plt.show()

结果为:
在这里插入图片描述
可见依然还是一个字:帅!

参考文献

[1] https://github.com/eriklindernoren/PyTorch-YOLOv3
[2] yolo系列之yolo v3【深度解析】
[3] yolov3_anchor kmeans python实现
[4] Bounding box object detectors: understanding YOLO, You Look Only Once
[5]https://www.paddlepaddle.org.cn/tutorials/projectdetail/783903#anchor-19

猜你喜欢

转载自blog.csdn.net/WANGWUSHAN/article/details/113870305
今日推荐