【精选】融合上下文扩展和特征细化网络CAM改进YOLOv5的地铁屏幕异常状态预警系统

1.研究背景与意义

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

研究背景与意义

随着城市化进程的加速和人口规模的不断增长,地铁作为一种高效、便捷的交通工具在现代城市中扮演着重要的角色。然而,地铁系统的正常运行往往受到各种因素的影响,例如设备故障、人员拥堵、安全隐患等。因此,开发一种能够实时监测地铁屏幕异常状态的预警系统对于提高地铁运行的安全性和效率具有重要意义。

目前,随着计算机视觉和深度学习技术的快速发展,人们已经提出了许多基于图像和视频分析的异常检测方法。然而,传统的方法往往需要大量的人工标注数据和复杂的特征工程,且在复杂场景下的准确率和鲁棒性有限。因此,如何利用深度学习技术来提高地铁屏幕异常状态预警系统的准确性和实时性成为一个研究热点。

近年来,YOLOv5作为一种高效的目标检测算法在计算机视觉领域取得了显著的成果。然而,由于YOLOv5网络结构的特殊性,其在处理小目标和复杂背景下的性能仍然有待提高。因此,本研究旨在融合上下文扩展和特征细化网络CAM,改进YOLOv5算法,以提高地铁屏幕异常状态预警系统的准确性和实时性。

具体而言,本研究将通过引入上下文扩展和特征细化网络CAM来改进YOLOv5算法。上下文扩展可以帮助模型更好地理解图像中的语义信息,从而提高目标检测的准确性。特征细化网络CAM可以帮助模型更好地捕捉目标的细节特征,从而提高目标检测的鲁棒性。通过融合这两种改进方法,我们可以期望在地铁屏幕异常状态预警系统中获得更好的性能。

本研究的意义主要体现在以下几个方面:

首先,通过改进YOLOv5算法,我们可以提高地铁屏幕异常状态预警系统的准确性和实时性。这将有助于提高地铁运行的安全性和效率,减少因异常状态而引发的事故和延误。

其次,本研究的方法可以为其他类似的目标检测任务提供借鉴和参考。上下文扩展和特征细化网络CAM是通用的改进方法,可以应用于其他领域的目标检测任务,如交通监控、工业安全等。

最后,本研究的成果可以为深度学习技术在实际应用中的推广和应用提供参考。通过在地铁屏幕异常状态预警系统中的应用,我们可以验证和优化改进方法的有效性和实用性,为深度学习技术在其他领域的应用提供经验和指导。

综上所述,本研究旨在融合上下文扩展和特征细化网络CAM,改进YOLOv5算法,以提高地铁屏幕异常状态预警系统的准确性和实时性。通过该研究,我们可以为地铁运行的安全性和效率提供支持,为深度学习技术在实际应用中的推广和应用提供参考。

2.图片演示

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

3.视频演示

融合上下文扩展和特征细化网络CAM改进YOLOv5的地铁屏幕异常状态预警系统_哔哩哔哩_bilibili

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

图片的收集

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

在这里插入图片描述

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 CAM.py

下面是对代码的逐文件分析:

  1. 首先是class CAM(nn.Module),这是一个继承自nn.Module的类。该类有一个构造函数__init__,接受一个参数inc和一个可选参数fusion,并进行一些初始化操作。其中,fusion的取值只能是'weight''adaptive''concat'。该类还定义了一些成员变量,包括conv1conv2conv3fusion_1fusion_2fusion_3fusion_4。最后,该类还定义了一个forward方法,接受一个参数x,并根据fusion的取值进行不同的操作,返回结果。

  2. 接下来是elif m is CAM,这是一个条件语句,判断m是否是CAM类的实例。如果是,执行相应的操作。

  3. 最后是一个YAML文件,定义了一些参数和模型结构。其中,nc表示类别的数量,depth_multiple表示模型深度的倍数,width_multiple表示层通道的倍数,anchors表示锚框的尺寸。

以上是代码中最核心的部分,可以将其封装为一个类。

CAM.py是一个Python程序文件,包含了一个名为CAM的类。CAM类继承自nn.Module类,用于定义一个特征融合模块。

CAM类的构造函数__init__接受两个参数:inc和fusion。其中,inc表示输入通道数,fusion表示特征融合方式。fusion参数只能取’weight’、'adaptive’或’concat’三个值。

CAM类定义了一系列的卷积层和融合层。其中,conv1、conv2和conv3分别表示三个卷积层,fusion_1、fusion_2和fusion_3分别表示三个融合层。如果fusion参数为’adaptive’,则还会定义一个名为fusion_4的卷积层。

CAM类还定义了前向传播函数forward。在forward函数中,输入x经过三个卷积层得到x1、x2和x3。根据fusion参数的不同,选择不同的特征融合方式。如果fusion为’weight’,则将x1、x2和x3经过对应的融合层后相加;如果fusion为’adaptive’,则将x1、x2和x3经过对应的融合层后进行softmax操作得到权重,然后将x1、x2和x3分别乘以对应的权重后相加;如果fusion为’concat’,则将x1、x2和x3经过对应的融合层后在通道维度上进行拼接。

在文件的最后部分,还有一个elif语句,判断如果变量m等于CAM,则将变量ch[f]赋值给变量c1,如果args[0]等于’concat’,则将变量ch[f]乘以3赋值给变量c2。

文件的最后部分是一个YAML格式的配置文件,用于配置YOLOv5模型的相关参数。其中包括类别数、模型深度倍数、模型通道倍数以及锚点等信息。还定义了YOLOv5模型的backbone和head部分的结构和参数配置。

5.2 iou.py

class WIoU_Scale:
    ''' monotonous: {
            None: origin v1
            True: monotonic FM v2
            False: non-monotonic FM v3
        }
        momentum: The momentum of running mean'''
    
    iou_mean = 1.
    monotonous = False
    _momentum = 1 - 0.5 ** (1 / 7000)
    _is_train = True

    def __init__(self, iou):
        self.iou = iou
        self._update(self)
    
    @classmethod
    def _update(cls, self):
        if cls._is_train: cls.iou_mean = (1 - cls._momentum) * cls.iou_mean + \
                                         cls._momentum * self.iou.detach().mean().item()
    
    @classmethod
    def _scaled_loss(cls, self, gamma=1.9, delta=3):
        if isinstance(self.monotonous, bool):
            if self.monotonous:
                return (self.iou.detach() / self.iou_mean).sqrt()
            else:
                beta = self.iou.detach() / self.iou_mean
                alpha = delta * torch.pow(gamma, beta - delta)
                return beta / alpha
        return 1
    

def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, SIoU=False, EIoU=False, WIoU=False, Focal=False, alpha=1, gamma=0.5, scale=False, eps=1e-7):
    # Returns Intersection over Union (IoU) of box1(1,4) to box2(n,4)

    # Get the coordinates of bounding boxes
    if xywh:  # transform from xywh to xyxy
        (x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1)
        w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2
        b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_
        b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_
    else:  # x1, y1, x2, y2 = box1
        b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1)
        b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1)
        w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps)
        w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps)

    # Intersection area
    inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * \
            (b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)).clamp(0)

    # Union Area
    union = w1 * h1 + w2 * h2 - inter + eps
    if scale:
        self = WIoU_Scale(1 - (inter / union))

    # IoU
    # iou = inter / union # ori iou
    iou = torch.pow(inter/(union + eps), alpha) # alpha iou
    if CIoU or DIoU or GIoU or EIoU or SIoU or WIoU:
        cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1)  # convex (smallest enclosing box) width
        ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1)  # convex height
        if CIoU or DIoU or EIoU or SIoU or WIoU:  # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1
            c2 = (cw ** 2 + ch ** 2) ** alpha + eps  # convex diagonal squared
            rho2 = (((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4) ** alpha  # center dist ** 2
            if CIoU:  # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47
                v = (4 / math.pi ** 2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2)
                with torch.no_grad():
                    alpha_ciou = v / (v - iou + (1 + eps))
                if Focal:
                    return iou - (rho2 / c2 + torch.pow(v * alpha_ciou + eps, alpha)), torch.pow(inter/(union + eps), gamma)  # Focal_CIoU
                else:
                    return iou - (rho2 / c2 + torch.pow(v * alpha_ciou + eps, alpha))  # CIoU
            elif EIoU:
                rho_w2 = ((b2_x2 - b2_x1) - (b1_x2 - b1_x1)) ** 2
                rho_h2 = ((b2_y2 - b2_y1) - (b1_y2 - b1_y1)) ** 2
                cw2 = torch.pow(cw ** 2 + eps, alpha)
                ch2 = torch.pow(ch ** 2 + eps, alpha)
                if Focal:
                    return iou - (rho2 / c2 + rho_w2 / cw2 + rho_h2 / ch2), torch.pow(inter/(union + eps), gamma) # Focal_EIou
                else:
                    return iou - (rho2 / c2 + rho_w2 / cw2 + rho_h2 / ch2) # EIou
            elif SIoU:
                # SIoU Loss https://arxiv.org/pdf/2205.12740.pdf
                s_cw = (b2_x1 + b2_x2 - b1_x1 - b1_x2) * 0.5 + eps
                s_ch = (b2_y1 + b2_y2 - b1_y1 - b1_y2) * 0.5 + eps
                sigma = torch.pow(s_cw ** 2 + s_ch ** 2, 0.5)
                sin_alpha_1 = torch.abs(s_cw) / sigma
                sin_alpha_2 = torch.abs(s_ch) / sigma
                threshold = pow(2, 0.5) / 2
                sin_alpha = torch.where(sin_alpha_1 > threshold, sin

这个程序文件名为iou.py,主要包含了一个名为WIoU_Scale的类和一个名为bbox_iou的函数。

WIoU_Scale类有以下属性和方法:

  • iou_mean:iou的均值,默认为1.0
  • monotonous:是否使用单调性,可以是None、True或False,默认为False
  • _momentum:运行均值的动量,默认为1 - 0.5 ** (1 / 7000)
  • _is_train:是否处于训练状态,默认为True
  • __init__方法:初始化方法,接受一个iou参数,并调用_update方法更新iou_mean属性
  • _update方法:更新iou_mean属性的方法,根据_is_train属性和传入的iou参数计算新的iou_mean值
  • _scaled_loss方法:计算缩放后的损失值的方法,接受gamma和delta两个参数,根据monotonous属性的不同返回不同的计算结果

bbox_iou函数有以下参数和功能:

  • box1和box2:表示边界框的坐标,可以是xywh格式或xyxy格式
  • xywh:表示box1和box2是否是xywh格式,默认为True
  • GIoU、DIoU、CIoU、SIoU、EIoU、WIoU、Focal:表示是否计算对应的IoU损失,默认都为False
  • alpha、gamma、scale、eps:用于计算IoU损失的参数
  • 返回box1和box2的IoU值或根据参数计算得到的IoU损失值

最后,程序中还包含了一些yolov8和yolov5的代码,用于计算IoU损失。

5.3 train.py
class YOLOv5Trainer:
    def __init__(self, hyp, opt, device, callbacks):
        self.hyp = hyp
        self.opt = opt
        self.device = device
        self.callbacks = callbacks
        self.save_dir = Path(opt.save_dir)
        self.epochs = opt.epochs
        self.batch_size = opt.batch_size
        self.weights = opt.weights
        self.single_cls = opt.single_cls
        self.evolve = opt.evolve
        self.data = opt.data
        self.cfg = opt.cfg
        self.resume = opt.resume
        self.noval = opt.noval
        self.nosave = opt.nosave
        self.workers = opt.workers
        self.freeze = opt.freeze
        self.w = self.save_dir / 'weights'
        self.last = self.w / 'last.pt'
        self.best = self.w / 'best.pt'
        self.plots = not self.evolve and not opt.noplots
        self.cuda = self.device.type != 'cpu'
        self.data_dict = None
        self.loggers = None
        self.train_path = None
        self.val_path = None
        self.nc = None
        self.names = None
        self.is_coco = None
        self.model = None
        self.amp = None
        self.freeze = None
        self.optimizer = None
        self.scheduler = None
        self.ema = None
        self.best_fitness = None
        self.start_epoch = None

    def train(self):
        self.callbacks.run('on_pretrain_routine_start')
        (self.w.parent if self.evolve else self.w).mkdir(parents=True, exist_ok=True)
        if isinstance(self.hyp, str):
            with open(self.hyp, errors='ignore') as f:
                self.hyp = yaml.safe_load(f)
        LOGGER.info(colorstr('hyperparameters: ') + ', '.join(f'{
      
      k}={
      
      v}' for k, v in self.hyp.items()))
        self.opt.hyp = self.hyp.copy()
        if not self.evolve:
            yaml_save(self.save_dir / 'hyp.yaml', self.hyp)
            yaml_save(self.save_dir / 'opt.yaml', vars(self.opt))
        self.data_dict = self.check_dataset(self.data)
        self.train_path, self.val_path = self.data_dict['train'], self.data_dict['val']
        self.nc = 1 if self.single_cls else int(self.data_dict['nc'])
        self.names = {
    
    0: 'item'} if self.single_cls and len(self.data_dict['names']) != 1 else self.data_dict['names']
        self.is_coco = isinstance(self.val_path, str) and self.val_path.endswith('coco/val2017.txt')
        self.check_suffix(self.weights, '.pt')
        self.pretrained = self.weights.endswith('.pt')
        if self.pretrained:
            self.weights = self.attempt_download(self.weights)
            ckpt = torch.load(self.weights, map_location='cpu')
            self.model = Model(self.cfg or ckpt['model'].yaml, ch=3, nc=self.nc, anchors=self.hyp.get('anchors')).to(self.device)
            exclude = ['anchor'] if (self.cfg or self.hyp.get('anchors')) and not self.resume else []
            csd = ckpt['model'].float().state_dict()
            csd = intersect_dicts(csd, self.model.state_dict(), exclude=exclude)
            self.model.load_state_dict(csd, strict=False)
            LOGGER.info(f'Transferred {
      
      len(csd)}/{
      
      len(self.model.state_dict())} items from {
      
      self.weights}')
        else:
            self.model = Model(self.cfg, ch=3, nc=self.nc, anchors=self.hyp.get('anchors')).to(self.device)
        self.amp = self.check_amp(self.model)
        self.freeze = [f'model.{
      
      x}.' for x in (self.freeze if len(self.freeze) > 1 else range(self.freeze[0]))]
        for k, v in self.model.named_parameters():
            v.requires_grad = True
            if any(x in k for x in self.freeze):
                LOGGER.info(f'freezing {
      
      k}')
                v.requires_grad = False
        self.gs = max(int(self.model.stride.max()), 32)
        self.imgsz = self.check_img_size(self.opt.imgsz, self.gs, floor=self.gs * 2)
        if self.RANK == -1 and self.batch_size == -1:
            self.batch_size = self.check_train_batch_size(self.model, self.imgsz, self.amp)
            self.loggers.on_params_update({
    
    'batch_size': self.batch_size})
        self.nbs = 64
        self.accumulate = max(round(self.nbs / self.batch_size), 1)
        self.hyp['weight_decay'] *= self.batch_size * self.accumulate / self.nbs
        self.optimizer = self.smart_optimizer(self.model, self.opt.optimizer, self.hyp['lr0'], self.hyp['momentum'], self.hyp['weight_decay'])
        if self.opt.cos_lr:
            self.lf = one_cycle(1, self.hyp['lrf'], self.epochs)
        else:
            self.lf = lambda x: (1 - x / self.epochs) * (1.0 - self.hyp['lrf']) + self.hyp['lrf']
        self.scheduler = lr_scheduler.LambdaLR(self.optimizer, lr_lambda=self.lf)
        self.ema = ModelEMA(self.model) if self.RANK in {
    
    -1, 0} else None
        self.best_fitness, self.start_epoch = 0.0, 0
        if self.pretrained:
            if self.resume:
                self.best_fitness, self.start_epoch, self.epochs = self.smart_resume(ckpt, self.optimizer, self.ema, self.weights, self.epochs, self.resume)
            del ckpt, csd
        if self.cuda and self.RANK == -1 and torch.cuda.device_count() > 1:
            LOGGER.warning(
                'WARNING ⚠️ DP not recommended, use torch.distributed.run for best DDP Multi-GPU results.\n'
                'See Multi-GPU Tutorial at https://docs.ultralytics.com/yolov5/tutorials/multi_gpu_training to get started.'
            )
            self.model = torch.nn.DataParallel(self.model)
        if self.opt.sync_bn and self.cuda and self.RANK != -1:
            self.model = torch.nn.SyncBatchNorm.convert_sync_batchnorm(self.model).t
            ......

train.py是一个用于训练YOLOv5模型的程序文件。该程序文件可以在自定义数据集上训练YOLOv5模型,并支持单GPU和多GPU分布式训练。

程序文件中的主要功能包括:

  • 解析命令行参数,包括数据集配置文件、模型权重、图像尺寸等参数。
  • 加载模型配置文件和权重,并创建模型实例。
  • 设置训练超参数,如学习率、动量、权重衰减等。
  • 创建训练数据加载器和验证数据加载器。
  • 定义损失函数和优化器,并设置学习率调度器。
  • 定义训练循环,包括前向传播、计算损失、反向传播和优化器更新。
  • 定义验证循环,计算模型在验证集上的精度指标。
  • 定义模型保存和加载的逻辑。
  • 定义训练过程中的回调函数,如保存模型、绘制训练曲线等。

该程序文件还提供了一些辅助函数,用于检查数据集、模型权重、模型配置文件等的合法性,并提供了一些工具函数,如计算损失、计算精度指标、绘制训练曲线等。

该程序文件还支持使用多GPU进行分布式训练,并提供了一些命令行参数用于配置分布式训练的参数。

该程序文件还支持从预训练模型权重开始训练,或者从头开始训练。可以根据需要选择不同的训练方式。

最后,该程序文件还提供了一些辅助函数,用于处理日志记录、模型保存、模型加载等操作。

5.4 ui.py

class ObjectDetection:
    def __init__(self):
        FILE = Path(__file__).resolve()
        ROOT = FILE.parents[0]  # YOLOv5 root directory
        if str(ROOT) not in sys.path:
            sys.path.append(str(ROOT))  # add ROOT to PATH
        ROOT = Path(os.path.relpath(ROOT, Path.cwd()))  # relative

        from models.common import DetectMultiBackend
        from utils.augmentations import letterbox
        from utils.datasets import IMG_FORMATS, VID_FORMATS, LoadImages, LoadStreams
        from utils.general import (LOGGER, check_file, check_img_size, check_imshow, check_requirements, colorstr,
                                   increment_path, non_max_suppression, print_args, scale_coords, strip_optimizer, xyxy2xywh)
        from utils.plots import Annotator, colors, save_one_box
        from utils.torch_utils import select_device, time_sync

        def load_model(
                weights=ROOT / 'best.pt',  # model.pt path(s)
                data=ROOT / 'data/coco128.yaml',  # dataset.yaml path
                device='',  # cuda device, i.e. 0 or 0,1,2,3 or cpu
                half=False,  # use FP16 half-precision inference
                dnn=False,  # use OpenCV DNN for ONNX inference

        ):
            # Load model
            device = select_device(device)
            model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data)
            stride, names, pt, jit, onnx, engine = model.stride, model.names, model.pt, model.jit, model.onnx, model.engine

            # Half
            half &= (pt or jit or onnx or engine) and device.type != 'cpu'  # FP16 supported on limited backends with CUDA
            if pt or jit:
                model.model.half() if half else model.model.float()
            return model, stride, names, pt, jit, onnx, engine

        self.model, self.stride, self.names, self.pt, self.jit, self.onnx, self.engine = load_model()

    def run(self, img, imgsz=(640, 640), conf_thres=0.55, iou_thres=0.05, max_det=1000, device='', classes=None, agnostic_nms=False, augment=False, half=False):
        cal_detect = []

        device = select_device(device)
        names = self.model.module.names if hasattr(self.model, 'module') else self.model.names  # get class names

        # Set Dataloader
        im = letterbox(img, imgsz, self.stride, self.pt)[0]

        # Convert
        im = im.transpose((2, 0, 1))[::-1]  # HWC to CHW, BGR to RGB
        im = np.ascontiguousarray(im)

        im = torch.from_numpy(im).to(device)
        im = im.half() if half else im.float()  # uint8 to fp16/32
        im /= 255  # 0
        ......

这个程序文件是一个使用PyQt5编写的地铁屏幕异常状态预警系统。它使用YOLOv5模型进行目标检测,可以实时检测地铁屏幕的状态,并根据检测结果显示在界面上。

程序文件中的主要功能包括:

  • 导入所需的库和模块
  • 加载模型和设置模型参数
  • 定义目标检测函数
  • 定义一个线程类用于实时检测
  • 定义UI界面类并设置界面布局和样式
  • 实现界面上的按钮功能,包括选择文件、文件检测、实时检测、关闭检测和退出系统
  • 主函数中初始化模型并启动应用程序

程序的运行流程大致如下:

  1. 导入所需的库和模块
  2. 加载模型和设置模型参数
  3. 定义目标检测函数,用于识别地铁屏幕的状态
  4. 定义一个线程类,用于实时检测地铁屏幕的状态
  5. 定义UI界面类,并设置界面布局和样式
  6. 实现界面上的按钮功能,包括选择文件、文件检测、实时检测、关闭检测和退出系统
  7. 主函数中初始化模型并启动应用程序

整个程序的目的是实现地铁屏幕异常状态的预警,通过目标检测技术检测地铁屏幕的状态,并在界面上显示检测结果。用户可以选择文件进行检测,也可以进行实时检测。

5.5 yolov5-ContextAggregation.py


class ContextAggregation(nn.Module):
    """
    Context Aggregation Block.

    Args:
        in_channels (int): Number of input channels.
        reduction (int, optional): Channel reduction ratio. Default: 1.
        conv_cfg (dict or None, optional): Config dict for the convolution
            layer. Default: None.
    """

    def __init__(self, in_channels, reduction=1):
        super(ContextAggregation, self).__init__()
        self.in_channels = in_channels
        self.reduction = reduction
        self.inter_channels = max(in_channels // reduction, 1)

        conv_params = dict(kernel_size=1, act_cfg=None)

        self.a = ConvModule(in_channels, 1, **conv_params)
        self.k = ConvModule(in_channels, 1, **conv_params)
        self.v = ConvModule(in_channels, self.inter_channels, **conv_params)
        self.m = ConvModule(self.inter_channels, in_channels, **conv_params)

        self.init_weights()

    def init_weights(self):
        for m in (self.a, self.k, self.v):
            caffe2_xavier_init(m.conv)
        constant_init(self.m.conv, 0)

    def forward(self, x):
        n, c = x.size(0), self.inter_channels

        # a: [N, 1, H, W]
        a = self.a(x).sigmoid()

        # k: [N, 1, HW, 1]
        k = self.k(x).view(n, 1, -1, 1).softmax(2)

        # v: [N, 1, C, HW]
        v = self.v(x).view(n, 1, c, -1)

        # y: [N, C, 1, 1]
        y = torch.matmul(v, k).view(n, c, 1, 1)
        y = self.m(y) * a

        return x + y

这是一个名为yolov5-ContextAggregation.py的程序文件。该文件定义了一个名为ContextAggregation的类,用于实现上下文聚合块。该类接受输入通道数和通道缩减比例作为参数,并定义了一系列卷积操作。在初始化函数中,通过调用init_weights函数对模型参数进行初始化。在前向传播函数中,首先对输入进行一系列卷积操作,然后计算注意力权重并将其应用于特征图,最后将注意力加权的特征图与输入相加并返回结果。

此外,文件中还包含了YOLOv5模型的参数设置,包括类别数、模型深度倍数、层通道倍数和锚框信息。模型的主干网络和头部网络结构也在文件中定义,其中包括一系列卷积、上采样和拼接操作。最后,通过调用ContextAggregation类和Detect类来构建完整的YOLOv5模型。

6.系统整体结构

整体功能和构架概述:
该项目是一个视觉项目,旨在开发一个地铁屏幕异常状态预警系统。该系统使用了融合上下文扩展和特征细化网络CAM改进的YOLOv5模型进行目标检测,并使用上下文聚合块进行特征融合。此外,还包括了分类和分割模块,用于对检测到的目标进行分类和分割。系统还提供了一个用户界面,用于实时显示检测结果。

下表整理了每个文件的功能:

文件路径 功能
CAM.py 定义特征融合模块
iou.py 计算IoU损失和相关指标
train.py 训练YOLOv5模型
ui.py 实现地铁屏幕异常状态预警系统的用户界面
yolov5-ContextAggregation.py 定义上下文聚合块
classify/predict.py 使用YOLOv5模型进行分类推断
classify/train.py 训练分类模型
classify/val.py 在验证集上评估分类模型
models/common.py 定义一些通用的模型组件
models/experimental.py 定义一些实验性的模型组件
models/tf.py 定义与TensorFlow相关的模型组件
models/yolo.py 定义YOLOv5模型
models/init.py 模型初始化文件
segment/predict.py 使用YOLOv5模型进行分割推断
segment/train.py 训练分割模型
segment/val.py 在验证集上评估分割模型
utils/activations.py 定义激活函数
utils/augmentations.py 定义数据增强方法
utils/autoanchor.py 自动计算锚框
utils/autobatch.py 自动批处理
utils/callbacks.py 定义训练过程中的回调函数
utils/dataloaders.py 定义数据加载器
utils/datasets.py 定义数据集类
utils/downloads.py 下载相关的功能
utils/general.py 定义一些通用的辅助函数
utils/loss.py 定义损失函数
utils/metrics.py 定义评估指标
utils/plots.py 绘制相关的图表
utils/torch_utils.py Torch相关的辅助函数
utils/triton.py Triton相关的辅助函数
utils/init.py 工具函数初始化文件
utils/aws/resume.py AWS相关的恢复功能
utils/aws/init.py AWS初始化文件
utils/flask_rest_api/example_request.py Flask REST API示例请求
utils/flask_rest_api/restapi.py Flask REST API相关功能
utils/loggers/init.py 日志记录初始化文件

7.上下文扩展和特征细化网络CAM简介

CAM来源于一篇ICLR 2022的会议论文,ICLR由Lecun,Hinton和Bengio三位神经网络的元老联手发起。近年来随着深度学习在工程实践中的成功,ICLR会议也在短短的几年中发展成为了神经网络的顶会。哎,我也想发这样的文章,今天我们就来看看这篇文章的成功之处,虽然论文给了代码但是现在打不开或者无法访问到,裂开了! 但是这篇文章的实验做得还是挺充分的,可视化也挺好,属于比较不错的一类,自己达不到的水平,哈哈!
在这里插入图片描述

微小物体由于分辨率低、体积小,很难被探测到。网络的局限性和训练数据集的不平衡是导致微小目标检测性能不佳的主要原因该文提出了一种复合结构的FPN,它包含一个上下文扩展模块和一个特征细化模块。上下文增强模块利用扩展卷积提取不同接收域的上下文信息,并将其集成到FPN中,对微小物体的上下文信息进行改进。特征细化模块结合了空间自适应融合和信道自适应融合,从信道和空间维度抑制冲突特征,突出有用特征。此外,为了防止训练不平衡,提出了一种微小对象的复制-减少-粘贴数据增强方法。

金字塔结构FPN可以在一定程度上缓解信息扩散问题,通过水平融合低分辨率特征图和高分辨率特征图。但是,直接融合不同密度的信息会导致语义冲突,限制了多尺度特征的表达,容易使微小的物体淹没在冲突的信息中。同时,在当前的经典公共数据集中,微小对象的注释数量远远少于较大目标的注释数量(Chen et al, 2020)。因此,在训练过程中,网络的收敛方向不断向较大的目标倾斜,导致对微小目标的性能较差。因此作者从这两方面作为突破口进行改进来提升小目标检测。为解决微小物体特征分散(意思就是小物体分布的非常散,并不是聚集在一起的)和层间语义差异(不同的层所代表的语义信息会产生混乱,比如说深层特征中包含的小目标信息较少,但是有时候影响到大目标的预测结果)的问题,提出了一种结合上下文增强和特征细化的特征金字塔复合神经网络结构。提出的算法框架如下图所示。在整体网络结构。CAM和FRM是该网络的主要组成部分。CAM向FPN注入上下文信息,FRM对FPN的冲突信息进行过滤。

上下文增强模块(CAM,CONTEXT AUGMENTATION MODULE)

微小目标检测需要上下文信息。我们提出使用具有不同扩张卷积率的扩张卷积来获取不同接受域的上下文信息,以丰富FPN的上下文信息。如下图所示,这就是常用的ASPP,当然如果只是这样引用肯定是不行的,所以作者做了下面的事情。

在这里插入图片描述

作者又将这样的ASPP模块的融合方式通过下面三种方式进行实验,其中(a)和(c)方式就是一般的进行相加和拼接,几种不同的特征的权重是相同的,而对于(b)方式就是将最终结果再通过一个注意力机制进行重要性分析。一般来说第二种方式是比较不错的,因为这种方法我是在其他论文上见过的,在那篇小目标检测论文中,(b)的方式是一个创新点部分。但是在这篇文章中,作者通过实验直接得到(c)的融合方式对小目标的检测是最好的。如下表所示。

在这里插入图片描述

功能模块细化(FRM,FEATURE REFINEMENT MODULE)

这一部分的内容就是用来过滤冲突信息,防止微小物体的特征直接被淹没在冲突信息中,因为在FPN中对不同尺度进行融合,容易产生大量的冗余信息和冲突信息,从而降低了多尺度表达能力。具体结构如下图所示:
在这里插入图片描述

该结构分为两种模块,分别是通道上的过滤模块以及空间上的过滤模块,对于通道净化模块,作者首先使用两种池化方法,然后将自适应平均池和自适应最大池相结合,获得更精细的图像全局特征。空间过滤模块 通过softmax生成各位置相对于通道的相对权重。至于一些公式,其实还是得看源码。当然其实也并不是特别的难,就是对不同的特征层自适应的赋予不同的权重。

8.YOLOv5改进(CONTEXT AUGMENTATION MODULE,CAM)

CONTEXT AUGMENTATION MODULE

通过C5 之后经过3个卷积核大小为3*3,扩张卷积率为1、3、5的卷积,然后三个卷积再进行融合。这样做融合不同感受野获得的特征,丰富上下文信息,获得更好的特征提取效果,便于微笑目标的检测,

融合有三种策略(代码中在yaml文件中可以选择)。方法(a)是加权融合,方法(b)是自适应融合 ,即假设输入的大小可以表示为(bs,C,H,W),可以通过执行卷积运算来获得(bs,3,H,W)的空间自适应权重连接和Softmax。三个通道与三个输入一一对应,通过计算加权和,可以将上下文信息聚合到输出,方法(c)是做concatenation,例:a = np.array([[1,2],[3,4]]),b =np.arrat([[5,6]]),c=np.concatenate((a,b)),c=array([1,2],[3,4],[5,6]).

yolov5n-CAM网络结构
### yolov5 cam yaml
nc: 1  # number of classes
depth_multiple: 0.33  # model depth multiple
width_multiple: 0.25  # layer channel multiple
anchors:
  - [10,13, 16,30, 33,23]  # P3/8
  - [30,61, 62,45, 59,119]  # P4/16
  - [116,90, 156,198, 373,326]  # P5/32

# YOLOv5 v6.0 backbone
backbone:
  # [from, number, module, args]
  [[-1, 1, Conv, [64, 6, 2, 2]],  # 0-P1/2
   [-1, 1, Conv, [128, 3, 2]],  # 1-P2/4
   [-1, 3, C3, [128]],
   [-1, 1, Conv, [256, 3, 2]],  # 3-P3/8
   [-1, 6, C3, [256]],
   [-1, 1, Conv, [512, 3, 2]],  # 5-P4/16
   [-1, 9, C3, [512]],
   [-1, 1, Conv, [1024, 3, 2]],  # 7-P5/32
   [-1, 3, C3, [1024]],
   [-1, 1, SPPF, [1024, 5]],  # 9
  ]

# YOLOv5 v6.0 head
head:
  [[-1, 1, Conv, [512, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 6], 1, Concat, [1]],  # cat backbone P4
   [-1, 3, C3, [512, False]],  # 13

   [-1, 1, Conv, [256, 1, 1]],
   [-1, 1, nn.Upsample, [None, 2, 'nearest']],
   [[-1, 4], 1, Concat, [1]],  # cat backbone P3
   [-1, 3, C3, [256, False]],  # 17 (P3/8-small)

   [-1, 1, Conv, [256, 3, 2]],
   [[-1, 14], 1, Concat, [1]],  # cat head P4
   [-1, 3, C3, [512, False]],  # 20 (P4/16-medium)

   [-1, 1, Conv, [512, 3, 2]],
   [10, 1, CAM, ['weight']],
   [[-2, -1], 1, Concat, [1]],  # cat head P5
   [-1, 3, C3, [1024, False]],  # 23 (P5/32-large)

   [[17, 20, 24], 1, Detect, [nc, anchors]],  # Detect(P3, P4, P5)
  ]

9.训练结果可视化分析

评价指标

epoch:训练纪元数。
train/box_loss、、、train/obj_loss:train/cls_loss训练期间边界框、对象和类预测的损失指标。
metrics/precision、metrics/recall、metrics/mAP_0.5、metrics/mAP_0.5:0.95:不同 IoU 阈值下的精度、召回率和平均精度 (mAP)。
val/box_loss、、、val/obj_loss:val/cls_loss验证期间边界框、对象和类预测的损失指标。
x/lr0, x/lr1, x/lr2:模型中不同层或阶段的学习率。

训练结果可视化

为了可视化和分析这些数据,我们将创建几个图表来了解这些指标在不同时期的趋势。此分析将帮助我们了解模型的性能在训练和验证过程中如何演变。绘图后,我将对结果进行详细分析。让我们从可视化开始。

import matplotlib.pyplot as plt

# Plotting settings
plt.figure(figsize=(20, 15))
plt.subplots_adjust(hspace=0.5)

# Training loss plots
plt.subplot(3, 2, 1)
plt.plot(data['epoch'], data['train/box_loss'], label='Box Loss')
plt.plot(data['epoch'], data['train/obj_loss'], label='Object Loss')
plt.plot(data['epoch'], data['train/cls_loss'], label='Class Loss')
plt.title('Training Losses')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()

# Validation loss plots
plt.subplot(3, 2, 2)
plt.plot(data['epoch'], data['val/box_loss'], label='Box Loss')
plt.plot(data['epoch'], data['val/obj_loss'], label='Object Loss')
plt.plot(data['epoch'], data['val/cls_loss'], label='Class Loss')
plt.title('Validation Losses')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()

# Precision, Recall, and mAP plots
plt.subplot(3, 2, 3)
plt.plot(data['epoch'], data['metrics/precision'], label='Precision')
plt.plot(data['epoch'], data['metrics/recall'], label='Recall')
plt.title('Precision and Recall')
plt.xlabel('Epoch')
plt.ylabel('Metric Value')
plt.legend()

plt.subplot(3, 2, 4)
plt.plot(data['epoch'], data['metrics/mAP_0.5'], label='[email protected]')
plt.plot(data['epoch'], data['metrics/mAP_0.5:0.95'], label='[email protected]:0.95')
plt.title('Mean Average Precision (mAP)')
plt.xlabel('Epoch')
plt.ylabel('mAP')
plt.legend()

# Learning rate plots
plt.subplot(3, 2, 5)
plt.plot(data['epoch'], data['x/lr0'], label='LR 0')
plt.plot(data['epoch'], data['x/lr1'], label='LR 1')
plt.plot(data['epoch'], data['x/lr2'], label='LR 2')
plt.title('Learning Rates')
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.legend()

# Show all plots
plt.show()

在这里插入图片描述
可视化提供了模型在训练周期内各种指标的性能的全面概述。现在,让我们详细分析这些结果:

训练和验证损失

框、对象和类损失(训练):随着时代的进展,所有三个损失指标(框、对象和类)都显示出下降趋势。这表明该模型正在有效地学习,提高了其预测边界框、识别对象和正确分类的能力。
框、对象和类损失(验证):验证损失也会随着时间的推移而减少,反映了训练损失。这是一个积极的信号,表明该模型没有过度拟合,并且可以很好地推广到未见过的数据。

精度、召回率和平均精度 (mAP)

精确度和召回率:精确度一开始相当低,但后来显着增加,表明模型在做出正确的积极预测方面变得越来越好。召回率也增加,表明模型查找所有相关案例的能力不断提高。
[email protected][email protected]:0.95:两个 mAP 指标都会随着时间的推移而增加。[email protected] 始终高于 [email protected]:0.95,这是预期的,因为后者是一个更严格的指标。这些指标的不断改进表明该模型在准确定位和分类对象方面的熟练程度不断提高。

学习率

学习率 ( x/lr0、x/lr1和x/lr2) 似乎随着时间的推移而降低,这在训练深度学习模型中很常见。这种逐渐减少有助于微调模型的权重,特别是当它接近最佳性能时。

混淆矩阵分析

混淆矩阵显示出近乎完美的分类,具有很高的真阳性率和非常低的假阴性率,表明该模型在识别阳性类方面非常有效。一些误报的存在表明,虽然模型对正类高度敏感,但它偶尔会将负实例错误分类为正实例。
在这里插入图片描述

F1分数曲线分析

F1 分数是精确率和召回率的调和平均值,当类别分布不均匀时,可以在两者之间实现平衡。F1 得分曲线在不同的置信度阈值上显示出较高的得分,这意味着该模型在其决策阈值上保持了精度和召回率之间的良好平衡。
在这里插入图片描述

精确率-召回率曲线分析

精确率-召回率 (PR) 曲线提供了不同阈值设置下精确率和召回率之间权衡的详细视图。靠近右上角的曲线表示模型同时实现了高精度和召回率。提供的 PR 曲线靠近右上角,表明模型具有较高的曲线下面积 (AUC),通常表明模型性能良好。
在这里插入图片描述

精密曲线分析

不同置信水平的精度曲线仍然很高,这表明即使置信阈值发生变化,真阳性在阳性中的比例仍然保持一致。
在这里插入图片描述

召回曲线分析

召回曲线表明,该模型能够识别不同置信阈值下的大多数真阳性,表明检测阳性类别的鲁棒性。
在这里插入图片描述

标签分布分析

显示标签实例的条形图表明存在明显的类不平衡,其中一个类的实例多于另一类。类别不平衡通常会导致模型偏向多数类别。在散点图中,标记数据点的分布可以指示模型可能用于做出决策的数据点集群。相关图可以表明不同标签之间的相关性,表明某些标签可能同时出现。
在这里插入图片描述

整体分析

损失减少、精确度、召回率和 mAP 增加的总体趋势表明训练和验证阶段成功。该模型正在按预期进行学习,并在多个时期内提高其检测和分类能力。
训练和验证指标的并行改进表明了良好的泛化性。训练和验证性能之间没有显着差异,这通常表明过度拟合。
学习率策略似乎是有效的,有助于模型的逐步稳定改进。

进一步改进的考虑因素

数据增强:如果尚未实施,数据增强技术可以进一步增强模型的稳健性。
超参数调整:尝试不同的学习率、批量大小甚至模型架构可以产生更好的性能。
先进技术:实施先进技术,如转移学习(如果适用)或探索不同的骨干网络进行特征提取也可能是有益的。
总而言之,结果是有希望的,显示了模型的有效学习和泛化能力。损失的减少以及精度、召回率和 mAP 指标的增加表明模型在地铁屏幕异常状态预警系统中表现良好。进一步的改进和实验可能会带来更强大的性能。

10.系统整合

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

在这里插入图片描述

参考博客《融合上下文扩展和特征细化网络CAM改进YOLOv5的地铁屏幕异常状态预警系统》

猜你喜欢

转载自blog.csdn.net/cheng2333333/article/details/135367909
今日推荐