从0开始 对yolov5官源代码一些代码理解

1 yolov5原理图个人感觉与v3差不多

本人注释代码在github建议搭配本人注释代码看 因为很多地方我卸载注释代码里面

链接 https://github.com/piged-brother/yolov5-
首先是fpu金字塔的左边的卷积输出
这个sppf是骨架网络的最后一层可以先不看
sppf 这里 两个5x5的s=1的maxpool 性能等于一个9x9的s=1的maxpooling
在这里插入图片描述
为什么

在这里插入图片描述

数据增强

预备知识

首先要读取图片,图片路径自己整一个

f = "./tmp/Cats_Test49.jpg"#./tmp/golf.jpg,Cats_Test49.jpg是路径
f2 = "./tmp/golf.jpg"
im = plt.imread(f)
im2 = plt.imread(f2)

认识数据增强千要明白一个数组叫透视变换数组

就比如我随机取一个像素点坐标。那么我接下来通过矩阵变换得到的像素点坐标
x ′ , y ′ 通过如下矩阵变换那么他们的位置也就是非直线位置就是被 m 11 , m 12 , m 21 , m 22 来决定 x{}',y{}' 通过如下矩阵变换那么他们的位置也就是非直线位置就是被m11 ,m12,m21,m22 来决定 x,y通过如下矩阵变换那么他们的位置也就是非直线位置就是被m11m12m21m22来决定
那么m13,m23 这两个再矩阵的相乘中并没有实际的和坐标相乘 只是再坐标进行乘法后相加。
比如
x ′ = m 11 ∗ x + m 12 ∗ y + m 13 所以 m 13 只是个以常数形式被相加 x{}' = m11*x + m12 * y + m13 所以m13只是个以常数形式被相加 x=m11x+m12y+m13所以m13只是个以常数形式被相加
所以我们下面所生成的3x3对角矩阵并且修改对角矩阵的参数这一块。就有原因了。

在这里插入图片描述

数据增强的下面的几种方法

mosaic: 将四张图片变成一张图片。

copy-paste: 把不同图像的目标放到另一张图像中类似数据分割。 但是必须要有示例标签不然无法进行分割,就是预先画好。

random-affine: 将数据随机缩放和平移

mixup: 将两张图片混合成一张新的图片

Augmented HSV: 随机调整色度饱和度

随机水平翻转

这几种数据增强启用的概率不同

数据增强rectangle变换

在这里插入图片描述
原理是尽量再图片resize后,让填充的黑边尽量小

数据增强举例旋转变换

原理如图
在这里插入图片描述
原坐标经过矩阵变换获得对应的矩阵坐标但是只需要前两行的矩阵变换

注:cv2.warpPerspective 函数的用法

rotate_scale = cv2.warpPerspective(im, RS, dsize=(w, h), borderValue=(114, 114, 114))
#这行代码的意思就是读取图片im  然后我们的透视变换矩阵是RS  输出的大小为w*h大小  然后填充颜色是bordervalue

im: 要进行透视变换的输入图像。
RS: 透视变换矩阵,由 cv2.getRotationMatrix2D 生成。
dsize: 输出图像的大小,这里设置为输入图像的大小 (w, h)。
borderValue: 在变换后的图像中,可能会出现一些区域没有被填充的情况。borderValue 指定了用什么值填充这些区域,这里设置为 (114, 114, 114),表示用 RGB 值为 (114, 114, 114) 的颜色进行填充。

degrees = 45    #degrees表示旋转的最大角度,scale表示随机缩放的最大比例。
scale = 0.5
h,w,_ = im.shape
# Rotation and Scale matrix
RS = np.eye(3)  #首先创建3x3的对角矩阵  为什么要创建3x3的矩阵?原理如上面的预备知识
angle = random.uniform(-degrees, degrees)
random_scale = random.uniform(1 - scale, 1 + scale)
RS[:2] = cv2.getRotationMatrix2D(angle=angle, center=(0, 0), scale=random_scale)
#这里创建了一个旋转和缩放矩阵RS,通过生成随机的旋转角度和缩放比例来进行设置。cv2.getRotationMatrix2D函数用于获取旋转#矩阵。在这里,RS[:2] 表示选择矩阵 RS 的前两行。RS 是一个3x3的矩阵,但是在这个上下文中,我们只对前两行进行操作。


rotate_scale = cv2.warpPerspective(im, RS, dsize=(w, h), borderValue=(114, 114, 114))
#使用cv2.warpPerspective函数对图像进行透视变换,以实现旋转和缩放操作。

#最后,通过Matplotlib库绘制了两个子图,左边显示原始图像,右边显示经过旋转和缩放处理后的图像。
plt.figure(figsize=(10, 20)) 
plt.subplot(1,2,1)
plt.imshow(im)
plt.title("origin")

plt.subplot(1,2,2)
plt.imshow(rotate_scale)
plt.title("rotate_scale")

数据增强举例平移变换

原理如图
在这里插入图片描述

t=0.1
h,w,_ = im.shape
T = np.eye(3)#同理的对角矩阵
T[0, 2] = random.uniform(0.5 - t, 0.5 + t) * w * 0.5
T[1, 2] = random.uniform(0.5 - t, 0.5 + t) * h * 0.5
translate = cv2.warpPerspective(im, T, dsize=(w, h), borderValue=(114, 114, 114))

plt.figure(figsize=(10, 20)) 
plt.subplot(1,2,1)
plt.imshow(im)
plt.title("origin")

plt.subplot(1,2,2)
plt.imshow(translate)
plt.title("translate")

结果如下
在这里插入图片描述

数据增强举例错切

原理如图
在这里插入图片描述

degree = 45
h,w,_ = im.shape
S = np.eye(3)
# 错切和旋转都是通过[0,1],[1,0]两个参数控制, 不同的是旋转两个参数互为相反数, 错切则不然
S[0, 1] = math.tan(random.uniform(-degree, degree) * math.pi / 180)
S[1, 0] = math.tan(random.uniform(-degree, degree) * math.pi / 180)

shear = cv2.warpPerspective(im, S, dsize=(w, h), borderValue=(114, 114, 114))

plt.figure(figsize=(10, 20)) 
plt.subplot(1,2,1)
plt.imshow(im)
plt.title("origin")

plt.subplot(1,2,2)
plt.imshow(shear)
plt.title("shear")

数据增强举例透视变换

原理如下
透视变换:就是一束光以某个角度射到这个图片,所产生的投影叫透视变换。
使用场合 就比如在ocr里面 我们处理图片的时候(举例我们在用手机拍摄身份证照片的时候)。我们可能出现俯视的角度去拍身份证 ,导致身份证图片会有点斜。如下图所示
在这里插入图片描述

在这里插入图片描述

p = 0.001
h, w, c = im.shape
im_copy = im.copy()
P = np.eye(3)
P[2, 0] = random.uniform(-p, p)
P[2, 1] = random.uniform(-p, p)
#这个函数在上面讲过不懂的可以看
perspective = cv2.warpPerspective(im_copy, P, dsize=(w, h), borderValue=(114, 114, 114))

plt.figure(figsize=(10, 20)) 
plt.subplot(1,2,1)
plt.imshow(im)
plt.title("origin")

plt.subplot(1,2,2)
plt.imshow(perspective)
plt.title("perspective")

在这里插入图片描述

数据增强举例翻转

原理如图
在这里插入图片描述

#利用np函数进行矩阵翻转
im_up = np.flipud(im)
im_right = np.fliplr(im)

plt.figure(figsize=(10, 30)) 
plt.subplot(1,3,1)
plt.imshow(im)
plt.title("origin")

plt.subplot(1,3,2)
plt.imshow(im_up)
plt.title("up")

plt.subplot(1,3,3)
plt.imshow(im_right)
plt.title("right")

结果如下
在这里插入图片描述

数据增强举例四图拼接

原理如图
在这里插入图片描述

im_size=640
mosaic_border = [-im_size // 2, -im_size // 2]
labels4, segments4 = [], []
s = im_size
# 这里随机计算一个xy中心点 
yc, xc = (int(random.uniform(-x, 2 * s + x)) for x in mosaic_border)  # mosaic center x, y
# indices = [index] + random.choices(self.indices, k=3)  # 3 additional image indices
# random.shuffle(indices)
im_files = [
    './tmp/000000000049.jpg',
    './tmp/000000000136.jpg',
    './tmp/000000000077.jpg',
    './tmp/000000000009.jpg',
]
#然后生成一张背景布
img4 = np.full((s * 2, s * 2, 3), 114, dtype=np.uint8)

   # 加载图片
for i, file in enumerate(im_files):

    # img, _, (h, w) = load_image(self, index)
    img = cv2.imread(file)
    h, w, _ = np.shape(img)

    # place img in img4
    if i == 0:  # top left
        # base image with 4 tiles
        # 这里计算第一张图贴到左上角部分的一个 起点xy, 终点xy就是xc,yc
        x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc  # xmin, ymin, xmax, ymax (large image)
        # 计算主要是裁剪出要贴的图,避免越界了, 其实起点一般就是(0,0),如果上面xc<w,yc<h,这里就会被裁剪掉部分, 终点就是w,h
        x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h  # xmin, ymin, xmax, ymax (small image)
    elif i == 1:  # top right
        x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc
        x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h
    elif i == 2:  # bottom left
        x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h)
        x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, w, min(y2a - y1a, h)
    elif i == 3:  # bottom right
        x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h)
        x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h)

    img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b]
    
#注释部分是拼接图片的原图
#plt.figure(figsize=(20, 100)) 
# plt.subplot(1,5,1)
# plt.imshow(plt.imread(im_files[0]))
# plt.title("origin 1")

# plt.subplot(1,5,2)
# plt.imshow(plt.imread(im_files[1]))
# plt.title("origin 2")

# plt.subplot(1,5,3)
# plt.imshow(plt.imread(im_files[2]))
# plt.title("origin 3")

# plt.subplot(1,5,4)
# plt.imshow(plt.imread(im_files[3]))
# plt.title("origin 4")
#展示拼接成果
plt.subplot(1,5,5)
plt.imshow(img4[:,:,::-1])
plt.title("mosaic")

在这里插入图片描述

数据增强举例mixup(透明度混合)

在这里插入图片描述

im_resize = cv2.resize(im,(320,320))
im2_resize = cv2.resize(im2,(320,320))
r = np.random.beta(32.0, 32.0)  # mixup ratio, alpha=beta=32.0
mix_up = (im_resize * r + im2_resize * (1 - r)).astype(np.uint8)

plt.figure(figsize=(10, 30)) 
plt.subplot(1,3,1)
plt.imshow(im)
plt.title("origin 1")

plt.subplot(1,3,2)
plt.imshow(im2)
plt.title("origin 2")

plt.subplot(1,3,3)
plt.imshow(mix_up)
plt.title("mix_up")

结果如下
在这里插入图片描述

数据增强举例分割填补(yolov5最难的地方)

**原理解释:**就是把一个图片放到另一张图片,然后看一下这两个图片的角色的重叠度(IOU)是不是小于我们所需要的设定值,下方设定值是小于0.3.。
在这里插入图片描述
代码部分太长不注释了

from generate_coco_data import CoCoDataGenrator
from visual_ops import draw_instance

def bbox_iou(box1, box2, eps=1E-7):
    """ Returns the intersection over box2 area given box1, box2. Boxes are x1y1x2y2
    box1:       np.array of shape(4)
    box2:       np.array of shape(nx4)
    returns:    np.array of shape(n)
    """
    box2 = box2.transpose()
    # Get the coordinates of bounding boxes
    b1_x1, b1_y1, b1_x2, b1_y2 = box1[0], box1[1], box1[2], box1[3]
    b2_x1, b2_y1, b2_x2, b2_y2 = box2[0], box2[1], box2[2], box2[3]
    # Intersection area
    inter_area = (np.minimum(b1_x2, b2_x2) - np.maximum(b1_x1, b2_x1)).clip(0) * \
                 (np.minimum(b1_y2, b2_y2) - np.maximum(b1_y1, b2_y1)).clip(0)
    # box2 area
    box2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) + eps
    # Intersection over box2 area
    return inter_area / box2_area

def copy_paste(im_origin, boxes_origin, im_masks, masks, mask_boxes, p=1.):
    """ 分割填补,  https://arxiv.org/abs/2012.07177
    :param boxes_origin:  [[x1,y1,x2,y2], ....]
    :param masks: [h,w,instances]
    """

    out_boxes = []
    out_masks = []
    n = masks.shape[-1]
    im_new = im_origin.copy()
    if p and n:
        h, w, c = im_origin.shape  # height, width, channels
        for j in random.sample(range(n), k=round(p * n)):
            start_x = np.random.uniform(0, w // 2)
            start_y = np.random.uniform(0, h // 2)
            box, mask = mask_boxes[j], masks[:, :, j:j + 1]
            new_box = [
                int(start_x),
                int(start_y),
                int(min(start_x + (box[2] - box[0]), w)),
                int(min(start_y + (box[3] - box[1]), h))
            ]
            iou = bbox_iou(new_box, boxes_origin)
            if (iou < 0.90).all():
                mask_im = (im_masks * mask)[
                          box[1]:int((new_box[3] - new_box[1]) + box[1]),
                          box[0]:int((new_box[2] - new_box[0])) + box[0], :]
                new_mask_im = np.zeros(shape=(h, w, 3), dtype=int)
                new_mask_im[new_box[1]:new_box[3], new_box[0]:new_box[2], :] = mask_im
                # cv2.imshow("", np.array(new_mask_im, dtype=np.uint8))

                target_mask = mask[
                              box[1]:int((new_box[3] - new_box[1]) + box[1]),
                              box[0]:int((new_box[2] - new_box[0])) + box[0], :]
                new_mask = np.zeros(shape=(h, w, 1), dtype=int)
                new_mask[new_box[1]:new_box[3], new_box[0]:new_box[2], :] = target_mask
                out_boxes.append(new_box)
                out_masks.append(new_mask)

                im_new = im_new * (1 - new_mask) + new_mask_im * new_mask

    out_boxes = np.array(out_boxes)
    out_masks = np.concatenate(out_masks, axis=-1)
    im_new = np.array(im_new, dtype=np.uint8)
    return im_new, out_boxes, out_masks



file = "./instances_val2017.json"
coco = CoCoDataGenrator(
    coco_annotation_file=file,
    train_img_nums=2,
    include_mask=True,
    include_keypoint=False,
    batch_size=2)
data = coco.next_batch()
gt_imgs = data['imgs']
gt_boxes = data['bboxes']
gt_classes = data['labels']
gt_masks = data['masks']
valid_nums = data['valid_nums']
im_new, out_boxes, out_masks = copy_paste(
    im_origin=gt_imgs[0],
    boxes_origin=gt_boxes[0][:valid_nums[0]],
    im_masks=gt_imgs[1],
    masks=gt_masks[1][:, :, :valid_nums[1]],
    mask_boxes=gt_boxes[1][:valid_nums[1]])
final_masks = np.concatenate([gt_masks[0][:, :, :valid_nums[0]], out_masks], axis=-1)
im_new = draw_instance(im_new, final_masks)

img0 = gt_imgs[0]
img0 = draw_instance(img0, gt_masks[0][:, :, :valid_nums[0]])

img1 = gt_imgs[1]
img1 = draw_instance(img1, gt_masks[1][:, :, :valid_nums[1]])


plt.figure(figsize=(20, 60)) 
plt.subplot(1,3,1)
plt.imshow(img0)
plt.title("origin")

plt.subplot(1,3,2)
plt.imshow(img1)
plt.title("copy")

plt.subplot(1,3,3)
plt.imshow(im_new)
plt.title("paste")

结果
在这里插入图片描述

处理完数据后的标签修改

我们处理完所有数据,但是数据所带的标签应道修改,因为生成的数据标签(这里的标签不是类别,而是类似坐标,颜色等等)自然不和原图一样。

步骤如下:
在这里插入图片描述
注解
1. 将所有变换矩阵连乘,这里怎么理解? 类比线性代数的初等变换,假设你左乘或者又乘一个矩阵,对应行和列进行变换。但是我们的图片进行二维变换,不存在一些所谓的初等矩阵。所以直接看作是对图像矩阵的所有操作的叠加就行。

**2.**因为我们的矩阵进行透视变换的时候是三维矩阵,所以我们在我们图片的二维矩阵中在添一个维度默认置为1,方便运算。然后xy是为了获取segment的前面两列也就是像素点的xy坐标(也就是一张图片会有很多像素点,那么segment就是着写像素点) ,然后假如是box坐标 (也就是左上和右下的坐标这个称为box)。

**3.**乘以最终的变换矩阵,意思就是进行一些多次数据增强后的总的那个矩阵(比如平移变换+透视变换等等他们用的不用矩阵,但是乘后就是最终变换的矩阵)

**4.**就是做了透视变换后,这个除以分母的操作是为了防止透视变换引起的坐标偏移和缩放问题。在透视变换中,由于透视效果,图像中的物体可能会在远离视点的地方变得更小,而在靠近视点的地方变得更大。通过除以第三个元素,可以将坐标归一化,以适应这种变化,从而实现正确的透视效果。
5,6就是进行一些变换后的过滤

第二部分是对数据增强源码解读

找到train.py文件

dataloader部分的LoadImagesAndLabels的__init__部分 是进行数据增强的一些定义

在这里插入图片描述
找到这个数据加载器,这是yolov5的所有数据处理部分。
在这里插入图片描述
这里是对一些超参数的一些注释和理解。

在这里插入图片描述

** img2label_paths函数:在这里就是根据images找到它的同级目录labels**
在这里插入图片描述

所以我们应该这样存放训练图片文件
在这里插入图片描述

然后是读取所有图片

在这里插入图片描述

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e54306abed184f4a9cc044746e7ce72c.png在这里插入图片描述进入cache_labels函数在这里插入图片描述

在进入verify_image_label函数
在这里插入图片描述
验证image部分
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/4b2daf7051de4c4dbc04b351fd6a72f7.png

验证label的部分

在这里插入图片描述
验证完后对返回的异常数值进行累加确定有多少异常最后返回的x字典包括

在这里插入图片描述

ok回到dataloader.py第494行

# Display cache(不用管)就是打印输出之前的cache_labels的返回数值

在这里插入图片描述

# Read cache可以看代码注释

**505行read  cacheh还有filter image 部分是将cahe_labe部分得到的图片进行再过滤,更新。重新对图片路径进行反推label文件路径。 为什么要更新  因为cache_label函数把有用的图片过滤出来了,剩下这些是有用的图片需要重新写入文件夹**

在这里插入图片描述

Update labels 这就是普通的根据个人的想法进行标签过滤具体看下面注释(dataloader.py)

在这里插入图片描述

(最重要!!!)Rectangular Training 这就是数据增强的第一种方法具体看如何实现在dataloader.py里)

在这里插入图片描述

接下来我们需要知道我们抓到每一个batch里面的每一张图片之后要怎么做,dataloader部分的LoadImagesAndLabels的__getitem__部分 是进行数据增强的一些定义

因为我们只是定义了图片的存储路径,图片的矩阵训练,图片的标签文件等等,没有涉及到数据增强
所以看到dataloader.py的656行的getitem初始函数,

在这里插入图片描述

然后看到663行load_mosaic函数 这个函数主要就是把index也就是图片传入后进行数据增强的实际操作包括旋转平移转换 等等 由于是if语句判断是否进行这个模式训练,所以这个模式的特点就是多了一个四图拼接。else下面就没有这个。
对应数据增强的四图拼接核心就是那个四段逻辑语句,这个逻辑语句就是在确定四张图的位置。这里是采用mosaic训练法才会进行load_mosaic函数不使用则会跳到下面。
在这里插入图片描述在这里插入图片描述

然后再到800行的copy_paste函数
在这里插入图片描述
ctrl点击跳转到augmentations.py 240行
在这里插入图片描述

然后就回到dataloaders.py801行的random_perspective函数这里就是对数据增强的一个系统实现
根据ppt前面的

在这里插入图片描述
来进行操做 我们直接到重点 重点代码大家可以自己去观察。
这里对应总体增强操作的第一步到第四步。第188行 注意这里是augmentations.py文件
在这里插入图片描述
然后后面的就是对图片的处理后得到图片我们需要进行越绝判断以及进一步过滤 这里也就对应上面的5-6步
函数是233行的 box_candidates函数
在这里插入图片描述

这是函数结构图
在这里插入图片描述

ok来到第三部分 backbone部分。

开始backbone部分 骨架网络细节

在这里插入图片描述
yolov5s:一般用于手机端,计算性能低的cpu
yolov5m :普通pc端
yolov5l:一般用于服务器端,推理速度比5x快,精度比5x低
yolov5x:一般用于大型服务器。

所以我上述的一些网络在骨架上都是一样的。但是有写地方不一样

这里左边的几个网络不同点在于下面这两个参数
**depth_multiple: 1.0 **
**width_multiple: 1.0 **

**depth_multiple:**这个意思是1x1网络的通道数也就是深度 也就所谓的瓶颈层的通道数。这让网络不变大的同时变得更深
**width_multiple: **这个参数是表示卷积通道的缩放因子,就是将配置里面的backbone和head部分有关Conv通道的设置,全部乘以该系数。导致网络变宽
在这里插入图片描述
在这里插入图片描述
就是这里backbone网络这几种的结构都是一样的。

知道这些结构后我们需要知道一些yolov3里面的一个知识点就是fpn

要知道yolov3里面是直接输出fpn的(这里的分数表示视野越小代表能看到的越细比如1/32 就代表原图的1/32)在这里插入图片描述
所以yolov5加了pan后:进行多层特征提取再用concat融合
所以我们来详细说说这些细节

backbone部分的卷积多层融合细节

在这里插入图片描述

backbone部分的conv层

class Conv(nn.Module):
# Standard convolution
def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
super().__init__()
self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
self.bn = nn.BatchNorm2d(c2)
self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())
def forward(self, x):
return self.act(self.bn(self.conv(x)))
def forward_fuse(self, x):
return self.act(self.conv(x))

这里我解释一下:关于分组前很简单明了,因为要得到6个卷积后的通道,那么输入通道为12的卷积层和我们的6个3x3x12卷积核卷积得到我们所需要结果参数量为6x3x3x12

分组后:其实我们需要得到的结果就是我们没分组的结果,

你可以这样想,我们分组后如何得到我们没有分组的结果呢?那么就是加入分3组,每个分组的输入通道就应该是12/3=4 我们得到的结果每个组的通道也应该是6/3=2 所以每组的卷积核可以减少为3x3x4,但是我们同样还是需要六个卷积核。 总参数量就是6x3x3x4

在这里插入图片描述

激活函数silu

这里为什么用silu。没有为什么,炼丹实践出来的。

SiLU的函数形式跟ReLU十分相似,区别在于SiLU在<0处有负值,随着输入的绝对值越大,其跟ReLU就越相似,表现出ReLU非线性特点,以及避免梯度弥散消失,同时也具有一定的正则效果。

在这里插入图片描述

backbone的CSP和C3

CSP层

CSPNET是根据稠密网络densenet的改造形成,由于densenet在进行反向传播的时候会进行大量的冗余计算导致出速度比较慢,所以提出CSP,在进行卷积的时候通过分离通道层,只对其中一部分特征做卷积,再concat,以此减少计算量,同时又能保证精度。

传统的desnsenet
在这里插入图片描述
CSPnet
在这里插入图片描述
创新点在于开始的时候在这里插入图片描述
进行减少了一半的层数去进行卷积,然后另一半卷积完了后直接和初始一般的层数去concat所以减少了计算加快了速度。

原CSPnet
在这里插入图片描述

但是yolov5 (6.0)中却没有用CSPnet而是用的C3。为什么叫C3原因是CSP中有4个conv层
C3去掉了一个,所以变成C3.

所谓的C3就是去掉上面的红圈后得到的网络
在这里插入图片描述

然后我们也可以在根目录下对比这两个网络的速度
’实验代码

csp = BottleneckCSP(1024,1024,2)
c3 = C3(1024,1024,2)
result = profile(input=torch.randn(6,1024,64,64), ops=[csp, c3],n=50)

然后实验结果
在这里插入图片描述

backbone的sppf层

传统SPP思想

本意上是为了解决RCNN目标检测中不同proposal边框最后都能接上FC层,通过对卷积层施加自适应的大小3种池化操作,最后得到结果再做flatten到固定的大小,就可以统一接fc,就可以避免当时的ROIwrap层的裁剪+reisze丢失精度。最主要的还是通过三个maxpool可以让精度丢失减少
,因为三个maxpoll他们宽高不同,但是他们得到的通道数是相同的。
在这里插入图片描述

改进后的yolov5的SPPF层

将传统的spp串行改成了并行。这样就可以用更小的卷积核代替更大的卷积核,获得更快的效率。

在这里插入图片描述
实验代码

from models.common import SPPF,SPP
from utils.torch_utils import profile
m1 = SPP(1024, 1024)
m2 = SPPF(1024, 1024)
results = profile(input=torch.randn(16, 1024, 64, 64), ops=[m1, m2], n=100)

在这里插入图片描述

backbone的源码解读

来到根目录下的train.py

126-137行主要是判断有无预训练模型,假如有则是走上面 ,无预训练模型则是走下面。

在这里插入图片描述
进入137行的Model函数
在这里插入图片描述
看到这些 参数含义是选择的模型/在model文件夹里面, ch:输入通道, nc:类的数量
但是默认的类别判断是coco数据集的,假如我们需要自己的数据集,那么我们必须要重新写一个配置文件存放自己的数据集。

然后进入yolo.py的194行的parse_model函数解析模型
在这里插入图片描述

然后开始解释parse_model函数作用

在这里插入图片描述
在这里插入图片描述
解释完parse_model函数作用后。
来到199行这里是得到最后一层也就是Detect层
在这里插入图片描述
Detect类作用在yolo.py文件的38行

这里如果不需要执行到推理阶段就只需要foward函数执行到70行
假如要推理则需要全部执行。

假如不需要进行推理:那么只需将得到的参数x 也就是(batchsize,na,宽,高,no)进行返回即可
假如需要推理:那么必须用._make_grid函数生成推理坐标图层里面的数据,也就是(xy, wh, conf.sigmoid(), mask)打包返回。
这里的_make_grid函数要注意很重要生成推理坐标图层

在这里插入图片描述
在这里插入图片描述

好的看完Detect类继续看200行
这里就是进行单次训练以及进行卷积。
在这里插入图片描述

在这里插入图片描述

推理部分

感觉在推理部分大家还是不太懂
在这里插入图片描述

对应代码为在yolo.py的第204和206行
在这里插入图片描述

这里解释一下边框预测细节的公式改动以及为什么 看下图
在这里插入图片描述
左边的yolov3的xy偏移公式 只是用sigmoid该函数把他限制在(0,1)之间。yolov5则是把它乘以二倍后在-0.5
值域变成了【-0.5,1.5】。这是为什么?

答案就是: 在yolov5中采样并不是单个采样,而是采样的时候会增加一些细节。多采样一些框旁边的东西,也就是正采样的时候多采样一点,这么说你可能听不懂?下面有一张图。

在这里插入图片描述
这张图你看,我们看A点坐标(0.7,0.7),我们在grid上(也就是我们前面生成的推理坐标图层)标记了A点的正样本,也就是我们通过图片进行采样得到的样本实际位置,但是它靠左上角,那么我们会进行一些采样,采上右两个样本进行精度提升。那么我们采样上样本的时候,上样本左上角坐标为(0,1),相对于x的偏移量为()
但是sigmoid函数只能把数字处理到(0,1)之间,它无法处理得到负数。所以不行,我们需要修改。那么取值到1.5也是同理
在这里插入图片描述

那么解决了中心点偏移量后我们需要继续解答为什么预测框的宽高要经过这个处理。
其实这也是实验的结果。在units/loss.py文件中里面大概204行

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/5d24a2699625440aa26544f1d1213b70.png

ok这里在来说说上面的操作也就是loss细节。 这是在这个build_targets函数里面进行的(这里不懂可以不用看直接跳到损失函数细节就行!!!!)

在loss.py文件里面大概177行 。但是我们上面讲到了208行这里继续将下面的操作 偏移操作。
由于我们输入的target数据中 xywh都是归一化到(0,1)区间内的所以我们想要在当前的tensor也就是当前缩放图层里面操作就必须将当前tensor乘以当前的比例 意思是比如8x8的图层我们当前右上角的中心点坐标是(0.9,0.9)那么我们需要乘以8 得到(5.4,5.4)这就是我们当前tensor图层的坐标类似下图
在这里插入图片描述
这里得到这些点后我们需要一些筛选 得到我们将要处理的点。
我们需要得到当前图层也就是当前tensor下的坐标 也就是上面那张图

ok然后进行筛选 源码如下loss.py里面
在这里插入图片描述
图片中灰色的表示我们筛选成功后的
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

对于我们得到的中心点筛选后的坐标他们对条件的满足情况用jk表示
在这里插入图片描述

l和m数组同理也是最反转后进行筛选的数组进行条件记录
在这里插入图片描述

最后也就是进行正样本的添加

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/111b09ebdc9844eebc4f74f2cb7612a0.png
在这里插入图片描述
在这里插入图片描述
最后是添加正样本了
在这里插入图片描述
在这里插入图片描述

最后一部分是yolov5的损失计算环节

根据传统yolov3损失计算我们知道,就是根据真实得到中心点和宽高(xywh)和预测的中心点和宽高进行做差平方。
但是我们GIOU会假如他们的边际值如下图解释很清楚 就是会假如右上角阴影部分来进行计算。为什么呢,因为检测的物体两个框我们需要知道他们真实查的多少,因为查100m也是差,差1m也是差。你要知道两个物体之间到底差多少呢?传统的检测损失不能很好的反应。因为真实框和预测框没有交集的化,也就是离100m或者离1m这个iou都是为0,得到的loss也就是1
在这里插入图片描述
那么根据Giou我们也有另一种损失 改进后叫Diou
在这里插入图片描述

后来又推出了ciou
在这里插入图片描述但是我们yolov5的损失计算沿用的还是CIou
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

损失函数源码解析

在loss.py的差不多91行 中途在定义损失器的时候会有个focalloss方法但是我们不用管,因为yolov5没用 但是如果想看我还是进行了标注和解释。
在这里插入图片描述
这里进行一些属性定义后在进行下面的操作这里主要讲我们如何计算我们的类别损失 目标边框预测损失。。其实上面都讲过这里是全部过一遍。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
build target结束后进入主函数 138进行损失计算

在这里插入图片描述
置信度损失计算
在这里插入图片描述
分类
在这里插入图片描述

训练

大概这么多就开始进行训练了

环境搭建

下载yolov5
– git clone https://github.com/ultralytics/yolov5.git
或者在 https://github.com/ultralytics/yolov5 上下载zip包
– 安装requirement.txt文件

测试自带的权重文件
python .\detect.py --source .\data\images --weights .\weight\yolov5n-7-k5.pt

收集数据集
从自然环境中收集数据集,但是图片最好具有多样性,采集在不同的天气、不同的时间、不同的光照强度、不同角度、不同来源的图片。
具体要求可搜索:YOLO官方推荐数据集需求。

标记数据集
使用labelImg 标记数据集,生成label

训练数据集()里面的是解释
!python train.py --batch-size 4(这个应该自己知道) --epochs 200(训练轮数) --data data-fps/data.yaml(data ymal文件位置) --weights yolov5n.pt(权重文件)

测试

训练的比较好的一个weight

将voc数据集转换为yolo数据集

按照下图创建
在这里插入图片描述

下面的代码可以将voc数据转换为yolo数据

import xml.etree.ElementTree as ET
import pickle
import os
from os import listdir, getcwd
from os.path import join
import cv2
# 根据自己情况修改
classes = ['head','body']
#图片类型
picture_class="png"

def convert(size, box):
    dw = 1. / size[0]
    dh = 1. / size[1]
    x = (box[0] + box[1]) / 2.0
    y = (box[2] + box[3]) / 2.0
    w = box[1] - box[0]
    h = box[3] - box[2]
    x = x * dw
    w = w * dw
    y = y * dh
    h = h * dh
    return (x, y, w, h)


def convert_annotation(image_id):

    if not os.path.exists('VOC2007/Annotations/%s.xml' % (image_id)):
        return
    img = cv2.imread('VOC2007/JPEGImages/%s.%s' % (image_id, picture_class))
    in_file = open('VOC2007/Annotations/%s.xml' % (image_id))

    out_file = open('VOC2007/YOLO/labels/%s.txt' % (image_id), 'w')
    tree = ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    save_img = False
    for obj in root.iter('object'):
        cls = obj.find('name').text
        if cls not in classes:
            continue
        else:
            save_img=True
        cls_id = classes.index(cls)
        xmlbox = obj.find('bndbox')
        b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
             float(xmlbox.find('ymax').text))
        bb = convert((w, h), b)
        out_file.write(str(0) + " " + " ".join([str(a) for a in bb]) + '\n')
    out_file.close()
    if save_img:
        cv2.imwrite('VOC2007/YOLO/images/%s.%s'%(image_id,picture_class),img)
    else:
        os.remove('VOC2007/YOLO/labels/%s.txt'%(image_id))

img_file = 'VOC2007/JPEGImages'
for image in os.listdir(img_file):
    image_id = image.split('.')[0]
    convert_annotation(image_id)

我想大家应该都会就是用labeling进行制作然后标注类别
在这里插入图片描述

假如是本地训练直接按照上面的代码改好自己参数就行了
然后弄好就可以进行训练了

链接服务器训练

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
成功了就是我这样的
在这里插入图片描述
找到在这里插入图片描述
在这里插入图片描述
正常登录
在这里插入图片描述
在这里插入图片描述
往下拉
在这里插入图片描述
在这里插入图片描述
安装好即可

  • 27
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值