yolov5yolo.py逐行代码解读

yolo.py解读
2.1 class Model 92~250行
2.1.1 init 94~130行

    def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None):  # model, input channels, number of classes
    # cfg: 可以是字典,也可以是yaml文件路径
    # ch:输入通道数
    # nc:类的个数
    # anchors:所有的anchor列表
    #类的构造函数 __init__() 这个构造函数可以根据提供的参数创建类的实例,并在实例化过程中对实例进行初始化操作。根据提供的参数值,该构造函数可以加载配置文件、设置图像通道数、目标类别数和锚框等属性,以便在后续的类方法中使用。
    #self: 表示类的实例对象自身。在类的方法中,self 参数必须作为第一个参数,用于引用类的实例对象。
        super().__init__()#super(): 这是一个内置函数,用于获取当前类的父类对象。
        #super().__init__(): 这是对父类构造函数的调用。super() 返回父类对象,然后通过调用 __init__() 方法来执行父类的构造函数。
        if isinstance(cfg, dict):# 如果cfg是字典
            self.yaml = cfg 
        else:  # is *.yaml
            import yaml  # 加载yaml模块
            self.yaml_file = Path(cfg).name
            with open(cfg, encoding='ascii', errors='ignore') as f:
                self.yaml = yaml.safe_load(f)  # 从yaml文件中加载出字典

        # Define model
        ch = self.yaml['ch'] = self.yaml.get('ch', ch)
        # ch: 输入通道数。 假如self.yaml有键‘ch’,则将该键对应的值赋给内部变量ch。假如没有‘ch’,则将形参ch赋给内部变量ch
        if nc and nc != self.yaml['nc']:
        # 假如yaml中的nc和方法形参中的nc不一致,则覆盖yaml中的nc。
            LOGGER.info(f"Overriding model.yaml nc={self.yaml['nc']} with nc={nc}")
            self.yaml['nc'] = nc  # override yaml value
        if anchors:
        # 假如yaml中的anchors和方法形参中的anchors不一致,则覆盖yaml中的anchors。
            LOGGER.info(f'Overriding model.yaml anchors with anchors={anchors}')
            self.yaml['anchors'] = round(anchors)  # override yaml value
            
        self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch])  # 得到模型,以及对应的特征图保存标签。
        # 所谓的特征图保存标签是一个列表,它的内容可能是[1,5],表示第1、5层前向传播后的特征图需要保存下来,以便跳跃连接使用
        # 详细解读见2.2
        
        
        self.names = [str(i) for i in range(self.yaml['nc'])]  # 初始化类名列表,默认为[0,1,2...]
        self.inplace = self.yaml.get('inplace', True)

        #确定步长、步长对应的锚框
        m = self.model[-1]  # Detect()
        if isinstance(m, Detect):# 如果模型的最后一层是detect模块
            s = 256  # 2x min stride
            m.inplace = self.inplace
            m.stride = torch.tensor([s / x.shape[-2] for x in self.forward(torch.zeros(1, ch, s, s))])  # 使用一张空白图片作为模型的输入,并以此得到实际步长。(默认的设置中,步长是8,16,32)
            check_anchor_order(m)  # anchor的顺序应该是从小到大,这里排一下序
            m.anchors /= m.stride.view(-1, 1, 1)
            # 得到anchor在实际的特征图中的位置
            # 因为加载的原始anchor大小是相对于原图的像素,但是经过卷积池化之后,图片也就缩放了
            # 对于的anchor也需要缩放操作

            self.stride = m.stride
            self._initialize_biases()  # only run once

        # Init weights, biases
        initialize_weights(self)
        self.info()
        LOGGER.info('')

def parse_model(d, ch):  # model_dict, input_channels(3)
    LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10}  {'module':<40}{'arguments':<30}")# 打印信息,
    anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple']# 加载字典中的anchors、nc、depth_multiple、width_multiple。
    na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors
    # na:anchor的数量
    
    #anchors的形式见“1.1 yaml文件解读”,以yolov5s.yaml中定义的为例,
    #anchors[0]是第一行的一个anchors,它的长度是6,表示3个w-h对,即3个anchor,这里的na也应当为3
    
    no = na * (nc + 5)  
    # 每一个anchor输出的数据数量 = anchors * (classes + 5)
    # 其中5代表x,y,w,h,conf五个量

    layers, save, c2 = [], [], ch[-1]
    # layers: 所有的网络层
    # save: 标记该层网络的特征图是否需要保存(因为模型中存在跳跃连接,有的特征图之后需要用到)比如save=[1,2,5]则第1,2,5层需要保存特征图
    # ch 该层所输出的通道数,比如save[i]=n表示第i层输出通道数为n




    for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']):  
    # f, n, m, args分别对应from, number, module, args
        m = eval(m) if isinstance(m, str) else m  # 根据字符串m的内容创建类
        #isinstance检测m是否为字符串,eval:将m解析为python表达式
        for j, a in enumerate(args):
        # args是一个列表,这一步把列表中的内容取出来
            try:
                args[j] = eval(a) if isinstance(a, str) else a  # eval strings
            except NameError:
                pass

        n = n_ = max(round(n * gd), 1) if n > 1 else n
        # 将深度与深度因子相乘,计算层深度。深度最小为1. 
        if m in [Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv,
                 BottleneckCSP, C3, C3TR, C3SPP, C3Ghost]:
                 # 假如module名字正确,则开始加载
            c1, c2 = ch[f], args[0]
            # c1: 输入通道数 c2:输出通道数
            
            if c2 != no:  # 该层不是最后一层,则将通道数乘以宽度因子
                c2 = make_divisible(c2 * gw, 8)
			# 也就是说,宽度因子作用于除了最后一层之外的所有层

            args = [c1, c2, *args[1:]]
			# 将前面的运算结果保存在args中,它也就是最终的方法参数。
#args[1:]是列表的切片,表示从索引位置1开始,()不包括1,获取列表的所有元素,*args[1:]表示扩展操作符,用于将每个元素解包成单独的元素。
# 上面这几步主要处理了一层网络的参数
            if m in [BottleneckCSP, C3, C3TR, C3Ghost]: # 根据每层网络参数的不同,分别处理参数
            #具体各个类的参数是什么请参考它们的__init__方法,这里不再详细解释了
                args.insert(2, n) 
                n = 1
        elif m is nn.BatchNorm2d:
            args = [ch[f]]
        elif m is Concat:
            c2 = sum(ch[x] for x in f)
        elif m is Detect:
            args.append([ch[x] for x in f])
            if isinstance(args[1], int):  # number of anchors
                args[1] = [list(range(args[1] * 2))] * len(f)
        elif m is Contract:
            c2 = ch[f] * args[0] ** 2
        elif m is Expand:
            c2 = ch[f] // args[0] ** 2
        else:
            c2 = ch[f]

        m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) 
        # 构建整个网络模块
        # 如果n>1,就需要加入n层网络。
        #nn.Sequential(创建一个顺序的神经网络,
        
        t = str(m)[8:-2].replace('__main__.', '')  # t是类名
        #replace字符串替换操作
        np = sum(x.numel() for x in m_.parameters())  # np: 参数个数
        m_.i, m_.f, m_.type, m_.np = i, f, t, np  # attach index, 'from' index, type, number params
        LOGGER.info(f'{i:>3}{str(f):>18}{n_:>3}{np:10.0f}  {t:<40}{str(args):<30}')  # print
        save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1)
		#如果x不是-1,则将其保存在save列表中,表示该层需要保存特征图
		
        layers.append(m_)# 将新创建的layer添加到layers数组中
        if i == 0: # 如果是初次迭代,则新创建一个ch(因为形参ch在创建第一个网络模块时需要用到,所以创建网络模块之后再初始化ch)
            ch = []
        ch.append(c2)
    return nn.Sequential(*layers), sorted(save)# 将所有的层封装为nn.Sequential
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值