第二十五章 解析cfg文件及读取获得网络结构

网络结构

以YOLOv3_SPP为例
在这里插入图片描述

cfg文件

部分,只是用来展示,全部的代码在文章最后

[net]
# Testing
# batch=1
# subdivisions=1
# Training
batch=64         
subdivisions=16  
width=608        
height=608       
channels=3       
momentum=0.9     
decay=0.0005     
angle=0          
saturation = 1.5  
exposure = 1.5 
hue=.1    

learning_rate=0.001  
burn_in=1000   
max_batches = 500200 
policy=steps  
steps=400000,450000 
scales=.1,.1  

[convolutional]
batch_normalize=1 
filters=32    
size=3      
stride=1       
pad=1        
activation=leaky  

# Downsample

[convolutional]    
batch_normalize=1
filters=64
size=3
stride=2          
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=32
size=1
stride=1
pad=1
activation=leaky

分析结构

图形化分析

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

更加详尽的分析

1. Net层

[net]
#Testing
#batch=1 
#subdivisions=1
#在测试的时候,设置batch=1,subdivisions=1
#Training
batch=16
subdivisions=4
#这里的batch与普遍意义上的batch不是一致的。
#训练的过程中将一次性加载16张图片进内存,然后分4次完成前向传播,每次4张。
#经过16张图片的前向传播以后,进行一次反向传播。
width=416
height=416
channels=3
#设置图片进入网络的宽、高和通道个数。
#由于YOLOv3的下采样一般是32倍,所以宽高必须能被32整除。
#多尺度训练选择为32的倍数最小320*320,最大608*608。
#长和宽越大,对小目标越好,但是占用显存也会高,需要权衡。
momentum=0.9
#动量参数影响着梯度下降到最优值得速度。
decay=0.0005
#权重衰减正则项,防止过拟合。
angle=0
#数据增强,设置旋转角度。
saturation = 1.5
#饱和度
exposure = 1.5
#曝光量
hue=.1
#色调

learning_rate=0.001
#学习率:刚开始训练时可以将学习率设置的高一点,而一定轮数之后,将其减小。
#在训练过程中,一般根据训练轮数设置动态变化的学习率。
burn_in=1000
max_batches = 500200
#最大batch
policy=steps
#学习率调整的策略,有以下policy:
#constant, steps, exp, poly, step, sig, RANDOM,constant等方式
#调整学习率的policy,        
#有如下policy:constant, steps, exp, poly, step, sig, RANDOM。
#steps#比较好理解,按照steps来改变学习率。

steps=400000,450000
scales=.1,.1
#在达到40000、45000的时候将学习率乘以对应的scale

2. 卷积层

[convolutional]
batch_normalize=1    		
#是否做BN操作
filters=32                  
#输出特征图的数量
size=3               		
#卷积核的尺寸
stride=1                	
#做卷积运算的步长
pad=1               		
#如果pad为0,padding由padding参数指定。
#如果pad为1,padding大小为size/2,padding应该是对输入图像左边缘拓展的像素数量
activation=leaky     		
#激活函数的类型:logistic,loggy,relu,
#elu,relie,plse,hardtan,lhtan,
#linear,ramp,leaky,tanh,stair
# alexeyAB版添加了mish, swish, nrom_chan等新的激活函数

feature map计算公式:

OutFeature=\frac{InFeature+2\times padding-size}{stride}+1 \

3. 下采样

可以通过调整卷积层参数进行下采样:

[convolutional]
batch_normalize=1
filters=128
size=3
stride=2
pad=1
activation=leaky

可以通过带入以上公式,可以得到OutFeature是InFeature的一半。

也可以使用maxpooling进行下采样:

[maxpool]
size=2
stride=2

4. 上采样

[upsample]
stride=2

上采样是通过线性插值实现的。

5. Shortcut和Route层

[shortcut]
from=-3
activation=linear
#shortcut操作是类似ResNet的跨层连接,参数from是−3,
#意思是shortcut的输出是当前层与先前的倒数第三层相加而得到。
# 通俗来讲就是add操作

[route]
layers = -1, 36
# 当属性有两个值,就是将上一层和第36层进行concate
#即沿深度的维度连接,这也要求feature map大小是一致的。
[route]
layers = -4
#当属性只有一个值时,它会输出由该值索引的网络层的特征图。 
#本例子中就是提取从当前倒数第四个层输出

6. YOLO层

[convolutional]
size=1
stride=1
pad=1
filters=18
#每一个[region/yolo]层前的最后一个卷积层中的 
#filters=num(yolo层个数)*(classes+5) ,5的意义是5个坐标,
#代表论文中的tx,ty,tw,th,po
#这里类别个数为1,(1+5)*3=18
activation=linear

[yolo]                  	
mask = 6,7,8 				
#训练框mask的值是0,1,2,			
#这意味着使用第一,第二和第三个anchor
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,\
		  59,119,  116,90,  156,198,  373,326
# 总共有三个检测层,共计9个anchor
# 这里的anchor是由kmeans聚类算法得到的。
classes=1 
#类别个数
num=9     			
#每个grid预测的BoundingBox num/yolo层个数
jitter=.3    		
#利用数据抖动产生更多数据,
#属于TTA(Test Time Augmentation)
ignore_thresh = .5
# ignore_thresh 指得是参与计算的IOU阈值大小。
#当预测的检测框与ground true的IOU大于ignore_thresh的时候,
#不会参与loss的计算,否则,检测框将会参与损失计算。
#目的是控制参与loss计算的检测框的规模,当ignore_thresh过于大,
#接近于1的时候,那么参与检测框回归loss的个数就会比较少,同时也容易造成过拟合;
#而如果ignore_thresh设置的过于小,那么参与计算的会数量规模就会很大。
#同时也容易在进行检测框回归的时候造成欠拟合。
#ignore_thresh 一般选取0.5-0.7之间的一个值
# 小尺度(13*13)用的是0.7,
# 大尺度(26*26)用的是0.5。

读取代码的实现

有详细注释

from build_utils.layers import *
from build_utils.parse_config import *

ONNX_EXPORT = False


def create_modules(modules_defs: list, img_size):
    """
    Constructs module list of layer blocks from module configuration in module_defs
    :param modules_defs: 通过.cfg文件解析得到的每个层结构的列表
    :param img_size: 输入图像的大小
    :return: 构建的模块列表
    """

    img_size = [img_size] * 2 if isinstance(img_size, int) else img_size
    # 如果img_size是整数类型,将其转化为长度为2的列表,否则保持原样
    modules_defs.pop(0)  # cfg training hyperparams (unused)
    # 删除模块定义列表的第一个元素,这个元素对应于"[net]"的配置,我们不需要它
    output_filters = [3]  # input channels
    # 初始化输出滤波器列表,值为3,对应输入通道数
    module_list = nn.ModuleList()
    # 初始化一个nn.ModuleList对象,用于保存构建的模块列表
    routs = []  # list of layers which rout to deeper layers
    # 初始化一个列表,用于保存那些输出会流向更深层的层的索引
    yolo_index = -1
    # 初始化yolo层的索引为-1,表示还没有找到yolo层

    # 遍历模块定义列表中的每个元素
    for i, mdef in enumerate(modules_defs):
        modules = nn.Sequential()
        # 创建一个nn.Sequential对象,用于保存一个层中的多个模块

        if mdef["type"] == "convolutional":
            bn = mdef["batch_normalize"]  # 1 or 0 / use or not
            filters = mdef["filters"]
            k = mdef["size"]  # kernel size
            stride = mdef["stride"] if "stride" in mdef else (mdef['stride_y'], mdef["stride_x"])
            # 如果层的类型是"convolutional":
            if isinstance(k, int):
                modules.add_module("Conv2d", nn.Conv2d(in_channels=output_filters[-1],
                                                       out_channels=filters,
                                                       kernel_size=k,
                                                       stride=stride,
                                                       padding=k // 2 if mdef["pad"] else 0,
                                                       bias=not bn))
            else:
                raise TypeError("conv2d filter size must be int type.")
            # 添加一个nn.Conv2d层,参数包括输入通道数、输出通道数、卷积核大小、步长、填充等
            # 如果卷积核大小不是整数类型,会抛出TypeError异常

            if bn:
                modules.add_module("BatchNorm2d", nn.BatchNorm2d(filters))
            else:
                # 如果该卷积操作没有bn层,意味着该层为yolo的predictor
                routs.append(i)  # detection output (goes into yolo layer)
            # 如果存在bn层,添加一个nn.BatchNorm2d层;否则将该层的索引添加到routs列表中,该层为yolo层的predictor层

            if mdef["activation"] == "leaky":
                modules.add_module("activation", nn.LeakyReLU(0.1, inplace=True))
            else:
                pass  # 如果存在激活函数为"leaky",添加一个nn.LeakyReLU层;否则不添加任何东西

        if mdef["type"] == "BatchNorm2d":
                # BatchNorm2d层的创建方法,代码中未给出具体实现,这里用pass表示
                pass
        # 如果"type"是"maxpool",表示这是一个最大池化层
        elif mdef["type"] == "maxpool":
            # 获取最大池化层的核大小(k)和步长(stride)
            k = mdef["size"]  # kernel size
            stride = mdef["stride"]
            # 根据核大小和步长创建最大池化层
            modules = nn.MaxPool2d(kernel_size=k, stride=stride, padding=(k - 1) // 2)
        # 如果"type"是"upsample",表示这是一个上采样层
        elif mdef["type"] == "upsample":
            # 如果在ONNX导出模式下,明确地设置上采样层的大小,避免使用scale_factor
            if ONNX_EXPORT:  # 如果在ONNX导出模式下,判断是否需要明确设置上采样层的大小
                # 计算增益(gain)g,通过yolo_index和img_size计算
                g = (yolo_index + 1) * 2 / 32  # gain
                # 根据增益g设置上采样层的大小
                modules = nn.Upsample(size=tuple(int(x * g) for x in img_size))
            else:
                # 在非ONNX导出模式下,使用scale_factor设置上采样层的大小
                modules = nn.Upsample(scale_factor=mdef["stride"])
        # 如果"type"是"route",表示这是一个路由层(用于网络中的特征融合)
        elif mdef["type"] == "route":  # [-2],  [-1,-3,-5,-6], [-1, 61]
            # 获取路由层的层次信息(layers)
            layers = mdef["layers"]
            # 计算路由层的特征图总输出通道数(filters)
            filters = sum([output_filters[l + 1 if l > 0 else l] for l in layers])
            # 将路由层的层次信息添加到routs列表中
            routs.extend([i + l if l < 0 else l for l in layers])
            # 根据路由层的层次信息创建特征融合层(FeatureConcat)
            modules = FeatureConcat(layers=layers)
        # 如果"type"是"shortcut",表示这是一个快捷连接层(用于网络中的残差连接)
        elif mdef["type"] == "shortcut":
            # 获取快捷连接层的输入层次信息(layers)和输出通道数(filters)
            layers = mdef["from"]
            filters = output_filters[-1]
            # 将快捷连接层的输入层次信息添加到routs列表中
            # routs.extend([i + l if l < 0 else l for l in layers])  这一行代码在原代码中被注释掉,可能是误操作,这里将其取消注释
            routs.append(i + layers[0])  # 这里假设输入的快捷连接层只有一个输入层次,所以直接用i + layers[0]表示其位置关系
            # 根据快捷连接层的输入层次信息创建加权特征融合层(WeightedFeatureFusion)
            modules = WeightedFeatureFusion(layers=layers, weight="weights_type" in mdef)  # 这里假设weight的判断条件是固定的,如果需要根据不同情况设置不同的weight,需要修改此处代码。

        elif mdef["type"] == "yolo":  # 如果模块类型是"yolo",执行以下代码
            yolo_index += 1  # 记录这是第几个yolo层,从0开始计数 [0, 1, 2]
            stride = [32, 16, 8]  # 预测特征层对应原图的缩放比例

            # 根据mdef中的信息创建一个YOLOLayer对象
            modules = YOLOLayer(anchors=mdef["anchors"][mdef["mask"]],  # anchor list
                                nc=mdef["classes"],  # number of classes
                                img_size=img_size,
                                stride=stride[yolo_index])

            # 对于初始化前的Conv2d()偏置进行初始化 (https://arxiv.org/pdf/1708.02002.pdf section 3.3)
            try:
                j = -1  # j初始化为-1,代表使用最后一个模块进行初始化
                # bias: shape(255,) 索引0对应Sequential中的Conv2d
                # view: shape(3, 85)
                b = module_list[j][0].bias.view(modules.na, -1)  # 获取最后一个模块的偏置,并重新塑造为(modules.na, -1)的形状
                b.data[:, 4] += -4.5  # obj 调整第4列的偏置值,可能是对某些特定对象检测的偏置进行调整
                b.data[:, 5:] += math.log(0.6 / (modules.nc - 0.99))  # cls (sigmoid(p) = 1/nc) 对第5列及之后的列进行偏置调整,可能是对类别检测的偏置进行调整
                module_list[j][0].bias = torch.nn.Parameter(b.view(-1), requires_grad=True)  # 将调整后的偏置作为参数返回,并设置为需要梯度更新
            except Exception as e:  # 如果出现异常
                print('WARNING: smart bias initialization failure.', e)  # 打印警告信息,说明智能偏置初始化失败,并打印异常信息
        else:  # 如果模块类型不是"yolo"
            print("Warning: Unrecognized Layer Type: " + mdef["type"])  # 打印警告信息,说明模块类型未被识别或不支持

        # 将创建的模块列表和输出过滤器列表添加到总的列表中
        module_list.append(modules)
        output_filters.append(filters)

        # 为每个模块定义创建一个二进制路由标记,初始值都为False
        routs_binary = [False] * len(modules_defs)
        for i in routs:  # 对每个路由进行遍历
            routs_binary[i] = True  # 将对应的路由标记设置为True,表示存在该路由
        return module_list, routs_binary  # 返回模块列表和路由标记列表


# 定义一个YOLOLayer类,继承自torch.nn.Module
class YOLOLayer(nn.Module):
    """
    对YOLO的输出进行处理
    """
    # 初始化函数
    def __init__(self, anchors, nc, img_size, stride):
        # 调用父类的初始化函数
        super(YOLOLayer, self).__init__()
        # 定义anchors张量,这是先验框的尺寸
        self.anchors = torch.Tensor(anchors)
        # 定义特征图上一步对应原图上的步距
        self.stride = stride  # layer stride 特征图上一步对应原图上的步距 [32, 16, 8]
        # 定义先验框的数量
        self.na = len(anchors)  # number of anchors (3)
        # 定义类别数量
        self.nc = nc  # number of classes (80)
        # 定义输出的数量(包括obj,cls1等)
        self.no = nc + 5  # number of outputs (85: x, y, w, h, obj, cls1, ...)
        # 初始化网格数量为0
        self.nx, self.ny, self.ng = 0, 0, (0, 0)  # initialize number of x, y gridpoints
        # 将anchors大小缩放到grid尺度
        self.anchor_vec = self.anchors / self.stride  # batch_size, na, grid_h, grid_w, wh, 值为1的维度对应的值不是固定值,后续操作可根据broadcast广播机制自动扩充
        self.anchor_wh = self.anchor_vec.view(1, self.na, 1, 1, 2)  # batch_size, na, grid_h, grid_w, wh
        self.grid = None  # grid变量初始化为None

        # 如果是在导出ONNX模型,设置训练状态为False,同时根据给定的img_size创建grid变量
        if ONNX_EXPORT:  # 如果ONNX_EXPORT为True,进入该条件块
            self.training = False  # 设置训练状态为False
            self.create_grids((img_size[1] // stride, img_size[0] // stride))  # number x, y grid points 根据给定的img_size和stride来创建grid变量

    # 定义一个名为create_grids的方法,该方法属于某个类,该类具有self(即实例本身)作为第一个参数
    def create_grids(self, ng=(13, 13), device="cpu"):
            """
            更新grids信息并生成新的grids参数
            :param ng: 特征图大小,默认为(13, 13)
            :param device: 设备类型,默认为"cpu"
            :return: 无返回值,但会更新类实例的属性值
            """

            # 将传入的ng参数赋值给self.nx和self.ny,表示特征图的横纵尺寸
            self.nx, self.ny = ng

            # 将ng参数转换为torch.tensor类型,并赋值给self.ng,用于记录特征图的尺寸
            self.ng = torch.tensor(ng, dtype=torch.float)

            # 构建xy offsets,即每个网格单元处的anchor的xy偏移量(相对于特征图原点)
            # build xy offsets 构建每个cell处的anchor的xy偏移量(在feature map上的)
            if not self.training:  # 如果当前不是训练模式...
                # 使用torch.meshgrid函数生成网格坐标,yv为纵坐标,xv为横坐标
                yv, xv = torch.meshgrid([torch.arange(self.ny, device=device),
                                        torch.arange(self.nx, device=device)])

                # 将生成的横纵坐标进行堆叠,并通过view方法变换形状,成为(1, 1, self.ny, self.nx, 2)的张量
                # batch_size, na, grid_h, grid_w, wh
                self.grid = torch.stack((xv, yv), 2).view((1, 1, self.ny, self.nx, 2)).float()

            # 如果anchor_vec的设备类型和当前的device不一致,就将anchor_vec和anchor_wh移动到当前的device上
            if self.anchor_vec.device != device:
                self.anchor_vec = self.anchor_vec.to(device)
                self.anchor_wh = self.anchor_wh.to(device)

    # 定义一个名为forward的函数,它是模型的前向传播过程
    def forward(self, p):
        # 判断是否是在导出ONNX模型,如果是则设置batch size为1,否则从输入参数中获取batch size
        if ONNX_EXPORT:
            bs = 1  # batch size
        else:
            # 获取输入参数p的形状,并将三个维度分别赋值给bs(batch size)、ny(网络高度)、nx(网络宽度)
            bs, _, ny, nx = p.shape  # batch_size, predict_param(255), grid(13), grid(13)
            # 检查网络尺寸是否与之前保存的尺寸相同,如果不同则重新创建网格,并赋值给self.grid
            if (self.nx, self.ny) != (nx, ny) or self.grid is None:  # fix no grid bug
                self.create_grids((nx, ny), p.device)

        # 将输入参数的形状从(batch_size, 255, 13, 13)变换为(batch_size, 3, 85, 13, 13),85是由255*3得到的
        # permute操作将维度顺序变换,具体为:先保证第一维度不动,然后依次将后面的维度进行置换
        # 这里的意思是将(batch_size, 255, 13, 13)变换为(batch_size, 3, 85, 13, 13),然后再将其变换为(batch_size, 3, 13, 13, 85)
        # [bs, anchor, grid, grid, xywh + obj + classes]
        p = p.view(bs, self.na, self.no, self.ny, self.nx).permute(0, 1, 3, 4, 2).contiguous()  # prediction

        # 如果模型处于训练状态,则直接返回预测结果p
        if self.training:
            return p
        # 如果是在导出ONNX模型,进行以下操作
        elif ONNX_EXPORT:
            # 为了避免ANN操作中的广播机制,计算锚点数量m,它是self.na、self.nx、self.ny三个值的乘积
            # 将self.ng的值重复m次,得到新的ng,然后计算其倒数ng
            m = self.na * self.nx * self.ny  # 3*
            ng = 1. / self.ng.repeat(m, 1)
            # 将self.grid的值重复(1, self.na, 1, 1, 1)次,得到新的grid,然后将其形状变换为(m, 2)
            grid = self.grid.repeat(1, self.na, 1, 1, 1).view(m, 2)
            # 将self.anchor_wh的值重复(1, 1, self.nx, self.ny, 1)次,得到新的anchor_wh,然后将其形状变换为(m, 2),并乘以ng
            anchor_wh = self.anchor_wh.repeat(1, 1, self.nx, self.ny, 1).view(m, 2) * ng

            # p = p.view(m, self.no)  # 调整张量的形状,以适应后续操作。m可能是一个预设的形状或者是某个特定形状的函数。self.no表示类别数量。
            p = p.view(m, self.no)
            # xy = torch.sigmoid(p[:, 0:2]) + grid  # x, y 通过sigmoid函数处理p的前两列并加上grid,grid可能表示网格坐标
            # wh = torch.exp(p[:, 2:4]) * anchor_wh  # width, height 对p的第3列到第5列应用指数函数处理并乘以anchor_wh(锚框的宽高)
            # p_cls = torch.sigmoid(p[:, 4:5]) if self.nc == 1 else \  # conf 使用sigmoid函数处理p的第5列,如果类别数量为1,否则处理前两个类别并乘以p的第5列
            p[:, :2] = (torch.sigmoid(p[:, 0:2]) + grid) * ng  # x, y 对p的前两列应用sigmoid函数并加上grid,然后乘以ng(某种缩放系数)
            p[:, 2:4] = torch.exp(p[:, 2:4]) * anchor_wh  # width, height 对p的第3列到第5列应用指数函数处理并乘以anchor_wh(锚框的宽高)
            p[:, 4:] = torch.sigmoid(p[:, 4:])  # 对p的第5列后的所有列应用sigmoid函数
            p[:, 5:] = p[:, 5:self.no] * p[:, 4:5]  # 对p的第5列后的所有列与第4列后的某一列相乘
            return p  # 返回处理后的p
        else:  # 判断是否处于推理模式
            # 推理模式下的操作
            # [bs, anchor, grid, grid, xywh + obj + classes]克隆p,创建一个新的张量io,其中包含和p相同的数据,但某些元素可能被修改
            io = p.clone()  # inference output
            io[..., :2] = torch.sigmoid(io[..., :2]) + self.grid  # xy 计算在feature map上的xy坐标 通过sigmoid函数处理io的前两列并加上self.grid(可能表示在feature map上的网格坐标)
            io[..., 2:4] = torch.exp(io[..., 2:4]) * self.anchor_wh  # wh yolo method 计算在feature map上的wh 对io的第3列到第5列应用指数函数处理并乘以self.anchor_wh(锚框的宽高)
            io[..., :4] *= self.stride  # 换算映射回原图尺度 io的前四列元素乘以self.stride(某种缩放系数)
            torch.sigmoid_(io[..., 4:])  # 对io的第5列后的所有列应用sigmoid函数
            return io.view(bs, -1, self.no), p  # view [1, 3, 13, 13, 85] as [1, 507, 85] 最后返回一个形如[bs, -1, self.no]的张量和一个原始的p张量

class Darknet(nn.Module):
    """
    YOLOv3 spp object detection model
    """
    def __init__(self, cfg, img_size=(416, 416), verbose=False):  
        super(Darknet, self).__init__()
        # 这里传入的img_size只在导出ONNX模型时起作用
        self.input_size = [img_size] * 2 if isinstance(img_size, int) else img_size
        # 解析网络对应的.cfg文件
        self.module_defs = parse_model_cfg(cfg)
        # 根据解析的网络结构一层一层去搭建
        self.module_list, self.routs = create_modules(self.module_defs, img_size)
        # 获取所有YOLOLayer层的索引
        self.yolo_layers = get_yolo_layers(self)

        # 打印下模型的信息,如果verbose为True则打印详细信息
        self.info(verbose) if not ONNX_EXPORT else None  # print model description

    def forward(self, x, verbose=False):
        return self.forward_once(x, verbose=verbose)

    def forward_once(self, x, verbose=False):
        # yolo_out收集每个yolo_layer层的输出
        # out收集每个模块的输出
        yolo_out, out = [], []  # 初始化两个空列表,用于存储YOLO层的输出和所有模块的输出
        if verbose:
            print('0', x.shape)  # 如果verbose为True,打印输入x的形状
            str = ""  # 初始化一个空字符串str,用于后续打印信息

        for i, module in enumerate(self.module_list):  # 遍历module_list中的每个模块,并用enumerate函数获取模块的索引i
            name = module.__class__.__name__  # 获取当前模块的类名
            if name in ["WeightedFeatureFusion", "FeatureConcat"]:  # 如果类名为"WeightedFeatureFusion"或"FeatureConcat"
                if verbose:
                    l = [i - 1] + module.layers  # 获取模块的层次列表,列表首项为当前模块索引,后面为上层模块索引
                    sh = [list(x.shape)] + [list(out[i].shape) for i in module.layers]  # 获取输入x的形状和上层模块输出的形状列表
                    str = ' >> ' + ' + '.join(['layer %g %s' % x for x in zip(l, sh)])  # 拼接字符串,用于打印信息
                x = module(x, out)  # 调用WeightedFeatureFusion或FeatureConcat方法,输入为x和上层模块的输出
            elif name == "YOLOLayer":  # 如果类名为"YOLOLayer"
                yolo_out.append(module(x))  # 将YOLO层的输出添加到yolo_out列表中
            else:  # 如果类名不属于["WeightedFeatureFusion", "FeatureConcat", "YOLOLayer"]中的任何一个
                x = module(x)  # 直接运行该模块,例如卷积、上采样、最大池化、批量归一化等操作

            out.append(x if self.routs[i] else [])  # 如果self.routs[i]为True,将当前模块的输出添加到out列表中,否则添加空列表
            if verbose:
                print('%g/%g %s -' % (i, len(self.module_list), name), list(x.shape), str)  # 打印模块索引、类名、输入形状和之前拼接的字符串str
                str = ''  # 重置字符串str为空,用于下一次拼接

        if self.training:  # train
            return yolo_out
        elif ONNX_EXPORT:  # export
            # x = [torch.cat(x, 0) for x in zip(*yolo_out)]
            # return x[0], torch.cat(x[1:3], 1)  # scores, boxes: 3780x80, 3780x4
            p = torch.cat(yolo_out, dim=0)

            # # 根据objectness虑除低概率目标
            # mask = torch.nonzero(torch.gt(p[:, 4], 0.1), as_tuple=False).squeeze(1)
            # # onnx不支持超过一维的索引(pytorch太灵活了)
            # # p = p[mask]
            # p = torch.index_select(p, dim=0, index=mask)
            #
            # # 虑除小面积目标,w > 2 and h > 2 pixel
            # # ONNX暂不支持bitwise_and和all操作
            # mask_s = torch.gt(p[:, 2], 2./self.input_size[0]) & torch.gt(p[:, 3], 2./self.input_size[1])
            # mask_s = torch.nonzero(mask_s, as_tuple=False).squeeze(1)
            # p = torch.index_select(p, dim=0, index=mask_s)  # width-height 虑除小目标
            #
            # if mask_s.numel() == 0:
            #     return torch.empty([0, 85])

            return p
        else:  # inference or test
            x, p = zip(*yolo_out)  # inference output, training output
            x = torch.cat(x, 1)  # cat yolo outputs

            return x, p

    def info(self, verbose=False):
        """
        打印模型的信息
        :param verbose:
        :return:
        """
        torch_utils.model_info(self, verbose)


def get_yolo_layers(self):
    """
    获取网络中三个"YOLOLayer"模块对应的索引
    :param self:
    :return:
    """
    return [i for i, m in enumerate(self.module_list) if m.__class__.__name__ == 'YOLOLayer']  # [89, 101, 113]
import os
import numpy as np


def parse_model_cfg(path: str):
    # 检查文件是否存在
    if not path.endswith(".cfg") or not os.path.exists(path):
        raise FileNotFoundError("the cfg file not exist...")

    # 读取文件信息
    with open(path, "r") as f:
        lines = f.read().split("\n")

    # 去除空行和注释行
    lines = [x for x in lines if x and not x.startswith("#")]
    # 去除每行开头和结尾的空格符
    lines = [x.strip() for x in lines]

    mdefs = []  # module definitions
    for line in lines:
        if line.startswith("["):  # this marks the start of a new block
            mdefs.append({})
            mdefs[-1]["type"] = line[1:-1].strip()  # 记录module类型
            # 如果是卷积模块,设置默认不使用BN(普通卷积层后面会重写成1,最后的预测层conv保持为0)
            if mdefs[-1]["type"] == "convolutional":
                mdefs[-1]["batch_normalize"] = 0
        else:
            key, val = line.split("=")
            key = key.strip()
            val = val.strip()

            if key == "anchors":
                # anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
                val = val.replace(" ", "")  # 将空格去除
                mdefs[-1][key] = np.array([float(x) for x in val.split(",")]).reshape((-1, 2))  # np anchors
            elif (key in ["from", "layers", "mask"]) or (key == "size" and "," in val):
                mdefs[-1][key] = [int(x) for x in val.split(",")]
            else:
                # TODO: .isnumeric() actually fails to get the float case
                if val.isnumeric():  # return int or float 如果是数值的情况
                    mdefs[-1][key] = int(val) if (int(val) - float(val)) == 0 else float(val)
                else:
                    mdefs[-1][key] = val  # return string  是字符的情况

    # check all fields are supported
    supported = ['type', 'batch_normalize', 'filters', 'size', 'stride', 'pad', 'activation', 'layers', 'groups',
                 'from', 'mask', 'anchors', 'classes', 'num', 'jitter', 'ignore_thresh', 'truth_thresh', 'random',
                 'stride_x', 'stride_y', 'weights_type', 'weights_normalization', 'scale_x_y', 'beta_nms', 'nms_kind',
                 'iou_loss', 'iou_normalizer', 'cls_normalizer', 'iou_thresh', 'probability']

    # 遍历检查每个模型的配置
    for x in mdefs[1:]:  # 0对应net配置
        # 遍历每个配置字典中的key值
        for k in x:
            if k not in supported:
                raise ValueError("Unsupported fields:{} in cfg".format(k))

    return mdefs
import torch.nn.functional as F
from .utils import *

# 定义一个名为FeatureConcat的类,继承自nn.Module类,nn.Module是所有神经网络模块的基类
class FeatureConcat(nn.Module):
    """
    # 类的文档字符串,用于解释类的功能
    将多个特征矩阵在channel维度进行concatenate拼接
    """
    # 初始化函数,在创建类的实例时被调用
    def __init__(self, layers):
        # 调用父类的初始化函数,这是Python的继承机制的一部分
        super(FeatureConcat, self).__init__()
        # 将传入的参数layers赋值给self.layers,layer indices表示层的索引
        self.layers = layers  # layer indices
        # 判断是否有多层,如果layers的长度大于1则说明有多个层,将结果赋值给self.multiple
        self.multiple = len(layers) > 1  # multiple layers flag

    # 前向传播函数,在计算输出时被调用
    def forward(self, x, outputs):
        # 如果有多层(即self.multiple为True),则使用torch.cat函数将outputs中对应self.layers的元素在channel维度拼接起来并返回,否则直接返回outputs中对应self.layers的元素
        return torch.cat([outputs[i] for i in self.layers], 1) if self.multiple else outputs[self.layers[0]]


# 定义一个名为WeightedFeatureFusion的类,继承自nn.Module,用于实现特征矩阵的加权融合
class WeightedFeatureFusion(nn.Module):  # weighted sum of 2 or more layers https://arxiv.org/abs/1911.09070
    """
    将多个特征矩阵的值进行融合(add操作)
    """
    def __init__(self, layers, weight=False):
        # 调用父类的构造函数,进行初始化操作
        super(WeightedFeatureFusion, self).__init__()
        # 定义成员变量layers,存储要进行融合的特征矩阵的索引
        self.layers = layers  # layer indices
        # 定义成员变量weight,表示是否应用权重,默认不应用权重
        self.weight = weight  # apply weights boolean
        # 定义成员变量n,表示融合的特征矩阵个数加1
        self.n = len(layers) + 1  # number of layers 融合的特征矩阵个数
        # 如果应用权重,则定义成员变量w为nn.Parameter类型,初始值为torch.zeros(self.n),要求计算梯度
        if weight:
            self.w = nn.Parameter(torch.zeros(self.n), requires_grad=True)  # layer weights

    def forward(self, x, outputs):
        # 前向传播函数,输入x为输入特征矩阵,outputs为所有层的输出特征矩阵
        # Weights
        if self.weight:
            # 如果应用权重,则对权重进行sigmoid激活函数处理,使得权重在0-1之间
            w = torch.sigmoid(self.w) * (2 / self.n)  # sigmoid weights (0-1)
            # 对输入特征矩阵x乘以权重w的第一个元素
            x = x * w[0]

        # Fusion
        nx = x.shape[1]  # 输入特征矩阵x的通道数
        for i in range(self.n - 1):
            a = outputs[self.layers[i]] * w[i + 1] if self.weight else outputs[self.layers[i]]  # feature to add
            na = a.shape[1]  # 特征矩阵a的通道数
            # Adjust channels
            # 根据相加的两个特征矩阵的channel选择相加方式
            if nx == na:  # same shape 如果channel相同,直接相加
                x = x + a
            elif nx > na:  # slice input 如果channel不同,将channel多的特征矩阵砍掉部分channel保证相加的channel一致
                x[:, :na] = x[:, :na] + a  # or a = nn.ZeroPad2d((0, 0, 0, 0, 0, dc))(a); x = x + a
            else:  # slice feature
                x = x + a[:, :nx]
        return x  # 返回融合后的特征矩阵x

全部cfg

[net]
# Testing
# batch=1
# subdivisions=1
# Training
batch=64         
subdivisions=16  
width=608        
height=608       
channels=3       
momentum=0.9     
decay=0.0005     
angle=0          
saturation = 1.5  
exposure = 1.5 
hue=.1    

learning_rate=0.001  
burn_in=1000   
max_batches = 500200 
policy=steps  
steps=400000,450000 
scales=.1,.1  

[convolutional]
batch_normalize=1 
filters=32    
size=3      
stride=1       
pad=1        
activation=leaky  

# Downsample

[convolutional]    
batch_normalize=1
filters=64
size=3
stride=2          
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=32
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=64
size=3
stride=1
pad=1
activation=leaky

[shortcut]    
from=-3      
activation=linear  

# Downsample

[convolutional]
batch_normalize=1
filters=128
size=3
stride=2
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=64
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=128
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

# Downsample

[convolutional]
batch_normalize=1
filters=256
size=3
stride=2
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear


[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

# Downsample

[convolutional]
batch_normalize=1
filters=512
size=3
stride=2
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear


[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear


[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear


[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear


[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear


[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

# Downsample

[convolutional]
batch_normalize=1
filters=1024
size=3
stride=2
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=1024
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=1024
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=1024
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
filters=1024
size=3
stride=1
pad=1
activation=leaky

[shortcut]
from=-3
activation=linear

######################

[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=1024
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky

### SPP ###
[maxpool]
stride=1
size=5

[route]
layers=-2

[maxpool]
stride=1
size=9

[route]
layers=-4

[maxpool]
stride=1
size=13

[route]
layers=-1,-3,-5,-6

### End SPP ###

[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky


[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=1024
activation=leaky

[convolutional]
batch_normalize=1
filters=512
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=1024
activation=leaky

[convolutional]
size=1
stride=1
pad=1
filters=75
activation=linear


[yolo]
mask = 6,7,8  
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
classes=20
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
random=1


[route]
layers = -4

[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[upsample]
stride=2

[route]
layers = -1, 61



[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky

[convolutional]
batch_normalize=1
filters=256
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=512
activation=leaky

[convolutional]
size=1
stride=1
pad=1
filters=75
activation=linear


[yolo]
mask = 3,4,5
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
classes=20
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
random=1



[route]
layers = -4

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[upsample]
stride=2

[route]
layers = -1, 36

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=256
activation=leaky

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=256
activation=leaky

[convolutional]
batch_normalize=1
filters=128
size=1
stride=1
pad=1
activation=leaky

[convolutional]
batch_normalize=1
size=3
stride=1
pad=1
filters=256
activation=leaky

[convolutional]
size=1
stride=1
pad=1
filters=75
activation=linear


[yolo]
mask = 0,1,2
anchors = 10,13,  16,30,  33,23,  30,61,  62,45,  59,119,  116,90,  156,198,  373,326
classes=20
num=9
jitter=.3
ignore_thresh = .7
truth_thresh = 1
random=1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/193434.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

振南技术干货集:FFT 你知道?那数字相敏检波 DPSD 呢?(2)

注解目录 1 、DPSD 的基础知识 1.1 应用模型 1.2 原理推导 1.3 硬件 PSD &#xff08;相敏检波&#xff0c;就是从繁乱复杂的信号中将我们关心的信号检出来&#xff0c;同时对相位敏感。 数学原理&#xff0c;逃不掉的&#xff0c;硬着头皮看吧。&#xff09; 2 、DPSD …

数据结构(超详细讲解!!)第二十五节 线索二叉树

1.线索二叉树的定义和结构 问题的提出&#xff1a; 通过遍历二叉树可得到结点的一个线性序列&#xff0c;在线性序列中&#xff0c;很容易求得某个结点的直接前驱和后继。但是在二叉树上只能找到结点的左孩子、右孩子&#xff0c;结点的前驱和后继只有在遍历过程中才能得到…

python中的简单线性拟合

简单线性回归可以拟合线性关系的数据&#xff0c;一般使用一次函数或二次函数即可。 import numpy as np import matplotlib.pyplot as pltxnp.array([1,2,3,4,5,6,7,8,9,10]) ynp.array([2.5,4.5,4.8,5.5,6.0,7.0,7.8,8.0,9.0,10.0])#一次拟合函数 slope,interceptnp.polyfit…

TUP通信——与多个客户端同时通信

一&#xff0c;概括&#xff1a;可以通过多线程思想每加一个客户端由线程池中的主线程交给一个子线程管理 二&#xff0c;案例 &#xff08;1&#xff09;&#xff0c;线程池 &#xff08;2&#xff09;&#xff0c;服务端 &#xff08;3&#xff09;&#xff0c;客户端

从零开始学优惠券样式代码编写,让你的网站焕然一新!

样式1&#xff1a; 代码实例&#xff1a; <div class"box"><div class"itemBox"><div class"leftBox">全额抵扣</div><div class"rightBotton"><button>立即使用</button></div><…

FLV 文件格式分析

前言 flv 是 flash video 的缩写&#xff0c;是 Adobe Flash payler 支持的一种流媒体播放格式。flv 是一种层级格式&#xff0c;除了一个 flv header 外&#xff0c;剩下全是由 一个个 tag 组成。tag 是由 tag 头和 tag 数据组成。tag 类型分为音频、视频、脚本&#xff0c;一…

Kafka 如何保证消息消费的全局顺序性

哈喽大家好&#xff0c;我是咸鱼 今天我们继续来讲一讲 Kafka 当有消息被生产出来的时候&#xff0c;如果没有指定分区或者指定 key &#xff0c;那么消费会按照【轮询】的方式均匀地分配到所有可用分区中&#xff0c;但不一定按照分区顺序来分配 我们知道&#xff0c;在 Kaf…

redis笔记 -- 基础数据结构

redis笔记 基础的数据结构&#xff1a;string、list、hash、set、zset 容器型数据结构&#xff08;list、hash、set、zset&#xff09;通用规则 如果容器不存在&#xff0c;就创建一个&#xff0c;再进行操作如果容器里没有数据了&#xff0c;就立即删除&#xff0c;回收内存…

数字IC芯片验证流程及验证工具推荐?收藏专用

验证其实是一个“证伪”的过程&#xff0c;从流程到工具&#xff0c;验证工程师的终极目的都只有一个&#xff1a; 发现所有BUG&#xff0c;或者证明没有BUG&#xff0c;以保证芯片功能性能的正确性和可靠性。 验证环节对于一颗芯片的重要性也是不言而喻的&#xff1a; 从项…

python爬虫指南之请求模块urllib的详细教程

文章目录 前言一、urllib的子模块二、HttpResponse常用方法与属性获取信息urlli.parse的使用(一般用于处理带中文的url) 三、爬取baidu官网HTML源代码添加请求头信息&#xff08;重构user\_agent&#xff09; 四、扩展知识with open和open两者的区别关于Python技术储备一、Pyth…

python+gurobi求解线性规划、整数规划、0-1规划

文章目录 简单回顾线性规划LP整数规划IP0-1规划 简单回顾 线性规划是数学规划中的一类最简单规划问题&#xff0c;常见的线性规划是一个有约束的&#xff0c;变量范围为有理数的线性规划。如&#xff1a; 使用matlab的linprog函数即可求解简单的线性规划问题&#xff0c;可以参…

人力资源管理后台 === 角色管理

目录 1.组织架构-编辑部门-弹出层获取数据 2.组织架构-编辑部门-编辑表单校验 3.组织架构-编辑部门-确认取消 4.组织架构-删除部门 5.角色管理-搭建页面结构 6.角色管理-获取数据 7.角色管理-表格自定义结构 8.角色管理-分页功能 9.角色管理-新增功能弹层 10.角色管理…

springboot实现验证码功能

转载自 : www.javaman.cn 1、编写工具类生成4位随机数 该工具类主要生成从0-9&#xff0c;a-z&#xff0c;A-Z范围内产生的4位随机数 /*** 产生4位随机字符串*/public static String getCheckCode() {String base "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn…

【TinyALSA全解析(三)】tinyplay、tincap、pcm_open源码解析

tinyplay、tincap、pcm_open源码解析 一、本文的目的二、tinyplay.c源码分析三、tinycap.c源码分析四、pcm.c如何调度到Linux Kernel4.1 pcm_open解析4.1.1 pcm_open的主要流程4.1.2 流程说明4.1.3 调用方法 4.2 pcm_write解析 /*********************************************…

文章改写工具-改写神器

当代社会&#xff0c;信息爆炸&#xff0c;写作已成为人们生活与工作中不可或缺的一环。无论是学术论文、商业报告还是日常沟通&#xff0c;文字的准确表达和精彩呈现是至关重要的。然而&#xff0c;许多人在面对写作时&#xff0c;常常为语言表达、词汇选择而苦恼。为了解决这…

基于OpenCV+YOLOv5实现车辆跟踪与计数(附源码)

导 读 本文主要介绍基于OpenCVYOLOv5实现车辆跟踪与计数的应用&#xff0c;并给出源码。 资源下载 基础代码和视频下载地址&#xff1a; https://github.com/freedomwebtech/win11vehiclecount main.py代码:​​​​​​​ import cv2import torchimport numpy as npfrom tr…

Dockerfile讲解

Dockerfile 1. 构建过程解析2. Dockerfile常用保留字指令3. 案例3.1. 自定义镜像mycentosjava83.2. 虚悬镜像 4. Docker微服务实战 dockerfile是用来构建docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。 dockerfile定义了进程需要的一切东西&…

hdlbits系列verilog解答(Exams/m2014 q4e)-46

文章目录 一、问题描述二、verilog源码三、仿真结果 一、问题描述 实现以下电路&#xff1a; 二、verilog源码 module top_module (input in1,input in2,output out);assign out ~(in1 | in2);endmodule三、仿真结果 转载请注明出处&#xff01;

JOSEF 综合继电器 HJZZ-32/2 AC220V 合闸延时整定0.02-9.99S

系列型号&#xff1a; HJZZ-91分闸、合闸、电源监视综合装置&#xff1b; HJZZ-92/1分闸、合闸、电源监视综合装置&#xff1b; HJZZ-92/2分闸、合闸、电源监视综合装置&#xff1b; HJZZ-92/2A分闸、合闸、电源监视综合装置&#xff1b; HJZZ-92/3分闸、合闸、电源监视综…

Gitee上传代码教程

1. 本地安装git 官网下载太慢&#xff0c;我们也可以使用淘宝镜像下载&#xff1a;CNPM Binaries Mirror 安装成功以后电脑会有Git Bush标识&#xff0c;空白处右键也可查看。 2. 注册gitee账号&#xff08;略&#xff09; 3. 创建远程仓库 4. 上传代码 4.1 在项目文件目录…
最新文章