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

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

链接 https://github.com/piged-brother/yolov5-

首先是fpu金字塔的左边的卷积输出

这个sppf是骨架网络的最后一层可以先不看

sppf 这里 两个5x5的s=1的maxpool 性能等于一个9x9的s=1的maxpooling

yolov4源码pytorch版本 yolov5源代码_矩阵变换


为什么

yolov4源码pytorch版本 yolov5源代码_数据_02

数据增强

预备知识

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

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

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

就比如我随机取一个像素点坐标。那么我接下来通过矩阵变换得到的像素点坐标
yolov4源码pytorch版本 yolov5源代码_数据_03
那么m13,m23 这两个再矩阵的相乘中并没有实际的和坐标相乘 只是再坐标进行乘法后相加。
比如
yolov4源码pytorch版本 yolov5源代码_数据_04
所以我们下面所生成的3x3对角矩阵并且修改对角矩阵的参数这一块。就有原因了。

yolov4源码pytorch版本 yolov5源代码_透视变换_05

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

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

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

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

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

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

随机水平翻转

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

数据增强rectangle变换

yolov4源码pytorch版本 yolov5源代码_透视变换_06


原理是尽量再图片resize后,让填充的黑边尽量小

数据增强举例旋转变换

原理如图

yolov4源码pytorch版本 yolov5源代码_YOLO_07


原坐标经过矩阵变换获得对应的矩阵坐标但是只需要前两行的矩阵变换

注: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")

数据增强举例平移变换

原理如图

yolov4源码pytorch版本 yolov5源代码_透视变换_08

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")

结果如下

yolov4源码pytorch版本 yolov5源代码_数据_09

数据增强举例错切

原理如图

yolov4源码pytorch版本 yolov5源代码_透视变换_10

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里面 我们处理图片的时候(举例我们在用手机拍摄身份证照片的时候)。我们可能出现俯视的角度去拍身份证 ,导致身份证图片会有点斜。如下图所示

yolov4源码pytorch版本 yolov5源代码_数据_11

yolov4源码pytorch版本 yolov5源代码_YOLO_12

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")

yolov4源码pytorch版本 yolov5源代码_数据_13

数据增强举例翻转

原理如图

yolov4源码pytorch版本 yolov5源代码_透视变换_14

#利用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")

结果如下

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_15

数据增强举例四图拼接

原理如图

yolov4源码pytorch版本 yolov5源代码_数据_16

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")

yolov4源码pytorch版本 yolov5源代码_矩阵变换_17

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

yolov4源码pytorch版本 yolov5源代码_透视变换_18

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")

结果如下

yolov4源码pytorch版本 yolov5源代码_YOLO_19

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

**原理解释:**就是把一个图片放到另一张图片,然后看一下这两个图片的角色的重叠度(IOU)是不是小于我们所需要的设定值,下方设定值是小于0.3.。

yolov4源码pytorch版本 yolov5源代码_透视变换_20


代码部分太长不注释了

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")

结果

yolov4源码pytorch版本 yolov5源代码_透视变换_21

处理完数据后的标签修改

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

步骤如下:

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_22


注解

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

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

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

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

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

找到train.py文件

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

yolov4源码pytorch版本 yolov5源代码_数据_23


找到这个数据加载器,这是yolov5的所有数据处理部分。

yolov4源码pytorch版本 yolov5源代码_矩阵变换_24


这里是对一些超参数的一些注释和理解。

yolov4源码pytorch版本 yolov5源代码_数据_25

** img2label_paths函数:在这里就是根据images找到它的同级目录labels**

yolov4源码pytorch版本 yolov5源代码_数据_26

所以我们应该这样存放训练图片文件

yolov4源码pytorch版本 yolov5源代码_数据_27

然后是读取所有图片

yolov4源码pytorch版本 yolov5源代码_透视变换_28

![在这里插入图片描述](

yolov4源码pytorch版本 yolov5源代码_数据_29

进入cache_labels函数

yolov4源码pytorch版本 yolov5源代码_YOLO_30

在进入verify_image_label函数

yolov4源码pytorch版本 yolov5源代码_透视变换_31


验证image部分

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_32

验证label的部分

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_33


验证完后对返回的异常数值进行累加确定有多少异常最后返回的x字典包括

yolov4源码pytorch版本 yolov5源代码_数据_34

ok回到dataloader.py第494行

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

yolov4源码pytorch版本 yolov5源代码_YOLO_35

# Read cache可以看代码注释

yolov4源码pytorch版本 yolov5源代码_透视变换_36

yolov4源码pytorch版本 yolov5源代码_透视变换_37

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

yolov4源码pytorch版本 yolov5源代码_数据_38

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

yolov4源码pytorch版本 yolov5源代码_数据_39

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

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

yolov4源码pytorch版本 yolov5源代码_矩阵变换_40

然后看到663行load_mosaic函数 这个函数主要就是把index也就是图片传入后进行数据增强的实际操作包括旋转平移转换 等等 由于是if语句判断是否进行这个模式训练,所以这个模式的特点就是多了一个四图拼接。else下面就没有这个。

对应数据增强的四图拼接核心就是那个四段逻辑语句,这个逻辑语句就是在确定四张图的位置。这里是采用mosaic训练法才会进行load_mosaic函数不使用则会跳到下面。

yolov4源码pytorch版本 yolov5源代码_YOLO_41

yolov4源码pytorch版本 yolov5源代码_数据_42

然后再到800行的copy_paste函数

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_43


ctrl点击跳转到augmentations.py 240行

yolov4源码pytorch版本 yolov5源代码_数据_44

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

yolov4源码pytorch版本 yolov5源代码_矩阵变换_45


来进行操做 我们直接到重点 重点代码大家可以自己去观察。

这里对应总体增强操作的第一步到第四步。第188行 注意这里是augmentations.py文件

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_46


然后后面的就是对图片的处理后得到图片我们需要进行越绝判断以及进一步过滤 这里也就对应上面的5-6步

函数是233行的 box_candidates函数

yolov4源码pytorch版本 yolov5源代码_矩阵变换_47

这是函数结构图

yolov4源码pytorch版本 yolov5源代码_矩阵变换_48

ok来到第三部分 backbone部分。

开始backbone部分 骨架网络细节

yolov4源码pytorch版本 yolov5源代码_YOLO_49


yolov5s:一般用于手机端,计算性能低的cpu

yolov5m :普通pc端

yolov5l:一般用于服务器端,推理速度比5x快,精度比5x低

yolov5x:一般用于大型服务器。

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

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

**depth_multiple:**这个意思是1x1网络的通道数也就是深度 也就所谓的瓶颈层的通道数。这让网络不变大的同时变得更深

**width_multiple: **这个参数是表示卷积通道的缩放因子,就是将配置里面的backbone和head部分有关Conv通道的设置,全部乘以该系数。导致网络变宽

yolov4源码pytorch版本 yolov5源代码_数据_50


yolov4源码pytorch版本 yolov5源代码_数据_51


就是这里backbone网络这几种的结构都是一样的。

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

要知道yolov3里面是直接输出fpn的(这里的分数表示视野越小代表能看到的越细比如1/32 就代表原图的1/32)

yolov4源码pytorch版本 yolov5源代码_矩阵变换_52


所以yolov5加了pan后:进行多层特征提取再用concat融合

所以我们来详细说说这些细节

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

yolov4源码pytorch版本 yolov5源代码_矩阵变换_53

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

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_54

激活函数silu

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

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

yolov4源码pytorch版本 yolov5源代码_矩阵变换_55

backbone的CSP和C3

CSP层

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

传统的desnsenet

yolov4源码pytorch版本 yolov5源代码_数据_56


CSPnet

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_57


创新点在于开始的时候

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_58


进行减少了一半的层数去进行卷积,然后另一半卷积完了后直接和初始一般的层数去concat所以减少了计算加快了速度。原CSPnet

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_59

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

所谓的C3就是去掉上面的红圈后得到的网络

yolov4源码pytorch版本 yolov5源代码_矩阵变换_60

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

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

然后实验结果

yolov4源码pytorch版本 yolov5源代码_透视变换_61

backbone的sppf层

传统SPP思想

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

,因为三个maxpoll他们宽高不同,但是他们得到的通道数是相同的。

yolov4源码pytorch版本 yolov5源代码_矩阵变换_62

改进后的yolov5的SPPF层

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

yolov4源码pytorch版本 yolov5源代码_透视变换_63


实验代码

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)

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_64

backbone的源码解读

来到根目录下的train.py

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

yolov4源码pytorch版本 yolov5源代码_数据_65


进入137行的Model函数

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_66


看到这些 参数含义是选择的模型/在model文件夹里面, ch:输入通道, nc:类的数量

但是默认的类别判断是coco数据集的,假如我们需要自己的数据集,那么我们必须要重新写一个配置文件存放自己的数据集。然后进入yolo.py的194行的parse_model函数解析模型

yolov4源码pytorch版本 yolov5源代码_透视变换_67

然后开始解释parse_model函数作用

yolov4源码pytorch版本 yolov5源代码_数据_68


yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_69


解释完parse_model函数作用后。

来到199行这里是得到最后一层也就是Detect层

yolov4源码pytorch版本 yolov5源代码_透视变换_70


Detect类作用在yolo.py文件的38行

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

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

yolov4源码pytorch版本 yolov5源代码_透视变换_71


yolov4源码pytorch版本 yolov5源代码_数据_72

好的看完Detect类继续看200行

这里就是进行单次训练以及进行卷积。

yolov4源码pytorch版本 yolov5源代码_YOLO_73

yolov4源码pytorch版本 yolov5源代码_透视变换_74

推理部分

感觉在推理部分大家还是不太懂

yolov4源码pytorch版本 yolov5源代码_矩阵变换_75

对应代码为在yolo.py的第204和206行

yolov4源码pytorch版本 yolov5源代码_YOLO_76

这里解释一下边框预测细节的公式改动以及为什么 看下图

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_77


左边的yolov3的xy偏移公式 只是用sigmoid该函数把他限制在(0,1)之间。yolov5则是把它乘以二倍后在-0.5
值域变成了【-0.5,1.5】。这是为什么?

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

yolov4源码pytorch版本 yolov5源代码_YOLO_78


这张图你看,我们看A点坐标(0.7,0.7),我们在grid上(也就是我们前面生成的推理坐标图层)标记了A点的正样本,也就是我们通过图片进行采样得到的样本实际位置,但是它靠左上角,那么我们会进行一些采样,采上右两个样本进行精度提升。那么我们采样上样本的时候,上样本左上角坐标为(0,1),相对于x的偏移量为

yolov4源码pytorch版本 yolov5源代码_数据_79


但是sigmoid函数只能把数字处理到(0,1)之间,它无法处理得到负数。所以不行,我们需要修改。那么取值到1.5也是同理

yolov4源码pytorch版本 yolov5源代码_透视变换_80

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

yolov4源码pytorch版本 yolov5源代码_矩阵变换_81

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图层的坐标类似下图

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_82


这里得到这些点后我们需要一些筛选 得到我们将要处理的点。

我们需要得到当前图层也就是当前tensor下的坐标 也就是上面那张图ok然后进行筛选 源码如下loss.py里面

yolov4源码pytorch版本 yolov5源代码_矩阵变换_83


图片中灰色的表示我们筛选成功后的

yolov4源码pytorch版本 yolov5源代码_YOLO_84


yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_85


yolov4源码pytorch版本 yolov5源代码_YOLO_86

对于我们得到的中心点筛选后的坐标他们对条件的满足情况用jk表示

yolov4源码pytorch版本 yolov5源代码_透视变换_87

l和m数组同理也是最反转后进行筛选的数组进行条件记录

yolov4源码pytorch版本 yolov5源代码_矩阵变换_88

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

yolov4源码pytorch版本 yolov5源代码_YOLO_89


yolov4源码pytorch版本 yolov5源代码_数据_90


yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_91


最后是添加正样本了

yolov4源码pytorch版本 yolov5源代码_透视变换_92


yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_93

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

根据传统yolov3损失计算我们知道,就是根据真实得到中心点和宽高(xywh)和预测的中心点和宽高进行做差平方。

但是我们GIOU会假如他们的边际值如下图解释很清楚 就是会假如右上角阴影部分来进行计算。为什么呢,因为检测的物体两个框我们需要知道他们真实查的多少,因为查100m也是差,差1m也是差。你要知道两个物体之间到底差多少呢?传统的检测损失不能很好的反应。因为真实框和预测框没有交集的化,也就是离100m或者离1m这个iou都是为0,得到的loss也就是1

yolov4源码pytorch版本 yolov5源代码_透视变换_94


那么根据Giou我们也有另一种损失 改进后叫Diou

yolov4源码pytorch版本 yolov5源代码_YOLO_95

后来又推出了ciou

yolov4源码pytorch版本 yolov5源代码_数据_96

但是我们yolov5的损失计算沿用的还是CIou

yolov4源码pytorch版本 yolov5源代码_YOLO_97


yolov4源码pytorch版本 yolov5源代码_透视变换_98


yolov4源码pytorch版本 yolov5源代码_透视变换_99

损失函数源码解析

在loss.py的差不多91行 中途在定义损失器的时候会有个focalloss方法但是我们不用管,因为yolov5没用 但是如果想看我还是进行了标注和解释。

yolov4源码pytorch版本 yolov5源代码_透视变换_100


这里进行一些属性定义后在进行下面的操作这里主要讲我们如何计算我们的类别损失 目标边框预测损失。。其实上面都讲过这里是全部过一遍。

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_101


yolov4源码pytorch版本 yolov5源代码_透视变换_102


yolov4源码pytorch版本 yolov5源代码_数据_103


build target结束后进入主函数 138进行损失计算

yolov4源码pytorch版本 yolov5源代码_YOLO_104


置信度损失计算

yolov4源码pytorch版本 yolov5源代码_透视变换_105


分类

yolov4源码pytorch版本 yolov5源代码_矩阵变换_106

训练

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

环境搭建

下载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数据集

按照下图创建

yolov4源码pytorch版本 yolov5源代码_矩阵变换_107

下面的代码可以将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进行制作然后标注类别

yolov4源码pytorch版本 yolov5源代码_矩阵变换_108

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

链接服务器训练

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_109


yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_110


yolov4源码pytorch版本 yolov5源代码_矩阵变换_111


成功了就是我这样的

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_112


找到

yolov4源码pytorch版本 yolov5源代码_矩阵变换_113


yolov4源码pytorch版本 yolov5源代码_透视变换_114


正常登录

yolov4源码pytorch版本 yolov5源代码_透视变换_115


yolov4源码pytorch版本 yolov5源代码_数据_116


往下拉

yolov4源码pytorch版本 yolov5源代码_yolov4源码pytorch版本_117


yolov4源码pytorch版本 yolov5源代码_透视变换_118


安装好即可