【改进YOLOv8】可变车道指示牌识别系统:融合移动端网络架构 RepViT改进改进YOLOv8

1.研究背景与意义

项目参考AAAI Association for the Advancement of Artificial Intelligence

随着计算机视觉和深度学习的快速发展,目标检测成为了计算机视觉领域的一个重要研究方向。目标检测是指在图像或视频中准确地定位和识别出特定目标的任务。在过去的几年中,许多基于深度学习的目标检测算法被提出,其中YOLO(You Only Look Once)系列算法因其高效和准确的特点而备受关注。

YOLOv8是YOLO系列算法的最新版本,它在YOLOv3的基础上进行了一系列的改进。YOLOv8采用了更深的网络结构和更多的卷积层,以提高目标检测的准确性和鲁棒性。此外,YOLOv8还引入了一些新的技术,如多尺度训练和测试、注意力机制等,以进一步提升算法的性能。

然而,尽管YOLOv8在目标检测方面取得了显著的成果,但仍存在一些问题和挑战。首先,YOLOv8在处理小目标时的性能较差,容易出现漏检和误检的情况。其次,YOLOv8在处理密集目标时的效果也不理想,容易出现目标重叠和遮挡的情况。此外,YOLOv8的速度相对较慢,对于实时应用来说还有待改进。

因此,改进YOLOv8的研究具有重要的理论和实际意义。首先,改进YOLOv8可以提高目标检测的准确性和鲁棒性,使其在更广泛的应用场景中得到有效的应用。其次,改进YOLOv8可以提高算法的处理速度,使其更适用于实时应用,如自动驾驶、智能监控等领域。此外,改进YOLOv8还可以为目标检测算法的研究提供新的思路和方法,推动计算机视觉领域的发展。

  1. 小目标检测:针对YOLOv8在处理小目标时的性能问题,可以通过引入更细粒度的特征表示、改进的损失函数等方法来提高算法的检测精度。此外,可以考虑引入目标上下文信息、多尺度特征融合等技术来增强算法对小目标的感知能力。

  2. 密集目标检测:针对YOLOv8在处理密集目标时的问题,可以考虑引入更细粒度的特征表示、改进的非极大值抑制算法等方法来提高算法的检测效果。此外,可以探索目标分割和目标关联等技术来解决目标重叠和遮挡的问题。

  3. 实时目标检测:针对YOLOv8的速度较慢的问题,可以通过网络剪枝、量化、模型压缩等方法来减少模型的计算量和参数量,从而提高算法的运行速度。此外,可以考虑使用硬件加速器、并行计算等技术来加速算法的推理过程。

  4. 注意力机制:YOLOv8引入了注意力机制来提高目标检测的性能,但其注意力机制仍有改进的空间。可以探索更有效的注意力机制设计,如自适应注意力、多尺度注意力等,以提高算法的感知能力和准确性。

改进YOLOv8的研究对于提高目标检测的准确性、鲁棒性和实时性具有重要的意义。通过改进小目标检测、密集目标检测、实时目标检测和注意力机制等方面,可以进一步提升YOLOv8的性能。此外,改进YOLOv8还可以为目标检测算法的研究提供新的思路和方法,推动计算机视觉领域的发展。因此,改进YOLOv8的研究具有重要的理论和实际意义。

2.图片演示



3.视频演示

可变车道指示牌识别系统:融合移动端网络架构 RepViT改进YOLOv8_哔哩哔哩_bilibili

4.数据集的采集&标注和整理

图片的收集

首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集KBDatasets。

labelImg是一个图形化的图像注释工具,支持VOC和YOLO格式。以下是使用labelImg将图片标注为VOC格式的步骤:

(1)下载并安装labelImg。
(2)打开labelImg并选择“Open Dir”来选择你的图片目录。
(3)为你的目标对象设置标签名称。
(4)在图片上绘制矩形框,选择对应的标签。
(5)保存标注信息,这将在图片目录下生成一个与图片同名的XML文件。
(6)重复此过程,直到所有的图片都标注完毕。

由于YOLO使用的是txt格式的标注,我们需要将VOC格式转换为YOLO格式。可以使用各种转换工具或脚本来实现。

下面是一个简单的方法是使用Python脚本,该脚本读取XML文件,然后将其转换为YOLO所需的txt格式。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import xml.etree.ElementTree as ET
import os

classes = []  # 初始化为空列表

CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))

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):
    in_file = open('./label_xml\%s.xml' % (image_id), encoding='UTF-8')
    out_file = open('./label_txt\%s.txt' % (image_id), 'w')  # 生成txt格式文件
    tree = ET.parse(in_file)
    root = tree.getroot()
    size = root.find('size')
    w = int(size.find('width').text)
    h = int(size.find('height').text)

    for obj in root.iter('object'):
        cls = obj.find('name').text
        if cls not in classes:
            classes.append(cls)  # 如果类别不存在,添加到classes列表中
        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(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')

xml_path = os.path.join(CURRENT_DIR, './label_xml/')

# xml list
img_xmls = os.listdir(xml_path)
for img_xml in img_xmls:
    label_name = img_xml.split('.')[0]
    print(label_name)
    convert_annotation(label_name)

print("Classes:")  # 打印最终的classes列表
print(classes)  # 打印最终的classes列表

整理数据文件夹结构

我们需要将数据集整理为以下结构:

-----data
   |-----train
   |   |-----images
   |   |-----labels
   |
   |-----valid
   |   |-----images
   |   |-----labels
   |
   |-----test
       |-----images
       |-----labels

确保以下几点:

所有的训练图片都位于data/train/images目录下,相应的标注文件位于data/train/labels目录下。
所有的验证图片都位于data/valid/images目录下,相应的标注文件位于data/valid/labels目录下。
所有的测试图片都位于data/test/images目录下,相应的标注文件位于data/test/labels目录下。
这样的结构使得数据的管理和模型的训练、验证和测试变得非常方便。

模型训练
 Epoch   gpu_mem       box       obj       cls    labels  img_size
 1/200     20.8G   0.01576   0.01955  0.007536        22      1280: 100%|██████████| 849/849 [14:42<00:00,  1.04s/it]
           Class     Images     Labels          P          R     [email protected] [email protected]:.95: 100%|██████████| 213/213 [01:14<00:00,  2.87it/s]
             all       3395      17314      0.994      0.957      0.0957      0.0843

 Epoch   gpu_mem       box       obj       cls    labels  img_size
 2/200     20.8G   0.01578   0.01923  0.007006        22      1280: 100%|██████████| 849/849 [14:44<00:00,  1.04s/it]
           Class     Images     Labels          P          R     [email protected] [email protected]:.95: 100%|██████████| 213/213 [01:12<00:00,  2.95it/s]
             all       3395      17314      0.996      0.956      0.0957      0.0845

 Epoch   gpu_mem       box       obj       cls    labels  img_size
 3/200     20.8G   0.01561    0.0191  0.006895        27      1280: 100%|██████████| 849/849 [10:56<00:00,  1.29it/s]
           Class     Images     Labels          P          R     [email protected] [email protected]:.95: 100%|███████   | 187/213 [00:52<00:00,  4.04it/s]
             all       3395      17314      0.996      0.957      0.0957      0.0845

5.核心代码讲解

5.1 train.py
from copy import copy
import numpy as np
from ultralytics.data import build_dataloader, build_yolo_dataset
from ultralytics.engine.trainer import BaseTrainer
from ultralytics.models import yolo
from ultralytics.nn.tasks import DetectionModel
from ultralytics.utils import LOGGER, RANK
from ultralytics.utils.torch_utils import de_parallel, torch_distributed_zero_first

class DetectionTrainer(BaseTrainer):
    def build_dataset(self, img_path, mode='train', batch=None):
        gs = max(int(de_parallel(self.model).stride.max() if self.model else 0), 32)
        return build_yolo_dataset(self.args, img_path, batch, self.data, mode=mode, rect=mode == 'val', stride=gs)

    def get_dataloader(self, dataset_path, batch_size=16, rank=0, mode='train'):
        assert mode in ['train', 'val']
        with torch_distributed_zero_first(rank):
            dataset = self.build_dataset(dataset_path, mode, batch_size)
        shuffle = mode == 'train'
        if getattr(dataset, 'rect', False) and shuffle:
            LOGGER.warning("WARNING ⚠️ 'rect=True' is incompatible with DataLoader shuffle, setting shuffle=False")
            shuffle = False
        workers = 0
        return build_dataloader(dataset, batch_size, workers, shuffle, rank)

    def preprocess_batch(self, batch):
        batch['img'] = batch['img'].to(self.device, non_blocking=True).float() / 255
        return batch

    def set_model_attributes(self):
        self.model.nc = self.data['nc']
        self.model.names = self.data['names']
        self.model.args = self.args

    def get_model(self, cfg=None, weights=None, verbose=True):
        model = DetectionModel(cfg, nc=self.data['nc'], verbose=verbose and RANK == -1)
        if weights:
            model.load(weights)
        return model

    def get_validator(self):
        self.loss_names = 'box_loss', 'cls_loss', 'dfl_loss'
        return yolo.detect.DetectionValidator(self.test_loader, save_dir=self.save_dir, args=copy(self.args))

    def label_loss_items(self, loss_items=None, prefix='train'):
        keys = [f'{prefix}/{x}' for x in self.loss_names]
        if loss_items is not None:
            loss_items = [round(float(x), 5) for x in loss_items]
            return dict(zip(keys, loss_items))
        else:
            return keys

    def progress_string(self):
        return ('\n' + '%11s' *
                (4 + len(self.loss_names))) % ('Epoch', 'GPU_mem', *self.loss_names, 'Instances', 'Size')

    def plot_training_samples(self, batch, ni):
        plot_images(images=batch['img'],
                    batch_idx=batch['batch_idx'],
                    cls=batch['cls'].squeeze(-1),
                    bboxes=batch['bboxes'],
                    paths=batch['im_file'],
                    fname=self.save_dir / f'train_batch{ni}.jpg',
                    on_plot=self.on_plot)

    def plot_metrics(self):
        plot_results(file=self.csv, on_plot=self.on_plot)

    def plot_training_labels(self):
        boxes = np.concatenate([lb['bboxes'] for lb in self.train_loader.dataset.labels], 0)
        cls = np.concatenate([lb['cls'] for lb in self.train_loader.dataset.labels], 0)
        plot_labels(boxes, cls.squeeze(), names=self.data['names'], save_dir=self.save_dir, on_plot=self.on_plot)

该程序文件是一个用于训练基于检测模型的程序。它使用Ultralytics YOLO库进行训练。

该文件定义了一个名为DetectionTrainer的类,它是BaseTrainer类的子类。DetectionTrainer类有以下主要方法和功能:

  1. build_dataset方法:用于构建YOLO数据集。
  2. get_dataloader方法:用于构建和返回数据加载器。
  3. preprocess_batch方法:用于对图像批次进行预处理,包括缩放和转换为浮点数。
  4. set_model_attributes方法:用于设置模型的属性,包括类别数量、类别名称和超参数。
  5. get_model方法:用于返回一个YOLO检测模型。
  6. get_validator方法:用于返回一个用于YOLO模型验证的DetectionValidator对象。
  7. label_loss_items方法:用于返回一个带有标记的训练损失项的损失字典。
  8. progress_string方法:用于返回训练进度的格式化字符串,包括当前的epoch、GPU内存、损失、实例数量和数据集大小。
  9. plot_training_samples方法:用于绘制训练样本及其注释。
  10. plot_metrics方法:用于绘制来自CSV文件的指标。
  11. plot_training_labels方法:用于创建一个带有标签的YOLO模型的训练图。

在主程序中,首先定义了一个参数字典args,包括模型文件路径、数据文件路径和训练轮数。然后创建了一个DetectionTrainer对象trainer,并调用其train方法进行训练。

5.2 backbone\revcol.py


class RevCol(nn.Module):
    def __init__(self, kernel='C2f', channels=[32, 64, 96, 128], layers=[2, 3, 6, 3], num_subnet=5, save_memory=True) -> None:
        super().__init__()
        self.num_subnet = num_subnet
        self.channels = channels
        self.layers = layers

        self.stem = Conv(3, channels[0], k=4, s=4, p=0)

        for i in range(num_subnet):
            first_col = True if i == 0 else False
            self.add_module(f'subnet{str(i)}', SubNet(channels, layers, kernel, first_col, save_memory=save_memory))
        
        self.channel = [i.size(1) for i in self.forward(torch.randn(1, 3, 640, 640))]

    def forward(self, x):
        c0, c1, c2, c3 = 0, 0, 0, 0
        x = self.stem(x)        
        for i in range(self.num_subnet):
            c0, c1, c2, c3 = getattr(self, f'subnet{str(i)}')(x, c0, c1, c2, c3)       
        return [c0, c1, c2, c3]

该程序文件名为backbone\revcol.py,是一个用于深度学习的神经网络模型的实现。该文件包含了多个模块和函数。

该文件的主要内容如下:

  1. 导入了torch、torch.nn、torch.distributed等模块,以及其他自定义的模块和函数。

  2. 定义了一个名为RevCol的类,继承自nn.Module。RevCol类是整个神经网络模型的主体部分。

  3. RevCol类的构造函数中初始化了一些参数,如卷积核的类型、通道数、层数等。

  4. RevCol类中定义了一个名为forward的方法,用于前向传播计算。

  5. RevCol类中还定义了其他一些辅助方法和辅助类,用于实现模型的具体功能。

总体来说,该程序文件实现了一个名为RevCol的神经网络模型,用于进行深度学习任务。该模型包含了多个子网络(SubNet),每个子网络由多个层(Level)组成,每个层由多个模块(Conv)组成。模型的输入是一个四维张量,输出是一个列表,包含了多个四维张量。

5.3 backbone\SwinTransformer.py


class Mlp(nn.Module):
    """ Multilayer perceptron."""

    def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
        super().__init__()
        out_features = out_features or in_features
        hidden_features = hidden_features or in_features
        self.fc1 = nn.Linear(in_features, hidden_features)
        self.act = act_layer()
        self.fc2 = nn.Linear(hidden_features, out_features)
        self.drop = nn.Dropout(drop)

    def forward(self, x):
        x = self.fc1(x)
        x = self.act(x)
        x = self.drop(x)
        x = self.fc2(x)
        x = self.drop(x)
        return x


def window_partition(x, window_size):
    """
    Args:
        x: (B, H, W, C)
        window_size (int): window size

    Returns:
        windows: (num_windows*B, window_size, window_size, C)
    """
    B, H, W, C = x.shape
    x = x.view(B, H // window_size, window_size, W // window_size, window_size, C)
    windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C)
    return windows


def window_reverse(windows, window_size, H, W):
    """
    Args:
        windows: (num_windows*B, window_size, window_size, C)
        window_size (int): Window size
        H (int): Height of image
        W (int): Width of image

    Returns:
        x: (B, H, W, C)
    """
    B = int(windows.shape[0] / (H * W / window_size / window_size))
    x = windows.view(B, H // window_size, W // window_size, window_size, window_size, -1)
    x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1)
    return x


class WindowAttention(nn.Module):
    """ Window based multi-head self attention (W-MSA) module with relative position bias.
    It supports both of shifted and non-shifted window.

    Args:
        dim (int): Number of input channels.
        window_size (tuple[int]): The height and width of the window.
        num_heads (int): Number of attention heads.
        qkv_bias (bool, optional):  If True, add a learnable bias to query, key, value. Default: True
        qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set
        attn_drop (float, optional): Dropout ratio of attention weight. Default: 0.0
        proj_drop (float, optional): Dropout ratio of output. Default: 0.0
    """

    def __init__(self, dim, window_size, num_heads, qkv_bias=True, qk_scale=None, attn_drop=0., proj_drop=0.):

        super().__init__()
        self.dim = dim
        self.window_size = window_size  # Wh, Ww
        self.num_heads = num_heads
        head_dim = dim // num_heads
        self.scale = qk

该程序文件是Swin Transformer模型的一个实现。Swin Transformer是一种基于窗口的多头自注意力模块,用于图像分类任务。该文件包含了Swin Transformer模型的各个组件,包括多层感知机(Mlp)、窗口分割和合并(window_partition和window_reverse)、窗口注意力(WindowAttention)、Swin Transformer块(SwinTransformerBlock)、补丁合并(PatchMerging)和基本层(BasicLayer)。

其中,Mlp是一个多层感知机模块,用于对输入进行线性变换和激活函数处理。window_partition和window_reverse函数用于将输入特征图分割成窗口,并将窗口合并成特征图。WindowAttention是窗口注意力模块,用于计算窗口内的自注意力。SwinTransformerBlock是Swin Transformer的基本块,包含了窗口注意力、残差连接和多层感知机。PatchMerging是补丁合并层,用于将特征图的分辨率减半。BasicLayer是Swin Transformer的一个阶段,包含多个SwinTransformerBlock。

该程序文件实现了Swin Transformer模型的各个组件,并可以用于图像分类任务。

5.4 backbone\VanillaNet.py
import torch
import torch.nn as nn
from timm.layers import weight_init

class activation(nn.ReLU):
    def __init__(self, dim, act_num=3, deploy=False):
        super(activation, self).__init__()
        self.deploy = deploy
        self.weight = torch.nn.Parameter(torch.randn(dim, 1, act_num*2 + 1, act_num*2 + 1))
        self.bias = None
        self.bn = nn.BatchNorm2d(dim, eps=1e-6)
        self.dim = dim
        self.act_num = act_num
        weight_init.trunc_normal_(self.weight, std=.02)

    def forward(self, x):
        if self.deploy:
            return torch.nn.functional.conv2d(
                super(activation, self).forward(x), 
                self.weight, self.bias, padding=(self.act_num*2 + 1)//2, groups=self.dim)
        else:
            return self.bn(torch.nn.functional.conv2d(
                super(activation, self).forward(x),
                self.weight, padding=self.act_num

该程序文件名为backbone\VanillaNet.py,是一个使用PyTorch实现的神经网络模型。该模型是VanillaNet,用于图像分类任务。该模型具有多个版本,分别命名为vanillanet_5、vanillanet_6、vanillanet_7等等。每个版本的模型结构稍有不同,但都是基于相同的基本模块构建的。

该程序文件中定义了一些辅助函数和类,包括activation类、Block类和VanillaNet类。activation类是一个激活函数类,继承自nn.ReLU,用于实现自定义的激活函数。Block类是一个基本模块类,包含了卷积、批归一化、池化和激活函数等操作。VanillaNet类是整个网络模型的

5.5 extra_modules\rep_block.py
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

class DiverseBranchBlock(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size,
                 stride=1, padding=None, dilation=1, groups=1,
                 internal_channels_1x1_3x3=None,
                 deploy=False, single_init=False):
        super(DiverseBranchBlock, self).__init__()
        self.deploy = deploy

        self.nonlinear = Conv.default_act

        self.kernel_size = kernel_size
        self.out_channels = out_channels
        self.groups = groups
        
        if padding is None:
            padding = autopad(kernel_size, padding, dilation)
        assert padding == kernel_size // 2

        if deploy:
            self.dbb_reparam = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride,
                                      padding=padding, dilation=dilation, groups=groups, bias=True)

        else:

            self.dbb_origin = conv_bn(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=padding, dilation=dilation, groups=groups)

            self.dbb_avg = nn.Sequential()
            if groups < out_channels:
                self.dbb_avg.add_module('conv',
                                        nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1,
                                                  stride=1, padding=0, groups=groups, bias=False))
                self.dbb_avg.add_module('bn', BNAndPadLayer(pad_pixels=padding, num_features=out_channels))
                self.dbb_avg.add_module('avg', nn.AvgPool2d(kernel_size=kernel_size, stride=stride, padding=0))
                self.dbb_1x1 = conv_bn(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=stride,
                                       padding=0, groups=groups)
            else:
                self.dbb_avg.add_module('avg', nn.AvgPool2d(kernel_size=kernel_size, stride=stride, padding=padding))

            self.dbb_avg.add_module('avgbn', nn.BatchNorm2d(out_channels))


            if internal_channels_1x1_3x3 is None:
                internal_channels_1x1_3x3 = in_channels if groups < out_channels else 2 * in_channels   # For mobilenet, it is better to have 2X internal channels

            self.dbb_1x1_kxk = nn.Sequential()
            if internal_channels_1x1_3x3 == in_channels:
                self.dbb_1x1_kxk.add_module('idconv1', IdentityBasedConv1x1(channels=in_channels, groups=groups))
            else:
                self.dbb_1x1_kxk.add_module('conv1', nn.Conv2d(in_channels=in_channels, out_channels=internal_channels_1x1_3x3,
                                                            kernel_size=1, stride=1, padding=0, groups=groups, bias=False))
            self.dbb_1x1_kxk.add_module('bn1', BNAndPadLayer(pad_pixels=padding, num_features=internal_channels_1x1_3x3, affine=True))
            self.dbb_1x1_kxk.add_module('conv2', nn.Conv2d(in_channels=internal_channels_1x1_3x3, out_channels=out_channels,
                                                            kernel_size=kernel_size, stride=stride, padding=0, groups=groups, bias=False))
            ......

该程序文件是一个名为rep_block.py的Python模块文件。该模块定义了一个名为DiverseBranchBlock的类,该类继承自nn.Module。该类实现了一个多样分支块,用于神经网络中的卷积操作。

该模块还定义了一些辅助函数和类,如transI_fusebntransII_addbranchtransIII_1x1_kxk等,用于在多样分支块中进行卷积操作的转换和合并。

DiverseBranchBlock类的构造函数接受一些参数,如输入通道数、输出通道数、卷积核大小等,用于初始化多样分支块的各个组件。该类还实现了前向传播方法forward,用于执行多样分支块的卷积操作。

此外,该模块还定义了一些辅助函数和类,如conv_bnIdentityBasedConv1x1BNAndPadLayer等,用于构建卷积层、标准化层和填充层。

最后,该模块还定义了一些辅助函数,如get_equivalent_kernel_biasswitch_to_deployinit_gamma等,用于获取等效的卷积核和偏置、切换到部署模式和初始化参数。

总体来说,该模块实现了一个多样分支块,用于神经网络中的卷积操作,并提供了一些辅助函数和类来支持其功能。

5.6 extra_modules\RFAConv.py
import torch
import torch.nn as nn
from einops import rearrange

class h_sigmoid(nn.Module):
    def __init__(self, inplace=True):
        super(h_sigmoid, self).__init__()
        self.relu = nn.ReLU6(inplace=inplace)

    def forward(self, x):
        return self.relu(x + 3) / 6

class h_swish(nn.Module):
    def __init__(self, inplace=True):
        super(h_swish, self).__init__()
        self.sigmoid = h_sigmoid(inplace=inplace)

    def forward(self, x):
        return x * self.sigmoid(x)

class RFAConv(nn.Module):
    def __init__(self,in_channel,out_channel,kernel_size,stride=1):
        super().__init__()
        self.kernel_size = kernel_size

        self.get_weight = nn.Sequential(nn.AvgPool2d(kernel_size=kernel_size, padding=kernel_size // 2, stride=stride),
                                        nn.Conv2d(in_channel, in_channel * (kernel_size ** 2), kernel_size=1, groups=in_channel,bias=False))
        self.generate_feature = nn.Sequential(
            nn.Conv2d(in_channel, in_channel * (kernel_size ** 2), kernel_size=kernel_size,padding=kernel_size//2,stride=stride, groups=in_channel, bias=False),
            nn.BatchNorm2d(in_channel * (kernel_size ** 2)),
            nn.ReLU())
       
        self.conv = nn.Sequential(nn.Conv2d(in_channel, out_channel, kernel_size=kernel_size, stride=kernel_size),
                                  nn.BatchNorm2d(out_channel),
                                  nn.ReLU())

    def forward(self,x):
        b,c = x.shape[0:2]
        weight =  self.get_weight(x)
        h,w = weight.shape[2:]
        weighted = weight.view(b, c, self.kernel_size ** 2, h, w).softmax(2)  # b c*kernel**2,h,w ->  b c k**2 h w 
        feature = self.generate_feature(x).view(b, c, self.kernel_size ** 2, h, w)  #b c*kernel**2,h,w ->  b c k**2 h w  
        weighted_data = feature * weighted
        conv_data = rearrange(weighted_data, 'b c (n1 n2) h w -> b c (h n1) (w n2)', n1=self.kernel_size, # b c k**2 h w ->  b c h*k w*k
                              n2=self.kernel_size)
        return self.conv(conv_data)


class SE(nn.Module):
    def __init__(self, in_channel, ratio=16):
        super(SE, self).__init__()
        self.gap = nn.AdaptiveAvgPool2d((1, 1))
        self.fc = nn.Sequential(
            nn.Linear(in_channel, ratio, bias=False),  # 从 c -> c/r
            nn.ReLU(),
            nn.Linear(ratio, in_channel, bias=False),  # 从 c/r -> c
            nn.Sigmoid()
        )
 
    def forward(self, x):
            b, c= x.shape[0:2]
            y = self.gap(x).view(b, c)
            y = self.fc(y).view(b, c,1, 1)
            return y

class RFCBAMConv(nn.Module):
    def __init__(self,in_channel,out_channel,kernel_size=3,stride=1):
        super().__init__()
        if kernel_size % 2 == 0:
            assert("the kernel_size must be  odd.")
        self.kernel_size = kernel_size
        self.generate = nn.Sequential(nn.Conv2d(in_channel,in_channel * (kernel_size**2),kernel_size,padding=kernel_size//2,
                                                stride=stride,groups=in_channel,bias =False),
                                      nn.BatchNorm2d(in_channel * (kernel_size**2)),
                                      nn.ReLU()
                                      )
        self.get_weight = nn.Sequential(nn.Conv2d(2,1,kernel_size=3,padding=1,bias=False),nn.Sigmoid())
        self.se = SE(in_channel)

        self.conv = nn.Sequential(nn.Conv2d(in_channel,out_channel,kernel_size,stride=kernel_size),nn.BatchNorm2d(out_channel),nn.ReLu())
        
    def forward(self,x):
        b,c = x.shape[0:2]
        channel_attention =  self.se(x)
        generate_feature = self.generate(x)

        h,w = generate_feature.shape[2:]
        generate_feature = generate_feature.view(b,c,self.kernel_size**2,h,w)
        
        generate_feature = rearrange(generate_feature, 'b c (n1 n2) h w -> b c (h n1) (w n2)', n1=self.kernel_size,
                              n2=self.kernel_size)
        
        unfold_feature = generate_feature * channel_attention
        max_feature,_ = torch.max(generate_feature,dim=1,keepdim=True)
        mean_feature = torch.mean(generate_feature,dim=1,keepdim=True)
        receptive_field_attention = self.get_weight(torch.cat((max_feature,mean_feature),dim=1))
        conv_data = unfold_feature  * receptive_field_attention
        return self.conv(conv_data)

class RFCAConv(nn.Module):
    def __init__(self, inp, oup, kernel_size, stride=1, reduction=32):
        super(RFCAConv, self).__init__()
        self.kernel_size = kernel_size
        self.generate = nn.Sequential(nn.Conv2d(inp,inp * (kernel_size**2),kernel_size,padding=kernel_size//2,
                                                stride=stride,groups=inp,
                                                bias =False),
                                      nn.BatchNorm2d(inp * (kernel_size**2)),
                                      nn.ReLU()
                                      )
        self.pool_h = nn.AdaptiveAvgPool2d((None, 1))
        self.pool_w = nn.AdaptiveAvgPool2d((1, None))

        mip = max(8, inp // reduction)

        self.conv1 = nn.Conv2d(inp, mip, kernel_size=1, stride=1, padding=0)
        self.bn1 = nn.BatchNorm2d(mip)
        self.act = h_swish()
        
        self.conv_h = nn.Conv2d(mip, inp, kernel_size=1, stride=1, padding=0)
        self.conv_w = nn.Conv2d(mip, inp, kernel_size=1, stride=1, padding=0)
        self.conv = nn.Sequential(nn.Conv2d(inp,oup,kernel_size,stride=kernel_size))
        

    def forward(self, x):
        b,c = x.shape[0:2]
        generate_feature = self.generate(x)
        h,w = generate_feature.shape[2:]
        generate_feature = generate_feature.view(b,c,self.kernel_size**2,h,w)
        
        generate_feature = rearrange(generate_feature, 'b c (n1 n2) h w -> b c (h n1) (w n2)', n1=self.kernel_size,
                              n2=self.kernel_size)
        
        x_h = self.pool_h(generate_feature)
        x_w = self.pool_w(generate_feature).permute(0, 1, 3, 2)

        y = torch.cat([x_h, x_w], dim=2)
        y = self.conv1(y)
        y = self.bn1(y)
        y = self.act(y) 
        
        h,w = generate_feature.shape[2:]
        x_h, x_w = torch.split(y, [h, w], dim=2)
        x_w = x_w.permute(0, 1, 3, 2)

        a_h = self.conv_h(x_h).sigmoid()
        a_w = self.conv_w(x_w).sigmoid()
        return self.conv(generate_feature * a_w * a_h)

这个程序文件是一个PyTorch模块,文件名为RFAConv.py。它定义了三个类:RFAConv、RFCBAMConv和RFCAConv。这些类都是继承自nn.Module类的子类。

RFAConv类实现了一个自适应的卷积操作。它接受输入张量x,并根据输入的kernel_size参数生成权重。然后,它使用这些权重对输入张量进行加权平均池化操作,并生成特征张量。最后,它将生成的特征张量通过卷积层进行卷积操作,得到最终的输出张量。

RFCBAMConv类实现了一个基于注意力机制的卷积操作。它首先使用一个卷积层生成特征张量,然后通过一个自定义的SE模块计算通道注意力。接下来,它将生成的特征张量进行分解,并计算最大特征和平均特征。然后,它使用一个卷积层生成一个注意力张量,并将分解后的特征张量与注意力张量相乘。最后,它通过卷积层对乘积结果进行卷积操作,得到最终的输出张量。

RFCAConv类实现了一个基于注意力机制的卷积操作。它首先使用一个卷积层生成特征张量,然后通过自适应平均池化操作计算高度和宽度的注意力。接下来,它将高度和宽度的注意力进行拼接,并通过一系列卷积层和激活函数进行处理。然后,它将处理后的注意力张量与生成的特征张量相乘,并通过卷积层进行卷积操作,得到最终的输出张量。

这些类都是用来实现不同的卷积操作,可以在深度学习模型中使用。

6.系统整体结构

以下是每个文件的功能总结:

文件 功能
train.py 训练YOLOv8模型的主程序
backbone\repvit.py 实现RepViT网络架构
backbone\revcol.py 实现RevCol网络架构
backbone\SwinTransformer.py 实现Swin Transformer网络架构
backbone\VanillaNet.py 实现VanillaNet网络架构
extra_modules\rep_block.py 实现多样分支块
extra_modules\RFAConv.py 实现基于注意力机制的卷积操作
extra_modules_init_.py extra_modules模块的初始化文件
extra_modules\ops_dcnv3\setup.py ops_dcnv3模块的安装脚本
extra_modules\ops_dcnv3\test.py ops_dcnv3模块的测试脚本
extra_modules\ops_dcnv3\functions\dcnv3_func.py ops_dcnv3模块的函数实现
extra_modules\ops_dcnv3\functions_init_.py ops_dcnv3模块的初始化文件
extra_modules\ops_dcnv3\modules\dcnv3.py ops_dcnv3模块的模块实现
extra_modules\ops_dcnv3\modules_init_.py ops_dcnv3模块的初始化文件
models\common.py 实现通用的模型函数
models\experimental.py 实现实验性的模型函数
models\tf.py 实现TensorFlow模型函数
models\yolo.py 实现YOLO模型函数
models_init_.py models模块的初始化文件
ultralytics_init_.py ultralytics模块的初始化文件
ultralytics\cfg_init_.py ultralytics.cfg模块的初始化文件
ultralytics\data\annotator.py 数据标注工具类
ultralytics\data\augment.py 数据增强函数
ultralytics\data\base.py 数据集基类
ultralytics\data\build.py 构建数据集的函数
ultralytics\data\converter.py 数据转换器类
ultralytics\data\dataset.py 数据集类
ultralytics\data\loaders.py 数据加载器类
ultralytics\data\utils.py 数据集工具函数
ultralytics\data_init_.py ultralytics.data模块的初始化文件
ultralytics\engine\exporter.py 模型导出器类
ultralytics\engine\model.py 模型类
ultralytics\engine\predictor.py 模型预测器类
ultralytics\engine\results.py 结果类
ultralytics\engine\trainer.py 训练器类
ultralytics\engine\tuner.py 调参器类

7.YOLOv8简介

YOLOv8 是 Ultralytics 公司继 YOLOv5 算法之后开发的下一代算法模型,目前支持图像分类、物体检测和实例分割任务。YOLOv8 是一个 SOTA 模型,它建立在之前YOLO 系列模型的成功基础上,并引入了新的功能和改进,以进一步提升性能和灵活性。具体创新包括:一个新的骨干网络、一个新的 Ancher-Free 检测头和一个新的损失函数,可以在从 CPU 到 GPU 的各种硬件平台上运行。注意到ultralytics 并没有直接将开源库命名为 YOLOv8,而是直接使用 Ultralytics这个单词,原因是Ultralytics这个库的定位是算法框架,而非特指某一个特定算法,其希望这个库不仅仅能够用于 YOLO 系列模型,同时也能支持其他的视觉任务如图像分类、实例分割等。下图画图YOLOv8目标检测算法同其他YOLO系列算法(YOLOv5、6、7)的实验对比图,左边是模型参数量对比,右边是速度对比。
【改进YOLOv8】可变车道指示牌识别系统:融合移动端网络架构 RepViT改进改进YOLOv8_第1张图片

下面两个表分别是YOLOv8和YOLOv5(v7.0版本)官方在 COCO Val 2017 数据集上测试结果,从中看出 YOLOv8 相比 YOLOv5 精度提升大,但是 N/S/M 模型相应的参数量、FLOPS等提高了不少。

【改进YOLOv8】可变车道指示牌识别系统:融合移动端网络架构 RepViT改进改进YOLOv8_第2张图片

YOLOv8概述

提供了一个全新的SOTA模型,和YOLOv5一样,基于缩放系数也提供了 N/S/M/L/X 尺度的不同大小模型,用于满足不同场景需求,同时支持图像分类、目标检测、实例分割和姿态检测任务
在骨干网络和Neck部分将YOLOv5的C3结构换成了梯度流更丰富的 C2f 结构,并对不同尺度模型调整了不同的通道数,大幅提升了模型性能;需要注意的是C2f 模块中存在Split等操作对特定硬件部署没有之前那么友好
【改进YOLOv8】可变车道指示牌识别系统:融合移动端网络架构 RepViT改进改进YOLOv8_第3张图片

Head部分换成了目前主流的解耦头结构,将分类和检测头分离,同时也从 Anchor-Based换成了Anchor-Free Loss
计算方面采用了 TaskAlignedAssigner 正样本分配策略,并引入了 Distribution Focal Loss
下图画出YOLOv8目标检测算法的整体结构图

YOLOv8模型

YOLOv8目标检测算法的模型配置文件如下:
【改进YOLOv8】可变车道指示牌识别系统:融合移动端网络架构 RepViT改进改进YOLOv8_第4张图片

从配置文件可以看出,YOLOv8与YOLOv5模型最明显的差异是使用C2F模块替换了原来的C3模块,两个模块的结构图下图所示。
【改进YOLOv8】可变车道指示牌识别系统:融合移动端网络架构 RepViT改进改进YOLOv8_第5张图片

另外Head 部分变化最大,从原先的耦合头变成了解耦头,并且从 YOLOv5 的 Anchor-Based 变成了 Anchor-Free。其结构对比图如下所示:
【改进YOLOv8】可变车道指示牌识别系统:融合移动端网络架构 RepViT改进改进YOLOv8_第6张图片

8.RepViT简介

近年来,与轻量级卷积神经网络(cnn)相比,轻量级视觉变压器(ViTs)在资源受限的移动设备上表现出了更高的性能和更低的延迟。这种改进通常归功于多头自注意模块,它使模型能够学习全局表示。然而,轻量级vit和轻量级cnn之间的架构差异还没有得到充分的研究。在这项研究中,我们重新审视了轻量级cnn的高效设计,并强调了它们在移动设备上的潜力。通过集成轻量级vit的高效架构选择,我们逐步增强了标准轻量级CNN的移动友好性,特别是MobileNetV3。这就产生了一个新的纯轻量级cnn家族,即RepViT。大量的实验表明,RepViT优于现有的轻型vit,并在各种视觉任务中表现出良好的延迟。在ImageNet上,RepViT在iPhone 12上以近1ms的延迟实现了超过80%的top-1精度,据我们所知,这是轻量级模型的第一次。

RepViT简介

轻量级模型研究一直是计算机视觉任务中的一个焦点,其目标是在降低计算成本的同时达到优秀的性能。轻量级模型与资源受限的移动设备尤其相关,使得视觉模型的边缘部署成为可能。在过去十年中,研究人员主要关注轻量级卷积神经网络(CNNs)的设计,提出了许多高效的设计原则,包括可分离卷积 、逆瓶颈结构 、通道打乱 和结构重参数化等,产生了 MobileNets ,ShuffleNets和 RepVGG 等代表性模型。

另一方面,视觉 Transformers(ViTs)成为学习视觉表征的另一种高效方案。与 CNNs 相比,ViTs 在各种计算机视觉任务中表现出了更优越的性能。然而,ViT 模型一般尺寸很大,延迟很高,不适合资源受限的移动设备。因此,研究人员开始探索 ViT 的轻量级设计。许多高效的ViTs设计原则被提出,大大提高了移动设备上 ViTs 的计算效率,产生了EfficientFormers ,MobileViTs等代表性模型。这些轻量级 ViTs 在移动设备上展现出了相比 CNNs 的更强的性能和更低的延迟。

轻量级 ViTs 优于轻量级 CNNs 的原因通常归结于多头注意力模块,该模块使模型能够学习全局表征。然而,轻量级 ViTs 和轻量级 CNNs 在块结构、宏观和微观架构设计方面存在值得注意的差异,但这些差异尚未得到充分研究。这自然引出了一个问题:轻量级 ViTs 的架构选择能否提高轻量级 CNN 的性能?在这项工作中,我们结合轻量级 ViTs 的架构选择,重新审视了轻量级 CNNs 的设计。我们的旨在缩小轻量级 CNNs 与轻量级 ViTs 之间的差距,并强调前者与后者相比在移动设备上的应用潜力。

在 ConvNeXt 中,参考该博客提出的基于 ResNet50 架构的基础上通过严谨的理论和实验分析,最终设计出一个非常优异的足以媲美 Swin-Transformer 的纯卷积神经网络架构。同样地,RepViT也是主要通过将轻量级 ViTs 的架构设计逐步整合到标准轻量级 CNN,即MobileNetV3-L,来对其进行针对性地改造(魔改)。在这个过程中,作者们考虑了不同粒度级别的设计元素,并通过一系列步骤达到优化的目标。
【改进YOLOv8】可变车道指示牌识别系统:融合移动端网络架构 RepViT改进改进YOLOv8_第7张图片

详细优化步骤如下:

训练配方的对齐

论文中引入了一种衡量移动设备上延迟的指标,并将训练策略与现有的轻量级 ViTs 对齐。这一步骤主要是为了确保模型训练的一致性,其涉及两个概念,即延迟度量和训练策略的调整。

延迟度量指标

为了更准确地衡量模型在真实移动设备上的性能,作者选择了直接测量模型在设备上的实际延迟,以此作为基准度量。这个度量方法不同于之前的研究,它们主要通过FLOPs或模型大小等指标优化模型的推理速度,这些指标并不总能很好地反映在移动应用中的实际延迟。

训练策略的对齐

这里,将 MobileNetV3-L 的训练策略调整以与其他轻量级 ViTs 模型对齐。这包括使用 AdamW 优化器-ViTs 模型必备的优化器,进行 5 个 epoch 的预热训练,以及使用余弦退火学习率调度进行 300 个 epoch 的训练。尽管这种调整导致了模型准确率的略微下降,但可以保证公平性。

块设计的优化

基于一致的训练设置,作者们探索了最优的块设计。块设计是 CNN 架构中的一个重要组成部分,优化块设计有助于提高网络的性能。

分离 Token 混合器和通道混合器

这块主要是对 MobileNetV3-L 的块结构进行了改进,分离了令牌混合器和通道混合器。原来的 MobileNetV3 块结构包含一个 1x1 扩张卷积,然后是一个深度卷积和一个 1x1 的投影层,然后通过残差连接连接输入和输出。在此基础上,RepViT 将深度卷积提前,使得通道混合器和令牌混合器能够被分开。为了提高性能,还引入了结构重参数化来在训练时为深度滤波器引入多分支拓扑。最终,作者们成功地在 MobileNetV3 块中分离了令牌混合器和通道混合器,并将这种块命名为 RepViT 块。

降低扩张比例并增加宽度

在通道混合器中,原本的扩张比例是 4,这意味着 MLP 块的隐藏维度是输入维度的四倍,消耗了大量的计算资源,对推理时间有很大的影响。为了缓解这个问题,我们可以将扩张比例降低到 2,从而减少了参数冗余和延迟,使得 MobileNetV3-L 的延迟降低到 0.65ms。随后,通过增加网络的宽度,即增加各阶段的通道数量,Top-1 准确率提高到 73.5%,而延迟只增加到 0.89ms!

宏观架构元素的优化

在这一步,本文进一步优化了MobileNetV3-L在移动设备上的性能,主要是从宏观架构元素出发,包括 stem,降采样层,分类器以及整体阶段比例。通过优化这些宏观架构元素,模型的性能可以得到显著提高。

浅层网络使用卷积提取器

ViTs 通常使用一个将输入图像分割成非重叠补丁的 “patchify” 操作作为 stem。然而,这种方法在训练优化性和对训练配方的敏感性上存在问题。因此,作者们采用了早期卷积来代替,这种方法已经被许多轻量级 ViTs 所采纳。对比之下,MobileNetV3-L 使用了一个更复杂的 stem 进行 4x 下采样。这样一来,虽然滤波器的初始数量增加到24,但总的延迟降低到0.86ms,同时 top-1 准确率提高到 73.9%。

更深的下采样层

在 ViTs 中,空间下采样通常通过一个单独的补丁合并层来实现。因此这里我们可以采用一个单独和更深的下采样层,以增加网络深度并减少由于分辨率降低带来的信息损失。具体地,作者们首先使用一个 1x1 卷积来调整通道维度,然后将两个 1x1 卷积的输入和输出通过残差连接,形成一个前馈网络。此外,他们还在前面增加了一个 RepViT 块以进一步加深下采样层,这一步提高了 top-1 准确率到 75.4%,同时延迟为 0.96ms。

更简单的分类器

在轻量级 ViTs 中,分类器通常由一个全局平均池化层后跟一个线性层组成。相比之下,MobileNetV3-L 使用了一个更复杂的分类器。因为现在最后的阶段有更多的通道,所以作者们将它替换为一个简单的分类器,即一个全局平均池化层和一个线性层,这一步将延迟降低到 0.77ms,同时 top-1 准确率为 74.8%。

整体阶段比例

阶段比例代表了不同阶段中块数量的比例,从而表示了计算在各阶段中的分布。论文选择了一个更优的阶段比例 1:1:7:1,然后增加网络深度到 2:2:14:2,从而实现了一个更深的布局。这一步将 top-1 准确率提高到 76.9%,同时延迟为 1.02 ms。

卷积核大小的选择

众所周知,CNNs 的性能和延迟通常受到卷积核大小的影响。例如,为了建模像 MHSA 这样的远距离上下文依赖,ConvNeXt 使用了大卷积核,从而实现了显著的性能提升。然而,大卷积核对于移动设备并不友好,因为它的计算复杂性和内存访问成本。MobileNetV3-L 主要使用 3x3 的卷积,有一部分块中使用 5x5 的卷积。作者们将它们替换为3x3的卷积,这导致延迟降低到 1.00ms,同时保持了76.9%的top-1准确率。

SE 层的位置

自注意力模块相对于卷积的一个优点是根据输入调整权重的能力,这被称为数据驱动属性。作为一个通道注意力模块,SE层可以弥补卷积在缺乏数据驱动属性上的限制,从而带来更好的性能。MobileNetV3-L 在某些块中加入了SE层,主要集中在后两个阶段。然而,与分辨率较高的阶段相比,分辨率较低的阶段从SE提供的全局平均池化操作中获得的准确率提升较小。作者们设计了一种策略,在所有阶段以交叉块的方式使用SE层,从而在最小的延迟增量下最大化准确率的提升,这一步将top-1准确率提升到77.4%,同时延迟降低到0.87ms。

注意!【这一点其实百度在很早前就已经做过实验比对得到过这个结论了,SE 层放置在靠近深层的地方效果好】

微观设计的调整

RepViT 通过逐层微观设计来调整轻量级 CNN,这包括选择合适的卷积核大小和优化挤压-激励(Squeeze-and-excitation,简称SE)层的位置。这两种方法都能显著改善模型性能。

网络架构

最终,通过整合上述改进策略,我们便得到了模型RepViT的整体架构,该模型有多个变种,例如RepViT-M1/M2/M3。同样地,不同的变种主要通过每个阶段的通道数和块数来区分。

【改进YOLOv8】可变车道指示牌识别系统:融合移动端网络架构 RepViT改进改进YOLOv8_第8张图片

9.系统整合

下图完整源码&数据集&环境部署视频教程&自定义UI界面

【改进YOLOv8】可变车道指示牌识别系统:融合移动端网络架构 RepViT改进改进YOLOv8_第9张图片

参考博客《可变车道指示牌识别系统:融合移动端网络架构 RepViT改进YOLOv8》

10.参考文献


[1]徐小高.基于强化学习的多场景可变车道自适应决策算法研究[D].2021.

你可能感兴趣的:(YOLO,目标跟踪,人工智能,yolov8)