yolo5_v7源码阅读
一:数据处理 (在LoadImagesAndLabels类中)
1 mosaic增强 按概率是否使用mosaic增强 这个概率设置在超参数里面
代码:mosaic = self.mosaic and random.random() < hyp['mosaic']
博客讲解:https://blog.csdn.net/weixin_34910922/article/details/121041318
2 mixup 增强 同mosaic一样,只不过yolo 必须使用了mosaic增强才能使用mixup
代码: if random.random() < hyp['mixup']:
mixup:r = np.random.beta(32.0, 32.0) # 初始化一个概率
im = (im * r + im2 * (1 - r)).astype(np.uint8) 将概率作为两张图片的权重相加
labels = np.concatenate((labels, labels2), 0) 两张图片的标签合并
额外说明:第一张图片是当前迭代的图片,第二张是随机取的
博客:https://blog.csdn.net/sophicchen/article/details/120432083
3 数据增广
用的就是pytorch自带的,推荐一篇博客:
https://blog.csdn.net/weixin_45250844/article/details/120469766
4 hsv值域变化
这个不是很清楚,只知道它和rgb格式差不多也是一种颜色值域。
5 坐标转换:
yolo需要的原始坐标样式:

坐标转换随便画了一个图:

转换公式为:x = (x1+x2) / 2 y=(y1+y2)/2 w=x2-x1 h=y2-y1
最后输出做了归一化,其实就是除以图片的w,h。在后面映射到网格的时候我一开始纠结了很久怎么感觉和教程说的不一样,就在这个地方。
6 : 需要的超参数
hsv_h: 0.015 # image HSV-Hue augmentation (fraction) hsv图像模式 色调
hsv_s: 0.7 # image HSV-Saturation augmentation (fraction) hsv 图像模式 饱和度
hsv_v: 0.4 # image HSV-Value augmentation (fraction) 明亮度
flipud: 0.0 # image flip up-down (probability) 上下翻转
fliplr: 0.5 # image flip left-right (probability) 水平翻转
mosaic: 1.0 # image mosaic (probability) mosaic数据增强 按概率 random.random() < hyp['mosaic']
mixup: 0.0 # image mixup (probability) mixup数据增强 两张图片混合 x, y * r + x1, y * (1-r)
大部分是按概率
7 输出的结构:
img:
targets: 总共有6位 shape (哪一张图片,类别,中心坐标,宽高)
图片也要归一化:其实就是除以255 rgb最大是255
二 : 模型(yolo.py common.py)
模型图 :https://blog.csdn.net/qq_38253797/article/details/119754854

nc: 表示的是预测类别数 :输出就等于nc + 5 再加上三个先眼眶 na * (nc + 5)
nc的话就是热编码 加的这个5表示 (x y w h iou)
depth_multiple width_multiple:控制的就是块的深度和宽度
if m in {BottleneckCSP, C3, C3TR, C3Ghost, C3x}: 就是这几个块
看一下BottleneckCSP具体代码:

backbone: 主要就是几个卷积块 Conv块的构成就是(卷积,批量归一化, silu激活函数)
这里没有用到池化层来缩小,但是池化层还是有使用三个串联起来的(我忘记在那个块了),用3的核步长为2代替了,没有全连接层。每个版本有些许不一样,但是大体都是一样的
总共缩小了32倍(这里跟感受野和上采样有关)

还记得配置文件第一个 -1吗,就是m.f。 等于-1 就直接执行下一个块。不等于-1的时候就从y里面取值。前向传播的时候先把一些块的值保存到y里面, 来自于self.save里面这些块([4, 6, 10, 14, 17, 20, 23]) 4 这个块去数配置开始于0, 4就是c3这个块。

再看head: 一个卷积,一个上采样,一个concat ,一个c3.其中concat的配置就是[-1, 4].
意思就是将现在输出的和第4个块加起来,然后再走一个c3块。这就是yolo快的秘密。
总共进行两次上采样,三个输出,分别代表大中小的图片。
理论部分可以看看B站大神的讲解。
Detect:层
这个是最后输出的层。
前面的模型走完输出的shape(图像数量,(类别数+5)* 先验眶类别(默认为3,大中小三种类型), w, h (表示网格大小))
detect将其转换为shape(图片数量, 先验框(默认3,每一个类别的3种类型), w,h, 预测类型 + 5(热编码 + x ,y, w, h ,iou))
三 : 锚框
这个函数build_targets :输入(模型的输出,dataload的输出)

1 首先将真实的坐标映射到模型输出的坐标上:
gain[2:6] = torch.tensor(shape)[[3, 2, 3, 2]] # xyxy gain
t = targets * gain
因为之前进行了归一化,这里做乘法 gain shape(那张图片,类别,x, y, w, h, 哪一个框)

如图:坐标的大小是模型输出shape的第2, 3 维度。因为原始图片做了归一化,乘以图片的大小就落在了如上图的格子里面了。
2 : 计算高宽比 并且过滤得到置信度合适的框
r = t[..., 4:6] / anchors[:, None] # wh ratio
j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']
比较早的版本是计算iou,v7改成了计算高宽比,只取最大的,因为要么小于1,要么大于1.越小越不好,越大也越不好,为了方便就取最大的。这里一个超参数。
3 偏移

找出真实标签 x < 0.5, x > 0.5, y<0.5 ,y>0.5。如果小于0.5就-0.5让它移动到旁边的网格,同样大于0.5就加0.5。这样最坏的情况一个真实框得到的候选框有1* 3*3*3。
然后过滤,代码如下
gxy = t[:, 2:4] # grid xy
gxi = gain[[2, 3]] - gxy # inverse
j, k = ((gxy % 1 < g) & (gxy > 1)).T
l, m = ((gxi % 1 < g) & (gxi > 1)).T
j = torch.stack((torch.ones_like(j), j, k, l, m))
t = t.repeat((5, 1, 1))[j]
4 输出:1 索引(图片,框,网格)
2 box (这里中心坐标完全变成了, 相对于当前网格的坐标了,之前是相对于整张图片)
3 类别
4 先验框
四:损失函数
送入损失函数的数据,只有被候选框过滤掉的,并不是全部送进去。

这个是模型输出的数据 例如(1,3,80,80,85)
(80,80)表示网格的大小,每一个格子里面有85个数据,前5个表示x,y,w,h,iou
后面80个表示类别的热编码:这里80个类别。
画圆圈表示:候选框所在的位置,yolo只选择有候选框的预测值。
代码:pxy, pwh, _, pcls = pi[b, a, gj, gi].split((2, 2, 1, self.nc), 1)
1 box损失: lbox += (1.0 - iou).mean() # iou
2 分类损失 :lcls += self.BCEcls(pcls, t) # BCE
3 置信度损失 :obji = self.BCEobj(pi[..., 4], tobj)
tobj : 是吧box损失计算的iou拿过来。tobj[b, a, gj, gi] = iou # iou ratio
最后三个损失相加,每个乘以一个超参数
lbox *= self.hyp['box']
lobj *= self.hyp['obj']
lcls *= self.hyp['cls']
模型样本不平衡性也有一些超参数:cls_pw: 1.0 # cls BCELoss positive_weight
obj: 1.0 # obj loss gain (scale with pixels)
obj_pw: 1.0 # obj BCELoss positive_weigh
总结:全手打的,全自我的理解,有错的地方请多包涵,也请手下留情,欢迎一起交流学习,分享心得。一点体会:模型在整个算法并没有显得那么重要,一些优化就是把别人的经验拿过,比如vgg, google,ResNet,v7版本新加了一个 Transformer。我觉得最重要的就是数据怎么组合,处理。最后放到损失函数里面它就会按照我们预想的得出结果。

