Yolov8代码详解,入门代码讲解

看不懂的代码可以复制进讯飞星火问问AI。以下是逐语句调试得出的执行顺序。

首先在根目录新建一个py文件,能够训练数据。

from ultralytics import YOLO
from ultralytics.utils import DEFAULT_CFG
from datetime import datetime

controller=1

def traindata():
    current_time = datetime.now()
    time_str = current_time.strftime("%Y-%m-%d_%H-%M-%S")  # 个人习惯, 用训练时间命名保存路径, 或者你自己自定义
    info=r'_only_yolov8'
    DEFAULT_CFG.save_dir = f"./Vincent/{time_str+info}"

    # 加载模型
    model = YOLO(r"E:\DeepLearning\ultralytics\ultralytics\cfg\models\v8\yolov8.yaml")  # 从头开始构建新模型
    # model = YOLO("yolov8n.pt")  # 加载预训练模型(推荐用于训练)

    # Use the model
    results = model.train(data=r"E:\DeepLearning\ultralytics\ultralytics\data\DVOR2024830_yolov8\data.yaml",epochs=200,batch=30,lr0=0.1)  # 训练模型
    metrics = model.val()  # 在验证集上评估模型性能
    # results = model("https://ultralytics.com/images/bus.jpg")  # 预测图像
    # success = model.export(format="onnx")  # 将模型导出为 ONNX 格式1

def predict():

    model = YOLO(r"E:\DeepLearning\ultralytics\runs\detect\train86\weights\best.pt")  # pretrained YOLOv8n model


    metrics = model.val(data=r"E:\DeepLearning\ultralytics\ultralytics\data\DVOR2024830_yolov8\data.yaml")


    vpath=r"E:\DeepLearning\ultralytics\ultralytics\data\ceshi/"
    # results = model([vpath+"vor (1).jpg",vpath+"vor (2).jpg",vpath+"vor (3).jpg",vpath+"VORBlack (1).jpg",vpath+"VORBlack (2).jpg",vpath+"VORBlack (3).jpg"], save=True,conf=0.1)  # return a list of Results objects


    # 视频路径
    file_path = r"E:\DeepLearning\ultralytics\ultralytics\data\video\vor3.mp4"
    file_path2 = r"E:\DeepLearning\ultralytics\ultralytics\data\video\vor.mp4"
    file_path3 = r"E:\DeepLearning\ultralytics\ultralytics\data\video\vor2.mp4"
    # 检测视频
    # results = model.predict(source=file_path3, device=0, show=False, save=True, conf=0.1)

    # # Process results list
    # for result in results:
    #     boxes = result.boxes  # Boxes object for bounding box outputs
    #     masks = result.masks  # Masks object for segmentation masks outputs
    #     keypoints = result.keypoints  # Keypoints object for pose outputs
    #     probs = result.probs  # Probs object for classification outputs
    #     obb = result.obb  # Oriented boxes object for OBB outputs
    #     result.show()  # display to screen
    #     result.save(filename="result.jpg")  # save to disk

def exportVincent():
    # Load the YOLOv8 model
    model = YOLO(r"E:\DeepLearning\ultralytics\runs\detect\train21\weights\best.pt")

    # Export the model to ONNX format
    model.export(format="onnx",dynamic=True)


if __name__ == '__main__':


    if(controller==1):

        traindata()
    elif controller==2:
        predict()
    else:
        print("null")
    #
    # exportVincent()

创建Yolov8模型结构

  • 根据vincentTrain.py中。代码model = YOLO("yolov8n.yaml")初始化时,调用ultralytics/models/yolo/model.py中的YOLO类初始化函数。

  • ultralytics/models/yolo/model.py 中def __init__(self, model="yolov8n.pt", task=None, verbose=False):由于传入参数为"yolov8n.yaml",执行super().__init__(model=model, task=task, verbose=verbose)
    super().init()函数调用基类ultralytics/engine/model.py中Model类的构造函数,这里的self参数传入的是子类也就是YOLO类。

  • ultralytics/engine/model.py中。在基类Model的构造函数中,识别到yaml文件,需要新创建模型,则执行代码self._new(model, task=task, verbose=verbose)。该代码会调用Model类中的_new()函数。

  • ultralytics/engine/model.py中。在_new函数中通过cfg_dict = yaml_model_load(cfg)读取模型结构yaml文件。

  • ultralytics/engine/model.py。读取结构配置后,通过self.task = task or guess_model_task(cfg_dict),确定任务类型。guess_model_task()函数用于猜测任务类型检测,分割,分类等。

  • ultralytics/engine/model.py。self.model = (model or self._smart_load("model"))(cfg_dict, verbose=verbose and RANK == -1)self._smart_load()只读属性表示调用YOLO类中的_smart_load创建只读属性,该属性变量在基类中并未实现。self._smart_load(“model”)获取到需要实例化的哪个类,然后传入参数(cfg_dict, verbose=verbose and RANK == -1)。

  • ultralytics/models/yolo/model.py。"detect": { "model": DetectionModel, "trainer": yolo.detect.DetectionTrainer, "validator": yolo.detect.DetectionValidator, "predictor": yolo.detect.DetectionPredictor, },根据代码self.model返回DetectionModel类,该类定义在ultralytics/nn/tasks.py中。

  • ultralytics/nn/tasks.py。调用DetectionModel的构造函数def init(self, cfg=“yolov8n.yaml”, ch=3, nc=None, verbose=True)

  • ultralytics/nn/tasks.py。self.model, self.save = parse_model(deepcopy(self.yaml), ch=ch, verbose=verbose)parse_model()函数对模型进行生成。deepcopy函数用于深度拷贝yaml模型配置信息。ch表示通道数。是后来加上去的参数。

开始遍历yaml中的backbone+head内容

  • ultralytics/nn/tasks.py。在parse_model(d, ch, verbose=True)函数中,for i, (f, n, m, args) in enumerate(d[“backbone”] + d[“head”])对yaml内容解析。enumerate函数使得d[“backbone”] + d[“head”]将backbone与head拼接到一个list中。i表示list的索引位置,(f, n, m, args)表示yaml中的参数。
  • ultralytics/cfg/models/v8/yolov8.yaml中。在yaml文件中标注如下
backbone:
  # [from, repeats, module, args]
  - [-1, 1, Conv, [64, 3, 2]] # 0-P1/2

f表示from -1,n表示repeats 1,m表示模块module Conv,args代表参数[64, 3, 2]

  • ultralytics/nn/tasks.py的parse_model()。m = getattr(torch.nn, m[3:]) if "nn." in m else globals()[m]。getattr函数用于获取它某个属性的值。该语句判断m中是否包含nn.字段,1)如果m为nn.Upsample,则m=torch.nn中Upsample对应的值。2)如果m为Conv,则globals()找到当前位置的全局变量(返回的可以是类型,也可以是具体的变量,或者函数,此处返回类型),返回名为Conv对应的值。class Conv(nn.Module)定义在ultralytics/nn/modules/conv.py
# ultralytics/nn/modules/conv.py
class Conv(nn.Module):
    """Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation)."""

    default_act = nn.SiLU()  # default activation

    def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True):
        """Initialize Conv layer with given arguments including activation."""
        super().__init__()
        self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False)
        self.bn = nn.BatchNorm2d(c2)
        self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()

    def forward(self, x):
        """Apply convolution, batch normalization and activation to input tensor."""
        return self.act(self.bn(self.conv(x)))
  • ultralytics/nn/tasks.py的parse_model()。c2 = make_divisible(min(c2, max_channels) * width, 8)。make_divisible函数将yaml模型结构中arg参数中第一个表示通道数的数据处理,确保是8的倍数。若不是,则c2设置为比参数1大的8倍数。
  • ultralytics/nn/tasks.py的parse_model()。m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)m存放了当前需要创建的模块类型,当前是Conv,m(*args)相当于Conv(*args),创建Conv类实例,调用ultralytics/nn/modules/conv.py中的构造函数,配置该类。并放入nn.Sequential类型的m_中。
  • ultralytics/nn/tasks.py的parse_model()。m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)。m存放了当前需要创建的模块类型,当前是Conv,m(*args)相当于Conv(*args),创建Conv类实例,调用ultralytics/nn/modules/conv.py中的构造函数,配置该类。并放入nn.Sequential类型的m_中。
  • ultralytics/nn/tasks.py的parse_model()。m.np = sum(x.numel() for x in m_.parameters())。m_表示整个神经网络模型,x作为模型中所有参数的迭代项,使用numel()函数计算每个参数的元素数量,最后求和得总数。
  • ultralytics/nn/tasks.py的parse_model()。save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) 。该语句存储yaml模型结构文件中,f参数不为-1的参数,最终存储[4, 6, 9, 12, 15, 18, 21]。layers.append(m_)将每个模块存放如layers中。

第三次循环出现m出现C2f模块

  • ultralytics/nn/tasks.py的parse_model()。
if m in {BottleneckCSP, C1, C2, C2f, C2fAttn, C3, C3TR, C3Ghost, C3x, RepC3}:
    args.insert(2, n)  # number of repeats
    n = 1
m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)

识别到模型为C2f后,在arg中插入参数n。代用m(*args)创建C2f模块。

  • ultralytics/nn/modules/block.py。class C2f(nn.Module),创建C2f模块,使用构造函数def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5)
# ultralytics/nn/modules/block.py
class C2f(nn.Module):
    """Faster Implementation of CSP Bottleneck with 2 convolutions."""

    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):
        """Initialize CSP bottleneck layer with two convolutions with arguments ch_in, ch_out, number, shortcut, groups,
        expansion.
        """
        super().__init__()
        self.c = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))

    def forward(self, x):
        """Forward pass through C2f layer."""
        y = list(self.cv1(x).chunk(2, 1))
        y.extend(m(y[-1]) for m in self.m)
        return self.cv2(torch.cat(y, 1))
  • ultralytics/nn/modules/block.py。self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))。构造函数中实例化Bottleneck类,该类同样定义在ultralytics/nn/modules/block.py中。
# ultralytics/nn/modules/block.py
class Bottleneck(nn.Module):
    """Standard bottleneck."""

    def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5):
        """Initializes a bottleneck module with given input/output channels, shortcut option, group, kernels, and
        expansion.
        """
        super().__init__()
        c_ = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, c_, k[0], 1)
        self.cv2 = Conv(c_, c2, k[1], 1, g=g)
        self.add = shortcut and c1 == c2

    def forward(self, x):
        """'forward()' applies the YOLO FPN to input data."""
        return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x))
  • ultralytics/nn/modules/block.py。在Bottleneck中关注构造函数__init__(),以及forward()函数。

  • 模型创建迭代到SPPF模块时,ultralytics/nn/tasks.py的parse_model()。ultralytics/nn/modules/block.py中的class SPPF(nn.Module)。定义了该类的结构。关注构造函数__init__(),以及forward()函数即可。

  • 接下来创建模型中head内容。nn.Upsample创建方式与前面相似。接下来详细说Concat。

  • ultralytics/cfg/models/v8/yolov8.yaml。在模型结构配置文件head中 - [[-1, 6], 1, Concat, [1]] 。其中[-1, 6]的-1表示前面一层,6表示第六层,在ultralytics/nn/tasks.py的parse_model()中layers变量按顺序存放了之前创建的每个层级模块。在yolov8的模型结构图中已经标识了层级的编号。这里的concat使用了6层的C2f输出,也使用了-1层也就是第10层的Upsample输出。

  • 创建concat时,concat类定义在ultralytics/nn/modules/conv.py中。在concat类中,无论是构造函数,还是前向传播函数,均没有涉及到使用参数[-1,6],只是返回了一个torch.cat(x, self.d)的拼接操作。

detect检测层
  • ultralytics/nn/tasks.py的parse_model()。最后,创建detect检测层时候。args[j] = locals()[a] if a in locals() else ast.literal_eval(a)。a表示-[[15,18,21],1,Detect,[nc]]中的nc,为了映射为前面的nc值,使用了locals()查询当前局部变量有哪些,若存在有名字为nc的变量,则将变量的值赋值给args[j]。所谓检测的种类数nc被放入了args中。
#ultralytics/nn/tasks.py的parse_model()
        elif m in {Detect, WorldDetect, Segment, Pose, OBB, ImagePoolingAttn}:
            args.append([ch[x] for x in f])
            if m is Segment:
                args[2] = make_divisible(min(args[2], max_channels) * width, 8)
  • f表示yaml中的from参数,这里是[15,18,21],分别指第15层,18层,21层的输出。将这些输出的通道数量存放入args中。

  • ultralytics/nn/tasks.py的parse_model()。使用m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args)。其中m(*args)语句创建detect模块。调用Detect类。

  • Detect类构造函数如下

#ultralytics/nn/modules/head.py的class Detect(nn.Module)
#class Detect(nn.Module):
    def __init__(self, nc=80, ch=()):
        """Initializes the YOLOv8 detection layer with specified number of classes and channels."""
        super().__init__()
        self.nc = nc  # number of classes
        self.nl = len(ch)  # number of detection layers
        self.reg_max = 16  # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
        self.no = nc + self.reg_max * 4  # number of outputs per anchor
        self.stride = torch.zeros(self.nl)  # strides computed during build
        c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], min(self.nc, 100))  # channels
        self.cv2 = nn.ModuleList(
            nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch
        )
        self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)
        self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()

nc: 整数,表示图像分类问题中的类别数;
nl: 整数,表示检测模型中使用的检测层数;
reg_max: 整数,表示每个锚点输出的通道数;
no: 整数,表示每个锚点的输出数量,其中包括类别数和位置信息;
stride: 一个形状为(nl,)的张量,表示每个检测层的步长(stride);
cv2: 一个 nn.ModuleList 对象,包含多个卷积层,用于预测每个锚点的位置信息;
cv3: 一个 nn.ModuleList 对象,包含多个卷积层,用于预测每个锚点的类别信息;
dfl: DFL(Distribution Focal Loss)
shape属性表示模型期望的输入形状,如果模型只接受固定形状的输入,则 self.shape 存储该形状。
在这个Detect类构造函数中,定义了c2,c3。分别对应yolov8模型结构图中的Detect模块,分别用于检测框损失计算,以及分类损失计算。
self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()代码将通过DFL类创建dfl模块。dfl相关论文(https://ieeexplore.ieee.org/document/9792391)

  • ultralytics/nn/modules/block.py中的class DFL(nn.Module)。
class DFL(nn.Module):
    """
    Integral module of Distribution Focal Loss (DFL).

    Proposed in Generalized Focal Loss https://ieeexplore.ieee.org/document/9792391
    """

    def __init__(self, c1=16):
        """Initialize a convolutional layer with a given number of input channels."""
        super().__init__()
        self.conv = nn.Conv2d(c1, 1, 1, bias=False).requires_grad_(False)
        x = torch.arange(c1, dtype=torch.float)
        self.conv.weight.data[:] = nn.Parameter(x.view(1, c1, 1, 1))
        self.c1 = c1

    def forward(self, x):
        """Applies a transformer layer on input tensor 'x' and returns a tensor."""
        b, _, a = x.shape  # batch, channels, anchors
        return self.conv(x.view(b, 4, self.c1, a).transpose(2, 1).softmax(1)).view(b, 4, a)
        # return self.conv(x.view(b, self.c1, 4, a).softmax(1)).view(b, 4, a)

到此,模型结构的构建完成,最终回到了ultralytics/nn/tasks.py中的class DetectionModel(BaseModel)类构造函数中。

  • ultralytics/nn/tasks.py的class DetectionModel(BaseModel)。
# Build strides
        m = self.model[-1]  # Detect()
        if isinstance(m, Detect):  # includes all Detect subclasses like Segment, Pose, OBB, WorldDetect
            s = 256  # 2x min stride
            m.inplace = self.inplace

            def _forward(x):
                """Performs a forward pass through the model, handling different Detect subclass types accordingly."""
                return self.forward(x)[0] if isinstance(m, (Segment, Pose, OBB)) else self.forward(x)

            m.stride = torch.tensor([s / x.shape[-2] for x in _forward(torch.zeros(1, ch, s, s))])  # forward
            self.stride = m.stride
            m.bias_init()  # only run once
        else:
            self.stride = torch.Tensor([32])  # default stride for i.e. RTDETR
  • ultralytics/nn/tasks.py的class DetectionModel(BaseModel)。self.stride = m.stride。m.stride存储了三个输出特征图的缩放比例,yolov8接收输入的图片大小640640,输出特征图大小分别为:8080,4040,2020。缩放比例分别为8,16,32。m.stride存储内容为[8,16,32]。

  • 到此,后面就进入到资源加载并训练的语句中。model.train(data=“coco8.yaml”, epochs=3) # 训练模型。调用ultralytics/engine/model.py中的class Model(nn.Module)。def train(self,trainer=None, **kwargs,)。

  • ultralytics/data/utils.py。函数def check_det_dataset(dataset, autodownload=True)中的data[“path”]存储了数据集路径。

  • ultralytics/engine/model.py中train函数。self.trainer = (trainer or self._smart_load(“trainer”))(overrides=args, _callbacks=self.callbacks)加载训练器。

  • self.trainer.model = self.trainer.get_model(weights=self.model if self.ckpt else None, cfg=self.model.yaml)。self指的是当前这个Model类实例对象,_smart_load()函数将"trainer"方式,选择一个trainer类型的训练器。trainer方式则获得<class ‘ultralytics.models.yolo.detect.train.DetectionTrainer’>。所有的类型存放在self.task_map中,通过任务类型,trainer条件选取。选取后,通过(overrides=args, _callbacks=self.callbacks)参数进行初始化。

  • ultralytics/models/yolo/detect/train.py。class DetectionTrainer(BaseTrainer):继承自BaseTrainer,且没有定义构造函数,构造函数将调用BaseTrainer中的构造函数。

  • class BaseTrainer:中的构造函数self.trainset, self.testset = self.get_dataset()获取数据集路径。用于初始化DetectionTrainer类。

  • ultralytics/engine/model.py中train函数。self.trainer.model = self.trainer.get_model(weights=self.model if self.ckpt else None, cfg=self.model.yaml)。get_model()函数来自DetectionTrainer中。该函数重新创建了一个DetectionModel,并返回该实例。

  • 现在回过头来梳理一下。首先我们自己用YOLO类实例化了一个对象,用来创建网络的结构。该YOLO类中存放了一个model属性,存放了具体的DetectionModel,也就是根据配置文件创建的网络模型结构。而刚刚的语句,将在该实例中的trainer属性中,继续创建一个相同的模型结构。然后使用self.model = self.trainer.model,替换之前创建的网络结构。(不明白有什么意义,可能是为了兼容.pt文件或者继续训练的原因)。

  • ultralytics/engine/model.py中train函数。self.trainer.train()。这里的self.trainer是DetectionTrainer,该类中没有train()函数,所以调用基类BaseTrainer中的train()函数,正式开始训练

开始训练

未完待续。。。。。

03-19
### IEEE 802.1Q VLAN Tagging Protocol Standard IEEE 802.1Q 是支持虚拟局域网(VLAN)的标准协议之一,通常被称为 Dot1q。该标准定义了一种用于以太网帧的 VLAN 标记系统以及交换机和桥接器处理这些标记帧的操作流程[^2]。 #### 协议结构概述 IEEE 802.1Q 的核心功能在于通过在以太网数据帧中插入特定字段来实现 VLAN 标签的功能。这种标签使得网络设备能够识别哪些流量属于哪个 VLAN,并据此执行转发决策。具体来说: - **Tag Header**: 在原始以太网帧头部增加了一个额外的 4 字节字段作为 VLAN 标签头。这四个字节包含了以下部分: - **Priority Code Point (PCP)**: 使用 3 比特表示优先级级别,范围从 0 到 7,主要用于 QoS 控制。 - **Canonical Format Indicator (CFI)**: 这是一个单比特位,在传统以太网环境中设置为零。 - **VLAN Identifier (VID)**: 使用 12 比特标识具体的 VLAN ID,理论上可以支持多达 4096 个不同的 VLAN(编号从 0 至 4095),其中某些特殊值保留给内部用途或管理目的。 #### 数据包处理机制 当一个带有 VLAN tag 的数据包进入支持 IEEE 802.1Q 的交换机时,它会依据此标签决定如何路由或者过滤该数据流。如果目标端口不属于同一 VLAN,则不会传输至其他无关联的物理接口上;反之亦然——只有相同 VLAN 成员之间才允许互相通信除非经过路由器跨网段访问[^1]。 此外,为了简化管理和配置过程并增强互操作性,还引入了一些辅助性的子协议和服务组件比如 GARP(通用属性注册协议)。GARP 可帮助分发有关 VLAN 成员资格的信息到各个连接节点以便动态调整其行为模式而无需频繁手动干预[^3]。 以下是创建带 VLAN TAG 的 Python 示例代码片段展示如何模拟构建这样的 Ethernet Frame: ```python from scapy.all import Ether, Dot1Q, IP, sendp def create_vlan_packet(src_mac="00:aa:bb:cc:dd:ee", dst_mac="ff:ff:ff:ff:ff:ff", vlan_id=100, src_ip="192.168.1.1", dst_ip="192.168.1.2"): ether = Ether(src=src_mac, dst=dst_mac) dot1q = Dot1Q(vlan=vlan_id) ip_layer = IP(src=src_ip, dst=dst_ip) packet = ether / dot1q / ip_layer return packet packet = create_vlan_packet() sendp(packet, iface="eth0") # Replace 'eth0' with your network interface name. ``` 上述脚本利用 Scapy 库生成包含指定源地址、目的地址及所属 VLAN 编号的数据报文并通过选定的网卡发送出去测试实际效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值