当前位置:   article > 正文

【精选】迁移DAMO-YOLO中的GFPN的改进YOLOv5的槟榔叶病虫害的识别系统

gfpn

1.研究背景与意义

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

研究背景与意义:

槟榔叶病虫害是槟榔树种植过程中常见的问题,严重影响了槟榔产量和质量。传统的槟榔叶病虫害识别方法主要依赖于人工观察和经验判断,存在识别准确率低、效率低下等问题。因此,开发一种高效准确的槟榔叶病虫害识别系统具有重要的实际意义。

近年来,深度学习技术在图像识别领域取得了巨大的突破,特别是目标检测领域。其中,YOLO(You Only Look Once)是一种快速且准确的目标检测算法,已经被广泛应用于各种领域。然而,传统的YOLO算法在处理小目标和密集目标时存在一定的困难,且对于复杂背景下的目标识别效果较差。

为了解决上述问题,本研究提出了一种改进的YOLOv5模型,引入了GFPN(Global Feature Pyramid Network)模块。GFPN模块可以有效地提取全局特征,并将其与局部特征进行融合,从而提高目标检测的准确性和鲁棒性。同时,为了提高模型的训练效率,我们采用了DAMO(Dense Asymmetric Multi-scale Output)模块,该模块可以在不同尺度上生成密集的检测输出,提高小目标和密集目标的检测效果。

本研究的主要目标是开发一种高效准确的槟榔叶病虫害识别系统,具体包括以下几个方面的意义:

首先,该系统可以提高槟榔叶病虫害的识别准确率。传统的人工观察和经验判断容易受到主观因素的影响,识别结果不稳定。而基于深度学习的目标检测算法可以通过大量的样本数据进行训练,提高识别准确率和稳定性。

其次,该系统可以提高槟榔叶病虫害的识别效率。传统的人工观察需要耗费大量的时间和人力,而基于深度学习的目标检测算法可以实现实时的自动识别,大大提高了识别效率。

此外,该系统还可以为槟榔种植者提供科学决策支持。通过实时监测和识别槟榔叶病虫害,种植者可以及时采取相应的防治措施,减少病虫害对槟榔产量和质量的影响,提高经济效益。

综上所述,开发一种基于改进的YOLOv5模型的槟榔叶病虫害识别系统具有重要的实际意义。该系统可以提高识别准确率和效率,为槟榔种植者提供科学决策支持,促进槟榔产业的健康发展。

2.图片演示

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

3.视频演示

迁移DAMO-YOLO中的GFPN的改进YOLOv5的槟榔叶病虫害的识别系统_哔哩哔哩_bilibili

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

图片的收集

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

在这里插入图片描述

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列表

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
整理数据文件夹结构

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

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

确保以下几点:

所有的训练图片都位于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     mAP@.5 mAP@.5:.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     mAP@.5 mAP@.5:.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     mAP@.5 mAP@.5:.95: 100%|███████   | 187/213 [00:52<00:00,  4.04it/s]
             all       3395      17314      0.996      0.957      0.0957      0.0845
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

5.核心代码讲解

5.1 export.py



def export_formats():
    # YOLOv5 export formats
    x = [
        ['PyTorch', '-', '.pt', True, True],
        ['TorchScript', 'torchscript', '.torchscript', True, True],
        ['ONNX', 'onnx', '.onnx', True, True],
        ['OpenVINO', 'openvino', '_openvino_model', True, False],
        ['TensorRT', 'engine', '.engine', False, True],
        ['CoreML', 'coreml', '.mlmodel', True, False],
        ['TensorFlow SavedModel', 'saved_model', '_saved_model', True, True],
        ['TensorFlow GraphDef', 'pb', '.pb', True, True],
        ['TensorFlow Lite', 'tflite', '.tflite', True, False],
        ['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False, False],
        ['TensorFlow.js', 'tfjs', '_web_model', False, False],
        ['PaddlePaddle', 'paddle', '_paddle_model', True, True],]
    return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'CPU', 'GPU'])


def try_export(inner_func):
    # YOLOv5 export decorator, i..e @try_export
    inner_args = get_default_args(inner_func)

    def outer_func(*args, **kwargs):
        prefix = inner_args['prefix']
        try:
            with Profile() as dt:
                f, model = inner_func(*args, **kwargs)
            LOGGER.info(f'{prefix} export success ✅ {dt.t:.1f}s, saved as {f} ({file_size(f):.1f} MB)')
            return f, model
        except Exception as e:
            LOGGER.info(f'{prefix} export failure ❌ {dt.t:.1f}s: {e}')
            return None, None

    return outer_func


@try_export
def export_torchscript(model, im, file, optimize, prefix=colorstr('TorchScript:')):
    # YOLOv5 TorchScript model export
    LOGGER.info(f'\n{prefix} starting export with torch {torch.__version__}...')
    f = file.with_suffix('.torchscript')

    ts = torch.jit.trace(model, im, strict=False)
    d = {"shape": im.shape, "stride": int(max(model.stride)), "names": model.names}
    extra_files = {'config.txt': json.dumps(d)}  # torch._C.ExtraFilesMap()
    if optimize:  # https://pytorch.org/tutorials/recipes/mobile_interpreter.html
        optimize_for_mobile(ts)._save_for_lite_interpreter(str(f), _extra_files=extra_files)
    else:
        ts.save(str(f), _extra_files=extra_files)
    return f, None


@try_export
def export_onnx(model, im, file, opset, dynamic, simplify, prefix=colorstr('ONNX:')):
    # YOLOv5 ONNX export
    check_requirements('onnx')
    import onnx

    LOGGER.info(f'\n{prefix} starting export with onnx {onnx.__version__}...')
    f = file.with_suffix('.onnx')

    output_names = ['output0', 'output1'] if isinstance(model, SegmentationModel) else ['output0']
    if dynamic:
        dynamic = {'images': {0: 'batch', 2: 'height', 3: 'width'}}  # shape(1,3,640,640)
        if isinstance(model, SegmentationModel):
            dynamic['output0'] = {0: 'batch', 1: 'anchors'}  # shape(1,25200,85)
            dynamic['output1'] = {0: 'batch', 2: 'mask_height', 3: 'mask_width'}  # shape(1,32,160,160)
        elif isinstance(model, DetectionModel):
            dynamic['output0'] = {0: 'batch', 1: 'anchors'}  # shape(1,25200,85)

    torch.onnx.export(
        model.cpu() if dynamic else model,  # --dynamic only compatible with cpu
        im.cpu() if dynamic else im,
        f,
        verbose=False,
        opset_version=opset,
        do_constant_folding=True,
        input_names=['images'],
        output_names=output_names,
        dynamic_axes=dynamic or None)

    # Checks
    model_onnx = onnx.load(f)  # load onnx model
    onnx.checker.check_model(model_onnx)  # check onnx model

    # Metadata
    d = {'stride': int(max(model.stride)), 'names': model.names}
    for k, v in d.items():
        meta = model_onnx.metadata_props.add()
        meta.key, meta.value = k, str(v)
    onnx.save(model_onnx, f)

    # Simplify
    if simplify:
        try:
            cuda = torch.cuda.is_available()
            check_requirements(('onnxruntime-gpu' if cuda else 'onnxruntime', 'onnx-simplifier>=0.4.1'))
            import onnxsim

            LOGGER.info(f'{prefix} simplifying with onnx-simplifier {onnxsim.__version__}...')
            model_onnx, check
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104

export.py是一个用于将YOLOv5 PyTorch模型导出为其他格式的程序文件。该文件定义了一些导出函数,包括export_torchscript和export_onnx,这些函数分别用于导出TorchScript和ONNX格式的模型。此外,该文件还定义了一些辅助函数和常量,以及一个用于解析命令行参数的函数。该文件还包含了一些示例用法和导出格式的说明。

该文件的主要功能是将YOLOv5模型导出为不同的格式,包括PyTorch、TorchScript、ONNX、OpenVINO、TensorRT、CoreML、TensorFlow SavedModel、TensorFlow GraphDef、TensorFlow Lite、TensorFlow Edge TPU、TensorFlow.js和PaddlePaddle。用户可以使用命令行参数来指定要导出的格式。导出的模型文件将保存在指定的路径下。

该文件还包含了一些辅助函数,用于检查系统环境和依赖库的安装情况,以及一些工具函数,用于加载图像数据和进行推理操作。

总之,export.py是一个用于将YOLOv5模型导出为其他格式的工具文件,提供了多种导出格式的选项,并提供了一些辅助函数和工具函数来支持导出过程。

5.2 extra_modules.py

这个程序文件名为extra_modules.py,它包含了一些额外的模块和网络层的定义。

其中包含的模块和网络层有:

  • conv_bn函数:用于创建包含卷积和批归一化的基本单元。
  • RepConv类:一个基本的rep-style块,包括训练和部署状态的定义。
  • Swish类:一个Swish激活函数的定义。
  • get_activation函数:根据名称获取激活函数的定义。
  • get_norm函数:根据名称获取归一化层的定义。
  • ConvBNAct类:一个包含卷积、批归一化和激活函数的块。
  • BasicBlock_3x3_Reverse类:一个基本的3x3反向块。
  • SPP类:一个空间金字塔池化层的定义。
  • CSPStage类:一个CSP阶段的定义,包括多个BasicBlock_3x3_Reverse块和SPP层。

这些模块和网络层的定义可以用于构建深度学习模型

5.2 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.w.parent if self.evolve else self.w).mkdir(parents=True, exist_ok=True)
        self.last = self.w / 'last.pt'
        self.best = self.w / 'best.pt'
        self.loggers = None
        self.data_dict = None
        self.plots = not self.evolve
        self.cuda = self.device.type != 'cpu'
        self.init_seeds(1 + RANK)
        self.data_dict = self.data_dict or 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 = ['item'] if self.single_cls and len(self.data_dict['names']) != 1 else self.data_dict['names']
        assert len(self.names) == self.nc, f'{len(self.names)} names found for nc={self.nc} dataset in {self.data}'
        self.is_coco = self.data.endswith('coco.yaml') and self.nc == 80
        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=self.device)
            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 = self.intersect_dicts(csd, self.model.state_dict(), exclude=exclude)
            self.model.load_state_dict(csd, strict=False)
        else:
            self.model = Model(self.cfg, ch=3, nc=self.nc, anchors=self.hyp.get('anchors')).to(self.device)
        self.freeze = [f'model.{x}.' for x in range(self.freeze)]
        for k, v in self.model.named_parameters():
            v.requires_grad = True
            if any(x in k for x in self.freeze):
                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 RANK == -1 and self.batch_size == -1:
            self.batch_size = self.check_train_batch_size(self.model, self.imgsz)
        nbs = 64
        self.accumulate = max(round(nbs / self.batch_size), 1)
        self.hyp['weight_decay'] *= self.batch_size * self.accumulate / nbs
        self.g0, self.g1, self.g2 = [], [], []
        for v in self.model.modules():
            if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter):
                self.g2.append(v.bias)
            if isinstance(v, nn.BatchNorm2d):
                self.g0.append(v.weight)
            elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter):
                self.g1.append(v.weight)
        if self.opt.adam:
            self.optimizer = Adam(self.g0, lr=self.hyp['lr0'], betas=(self.hyp['momentum'], 0.999))
        else:
            self.optimizer = SGD(self.g0, lr=self.hyp['lr0'], momentum=self.hyp['momentum'], nesterov=True)
        self.optimizer.add_param_group({'params': self.g1, 'weight_decay': self.hyp['weight_decay']})
        self.optimizer.add_param_group({'params': self.g2})
        if self.opt.linear_lr:
            lf = lambda x: (1 - x / (self.epochs - 1)) * (1.0 - self.hyp['lrf']) + self.hyp['lrf']
        else:
            lf = one_cycle(1, self.hyp['lrf'], self.epochs)
        self.scheduler = lr_scheduler.LambdaLR(self.optimizer, lr_lambda=lf)
        self.ema = ModelEMA(self.model) if RANK in [-1, 0] else None
        self.start_epoch, self.best_fitness = 0, 0.0
        if self.pretrained:
            if ckpt['optimizer'] is not None:
                self.optimizer.load_state_dict(ckpt['optimizer'])
                self.best_fitness = ckpt['best_fitness']
            if self.ema and ckpt.get('ema'):
                self.ema.ema.load_state_dict(ckpt['ema'].float().state_dict())
                self.ema.updates = ckpt['updates']
            self.start_epoch = ckpt['epoch'] + 1
            if self.resume:
                assert self.start_epoch > 0, f'{self.weights} training to {self.epochs} epochs is finished, nothing to resume.'
                ......

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

这个程序文件是用来训练一个YOLOv5模型的。它可以接受一些命令行参数,包括数据集配置文件、模型权重文件、输入图像尺寸等。训练过程中会使用指定的超参数进行模型训练,并保存训练过程中的权重文件和日志信息。程序文件中还包含了一些辅助函数和工具类,用于数据加载、模型构建、优化器设置等。训练过程中还会使用一些回调函数来记录训练过程中的指标和生成可视化图表。

5.3 ui.py


class ObjectDetector:
    def __init__(self, weights, data, device='', half=False, dnn=False):
        self.weights = weights
        self.data = data
        self.device = device
        self.half = half
        self.dnn = dnn
        self.model, self.stride, self.names, self.pt, self.jit, self.onnx, self.engine = self.load_model()

    def load_model(self):
        device = self.select_device(self.device)
        model = DetectMultiBackend(self.weights, device=device, dnn=self.dnn, data=self.data)
        stride, names, pt, jit, onnx, engine = model.stride, model.names, model.pt, model.jit, model.onnx, model.engine

        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

    def select_device(self, device):
        return torch.device(device)

    def detect(self, img, imgsz=(640, 640), conf_thres=0.45, iou_thres=0.05, max_det=1000, classes=None, agnostic_nms=False, augment=False):
        cal_detect = []

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

        im = letterbox(img, imgsz, self.stride, self.pt)[0]
        im = im.transpose((2, 0, 1))[::-1]
        im = np.ascontiguousarray(im)

        im = torch.from_numpy(im).to(device)
        im = im.half() if self.half else im.float()
        im /= 255
        if len(im.shape) == 3:
            im = im[None]

        pred = self.model(im, augment=augment)

        pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)

        for i, det in enumerate(pred):
            if len(det):
                det[:, :4] = scale_coords(im.shape[2:], det[:, :4], img.shape).round()

                for *xyxy, conf, cls in reversed(det):
                    c = int(cls)
                    label = f'{names[c]}'
                    lbl = names[int(cls)]

                    cal_detect.append([label, xyxy, float(conf)])
        return cal_detect

class ImageProcessor:
    def __init__(self, weights, data, device='', half=False, dnn=False):
        self.detector = ObjectDetector(weights, data, device, half, dnn)

    def process_image(self, image_path):
        image = cv2.imread(image_path)
        results = self.detector.detect(image)
        for i in results:
            box = i[1]
            p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
            color = [0, 0, 255]
            cv2.rectangle(image, p1, p2, color, thickness=3, lineType=cv2.LINE_AA)
            cv2.putText(image, str(i[0]), (int(box[0]), int(box[1]) - 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 2, color, 3)
        return image


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

这个程序文件是一个使用PyQt5和OpenCV实现的槟榔病虫害识别系统。程序的主要功能是通过选择图片文件或实时摄像头输入,使用预训练的YOLOv5模型进行目标检测和识别,并在界面上显示检测结果。

程序的主要结构如下:

  1. 导入所需的库和模块。
  2. 定义了一个load_model函数,用于加载模型和设置相关参数。
  3. 定义了一个run函数,用于运行模型进行目标检测和识别。
  4. 定义了一个det函数,用于处理图像并调用run函数进行识别。
  5. 定义了一个Thread_1类,继承自QThread,用于创建一个线程来执行识别操作。
  6. 定义了一个Ui_MainWindow类,用于创建程序的主窗口界面,并实现了相关的按钮点击事件和界面显示方法。
  7. __main__函数中,加载模型并创建应用程序对象,创建主窗口对象并显示。

整个程序的功能是通过界面上的按钮选择图片文件或实时摄像头输入,然后调用det函数进行识别,并将识别结果显示在界面上。

5.4 models\common.py

这个程序文件是YOLOv5的一个模块,包含了一些常用的模块和函数。文件中定义了一些卷积层(Conv)、深度卷积层(DWConv)、转置卷积层(DWConvTranspose2d)、Transformer层(TransformerLayer)、Transformer块(TransformerBlock)、瓶颈块(Bottleneck)、CSP瓶颈块(BottleneckCSP)、交叉卷积层(CrossConv)、C3模块(C3)、带有交叉卷积层的C3模块(C3x)、带有TransformerBlock的C3模块(C3TR)、带有SPP的C3模块(C3SPP)、带有GhostBottleneck的C3模块(C3Ghost)、空间金字塔池化层(SPP)、快速空间金字塔池化层(SPPF)、焦点层(Focus)、Ghost卷积层(GhostConv)、Ghost瓶颈块(GhostBottleneck)等。这些模块和函数用于构建YOLOv5的网络结构。

5.4 models\experimental.py

这个程序文件是YOLOv5的实验模块。它包含了一些实验性的网络层和模型。

文件中定义了以下几个类:

  1. Sum:实现了多个层的加权求和操作。可以选择是否对层应用权重。

  2. MixConv2d:实现了混合深度卷积操作。可以选择是否在每个卷积组中使用相同数量的通道。

  3. Ensemble:模型的集合,可以同时对多个模型进行推理。

文件还定义了一个辅助函数attempt_load,用于加载模型权重。可以加载单个模型或多个模型的集合。

总体来说,这个程序文件包含了一些实验性的网络层和模型,用于YOLOv5的实验和研究。

5.5 models\yolo.py



class Detect(nn.Module):
    # YOLOv5 Detect head for detection models
    stride = None  # strides computed during build
    dynamic = False  # force grid reconstruction
    export = False  # export mode

    def __init__(self, nc=80, anchors=(), ch=(), inplace=True):  # detection layer
        super().__init__()
        self.nc = nc  # number of classes
        self.no = nc + 5  # number of outputs per anchor
        self.nl = len(anchors)  # number of detection layers
        self.na = len(anchors[0]) // 2  # number of anchors
        self.grid = [torch.empty(0) for _ in range(self.nl)]  # init grid
        self.anchor_grid = [torch.empty(0) for _ in range(self.nl)]  # init anchor grid
        self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2))  # shape(nl,na,2)
        self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)  # output conv
        self.inplace = inplace  # use inplace ops (e.g. slice assignment)

    def forward(self, x):
        z = []  # inference output
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()

            if not self.training:  # inference
                if self.dynamic or self.grid[i].shape[2:4] != x[i].shape[2:4]:
                    self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i)

                if isinstance(self, Segment):  # (boxes + masks)
                    xy, wh, conf, mask = x[i].split((2, 2, self.nc + 1, self.no - self.nc - 5), 4)
                    xy = (xy.sigmoid() * 2 + self.grid[i]) * self.stride[i]  # xy
                    wh = (wh.sigmoid() * 2) ** 2 * self.anchor_grid[i]  # wh
                    y = torch.cat((xy, wh, conf.sigmoid(), mask), 4)
                else:  # Detect (boxes only)
                    xy, wh, conf = x[i].sigmoid().split((2, 2, self.nc + 1), 4)
                    xy = (xy * 2 + self.grid[i]) * self.stride[i]  # xy
                    wh = (wh * 2) ** 2 * self.anchor_grid[i]  # wh
                    y = torch.cat((xy, wh, conf), 4)
                z.append(y.view(bs, self.na * nx * ny, self.no))

        return x if self.training else (torch.cat(z, 1),) if self.export else (torch.cat(z, 1), x)

    def _make_grid(self, nx=20, ny=20, i=0, torch_1_10=check_version(torch.__version__, '1.10.0')):
        d = self.anchors[i].device
        t = self.anchors[i].dtype
        shape = 1, self.na, ny, nx, 2  # grid shape
        y, x = torch.arange(ny, device=d, dtype=t), torch.arange(nx, device=d, dtype=t)
        yv, xv = torch.meshgrid(y, x, indexing='ij') if torch_1_10 else torch.meshgrid(y, x)  # torch>=0.7 compatibility
        grid = torch.stack((xv, yv), 2).expand(shape) - 0.5  # add grid offset, i.e. y = 2.0 * x - 0.5
        anchor_grid = (self.anchors[i] * self.stride[i]).view((1, self.na, 1, 1, 2)).expand(shape)
        return grid, anchor_grid


class Segment(Detect):
    # YOLOv5 Segment head for segmentation models
    def __init__(self, nc=80, anchors=(), nm=32, npr=256, ch=(), inplace=True):
        super().__init__(nc, anchors, ch, inplace)
        self.nm = nm  # number of masks
        self.npr = npr  # number of protos
        self.no = 5 + nc + self.nm  # number of outputs per anchor
        self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)  # output conv
        self.proto = Proto(ch[0], self.npr, self.nm)  # protos
        self.detect = Detect.forward

    def forward(self, x):
        p = self.proto(x[0])
        x = self.detect(self, x)
        return (x, p) if self.training else (x[0], p) if self.export else (x[0], p, x[1])


class BaseModel(nn.Module):
    # YOLOv5 base model
    def forward(self, x, profile=False, visualize=False):
        return self._forward_once(x, profile, visualize)  # single-scale inference, train

    def _forward_once(self, x, profile=False, visualize=False):
        y, dt = [], []  # outputs
        for m in self.model:
            if m.f != -1:  # if not from previous layer
                x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f]  # from earlier layers
            if profile:
                self._profile_one_layer(m, x, dt)
            x = m(x)  # run
            y.append(x if m.i in self
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88

这是一个YOLOv5模型的程序文件。它包含了YOLOv5模型的各个组件和功能。

程序文件首先导入了必要的库和模块。然后定义了一些全局变量和常量。接下来定义了YOLOv5模型的一些特定模块,如DetectSegmentDetect是YOLOv5检测模型的头部,用于生成检测结果。Segment是YOLOv5分割模型的头部,用于生成分割结果。这些模块都继承自nn.Module类。

BaseModel是YOLOv5基础模型,包含了模型的前向传播方法和一些辅助方法。DetectionModel是YOLOv5检测模型,继承自BaseModel,定义了模型的构建和初始化方法。

整个程序文件的结构是模块的定义和初始化,然后是模型的前向传播方法和一些辅助方法的定义。最后是模型的构建和初始化方法的定义。

这个程序文件实现了YOLOv5模型的各个组件和功能,可以用于检测和分割任务。

6.系统整体结构

根据以上分析,该程序是一个用于槟榔叶病虫害识别系统的工程。它使用了YOLOv5作为基础模型,并在此基础上进行了改进和迁移。程序包含了多个文件,每个文件都有不同的功能,如模型定义、训练、导出、界面展示等。

下面是每个文件的功能概述:

文件路径功能概述
export.py导出YOLOv5模型为其他格式的工具文件
extra_modules.py包含一些额外的模块和网络层的定义
train.py用于训练YOLOv5模型的程序文件
ui.py使用PyQt5和OpenCV实现的槟榔病虫害识别系统的界面文件
models\common.py包含了一些常用的模块和函数,用于构建YOLOv5的网络结构
models\experimental.py包含了一些实验性的网络层和模型
models\tf.py使用TensorFlow和Keras实现的YOLOv5模型的文件
models\yolo.pyYOLOv5模型的组件和功能的定义
models_init_.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包含了下载数据集和模型权重的函数

7.DAMO-YOLO简介

DAMO-YOLO是一个兼顾速度与精度的目标检测框架,其效果超越了目前的一众YOLO系列方法,在实现SOTA的同时,保持了很高的推理速度。DAMO-YOLO是在YOLO框架基础上引入了一系列新技术,对整个检测框架进行了大幅的修改。具体包括:基于NAS搜索的新检测backbone结构,更深的neck结构,精简的head结构,以及引入蒸馏技术实现效果的进一步提升。模型之外,DAMO-YOLO还提供高效的训练策略以及便捷易用的部署工具,帮助您快速解决工业落地中的实际问题!

在这里插入图片描述

NAS backbone: MAE-NAS

Backbone的网络结构在目标检测中起着重要的作用。DarkNet在早期YOLO系列中一直占据着主导地位。最近,一些工作也开始探索其他对检测有效的网络结构,比如YOLOv6和YOLOv7。然而,这些网络仍然是人工设计的。随着神经网络结构搜索技术(NAS)的发展,出现了许多可以用于检测任务的NAS网络结构,并且相比于传统手动设计的网络,NAS网络结构可以达到能好的检测效果。因此,我们利用NAS技术搜索出合适的网络结构作为我们的DAMO-YOLO的backbone。这里我们采用的是阿里自研的MAE-NAS(开源链接)。MAE-NAS是一种启发式和免训练的NAS搜索方法,可以用于快速大范围搜索各种不同规模的骨干网络结构。

MAE-NAS利用信息论理论从熵的角度去评测初始化网络,评测过程不需要任何训练过程,从而解决了之前NAS搜索方法需要训练再评测的弊端。实现短时间内大范围的网络搜索,降低搜索成本的同时,也提高了可以找到的潜在更优网络结构的可能性。特别值得注意的是,在MAE-NAS搜索中,我们使用K1K3作为基本搜索模块。同时,受YOLOv6启发,我们直接使用GPU推理延迟Latency,而不是 Flops,作为目标预算。搜索后,我们将空间金字塔池化和焦点模块应用到最后的骨干。

下表1中列出了不同的主干的性能对比结果。可以看到MAE-NAS骨干网络的效果要明显优于DarkNet网络结构。

在这里插入图片描述
​表1 MAE-NAS网络结构和DarkNet对比

Large Neck: RepGFPN

在FPN(Feature Pyramid Network)中,多尺度特征融合旨在对从backbone不同stage输出的特征进行聚合,从而增强输出特征的表达能力,提升模型性能。传统的FPN引入top-to-down的路径来融合多尺度特征。考虑到单向信息流的限制,PAFPN增加了一个额外的自底向上的路径聚合网络,然而增加了计算成本。为了降低计算量,YOLO系列检测网络选择带有CSPNet的PAFPN来融合来自backbone输出的多尺度特征。

我们在ICLR2022的工作GiraffeDet中提出了新颖的Light-Backbone Heavy-Neck结构并达到了SOTA性能,原因在于给出的neck结构GFPN(Generalized FPN)能够充分交换高级语义信息和低级空间信息。在GFPN中,多尺度特征融合发生在前一层和当前层的不同尺度特征中,此外,log_2(n)的跨层连接提供了更有效的信息传输,可以扩展到更深的网络。

因此,我们尝试将GFPN引入到DAMO-YOLO中,相比于PANet,我们取得了更高的精度,这是在预期之中的。然而与之同时,GFPN带来了模型推理时延的增加,使得精度/时延的权衡并未取得较大的优势。通过对原始GFPN结构的分析,我们将原因归结为以下几个方面:(1)不同尺度特征共享相同通道数,导致难以给出一个最优通道数来保证高层低分辨率特征和低层高分辨率特征具有同样丰富的表达能力;(2)GFPN采用Queen-Fusion强化特征之间的融合,而Queen-Fusion包含大量的上采样和下采样操作来实现不同尺度特征的融合,极大影响推理速度;(3)GFPN中使用的3x3卷积进行跨尺度特征融合的效率不高,不能满足轻量级计算量的需求,需要进一步优化。

在这里插入图片描述

经过上述分析后,我们在GFPN的基础上提出了新的Efficient-RepGFPN来满足实时目标检测中neck的设计,主要包括以下改进:(1)不同尺度特征使用不同的通道数,从而在轻量级计算量约束下,灵活控制高层特征和低层特征的表达能力;(2)删除了Queen-Fusion中的额外的上采样操作,在精度下降较少的情况下,较大降低模型推理时延;(3)将原始基于卷积的特征融合改进为CSPNet连接,同时引入重参数化思想和ELAN连接,在不增加更多计算量的同时,提升模型的精度。最终的Efficient-RepGFPN网络结构如上图2所示。

灵活控制不同尺度特征图的通道数,我们能够取得相比于所有尺度特征图共享相同通道数更高的精度,表明灵活控制高层特征和低层特征的表达能力能够带来更多收益。同时,通过控制模型在同一计算量级别,我们也做了Efficient-RepGFPN中depth/width的权衡对比,当depth=3,width=(96,192,384)时,模型取得了最高精度。

当不增加额外的上采样及下采样算子时,neck结构为PANet连接。我们尝试了只增加上采样算子和只增加下采样算子以及完整的Queen-Fusion结构,模型精度均取得了提升。然而,只增加上采样算子带来了0.6ms的推理时间增加,精度仅提升0.3,远远低于只增加额外下采样算子的精度/时延收益,因此在最终设计上我们摒弃了额外的上采样算子。

8.GFPN的改进YOLOv5

RepGFPN

在FPN(Feature Pyramid Network)中,多尺度特征融合旨在对从backbone不同stage输出的特征进行聚合,从而增强输出特征的表达能力,提升模型性能。传统的FPN引入top-to-down的路径来融合多尺度特征。考虑到单向信息流的限制,PAFPN增加了一个额外的自底向上的路径聚合网络,然而增加了计算成本。为了降低计算量,YOLO系列检测网络选择带有CSPNet的PAFPN来融合来自backbone输出的多尺度特征。
在ICLR2022的工作GiraffeDet中提出了新颖的Light-Backbone Heavy-Neck结构并达到了SOTA性能,原因在于给出的neck结构GFPN(Generalized FPN)能够充分交换高级语义信息和低级空间信息。在GFPN中,多尺度特征融合发生在前一层和当前层的不同尺度特征中,此外,log_2(n)的跨层连接提供了更有效的信息传输,可以扩展到更深的网络。
因此,作者尝试将GFPN引入到DAMO-YOLO中,相比于PANet,我们取得了更高的精度,这是在预期之中的。然而与之同时,GFPN带来了模型推理时延的增加,使得精度/时延的权衡并未取得较大的优势。通过对原始GFPN结构的分析,我们将原因归结为以下几个方面:(1)不同尺度特征共享相同通道数,导致难以给出一个最优通道数来保证高层低分辨率特征和低层高分辨率特征具有同样丰富的表达能力;(2)GFPN采用Queen-Fusion强化特征之间的融合,而Queen-Fusion包含大量的上采样和下采样操作来实现不同尺度特征的融合,极大影响推理速度;(3)GFPN中使用的3x3卷积进行跨尺度特征融合的效率不高,不能满足轻量级计算量的需求,需要进一步优化。

yolov5_GFPN.yaml
# YOLOv5 
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/770325
推荐阅读
相关标签