1.研究背景与意义
项目参考AAAI Association for the Advancement of Artificial Intelligence
研究背景与意义
随着科技的不断发展,计算机视觉技术在各个领域中得到了广泛的应用。其中,物体检测是计算机视觉领域中的一个重要研究方向。物体检测的目标是在图像或视频中准确地识别和定位出物体的位置。这项技术在许多领域中都有着重要的应用,如智能交通系统、安防监控、无人驾驶等。
近年来,YOLO(You Only Look Once)系列算法成为了物体检测领域的热门研究方向。YOLO算法通过将物体检测任务转化为一个回归问题,将物体的位置和类别同时预测出来,从而实现了实时物体检测的能力。然而,YOLO算法在处理小尺寸物体时存在一定的困难。尤其是在矿物尺寸图像分析系统中,对于小尺寸的矿物颗粒的检测和定位,传统的YOLO算法往往无法达到较高的准确性和鲁棒性。
因此,本研究旨在改进YOLOv8算法,提出一种融合位置感知循环卷积(ParC)的改进方法,以提高矿物尺寸图像分析系统中对小尺寸矿物颗粒的检测和定位准确性。
首先,矿物尺寸图像分析系统在矿石矿物分析中起着重要的作用。通过对矿石中的矿物颗粒进行分析,可以获得矿石的成分和性质信息,从而指导矿石的选矿和冶炼过程。然而,由于矿石中的矿物颗粒尺寸较小,传统的物体检测算法往往难以准确地检测和定位这些小尺寸的矿物颗粒。因此,改进矿物尺寸图像分析系统中的物体检测算法对于提高矿石矿物分析的准确性和效率具有重要意义。
其次,YOLOv8算法作为YOLO系列算法的最新版本,已经在物体检测领域取得了显著的成果。然而,YOLOv8算法在处理小尺寸物体时仍然存在一定的问题。传统的YOLOv8算法主要通过多尺度特征融合和上采样操作来提高小尺寸物体的检测准确性,但这种方法往往会导致较大的计算开销和较高的误检率。因此,需要进一步改进YOLOv8算法,提高其对小尺寸物体的检测和定位能力。
最后,本研究提出了一种融合位置感知循环卷积(ParC)的改进方法,以提高矿物尺寸图像分析系统中对小尺寸矿物颗粒的检测和定位准确性。ParC模块通过引入位置感知循环卷积操作,能够有效地捕捉到小尺寸物体的位置信息,并将其融合到物体检测过程中。通过在YOLOv8算法中引入ParC模块,可以显著提高对小尺寸矿物颗粒的检测和定位准确性,同时减少计算开销和误检率。
综上所述,本研究的改进YOLOv8算法融合位置感知循环卷积的矿物尺寸图像分析系统具有重要的研究意义和应用价值。通过提高对小尺寸矿物颗粒的检测和定位准确性,可以提高矿石矿物分析的准确性和效率,为矿石选矿和冶炼过程提供有力的支持。同时,该研究对于改进物体检测算法在其他领域中的应用也具有一定的借鉴意义,有助于推动计算机视觉技术的发展和应用。
2.图片演示
3.视频演示
【改进YOLOv8】矿物尺寸图像分析系统:融合位置感知循环卷积(ParC)改进YOLOv8_哔哩哔哩_bilibili
4.数据集的采集&标注和整理
图片的收集
首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集KSDatasets。
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 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
5.核心代码讲解
5.1 predict.py
from ultralytics.engine.predictor import BasePredictor
from ultralytics.engine.results import Results
from ultralytics.utils import ops
class DetectionPredictor(BasePredictor):
def postprocess(self, preds, img, orig_imgs):
preds = ops.non_max_suppression(preds,
self.args.conf,
self.args.iou,
agnostic=self.args.agnostic_nms,
max_det=self.args.max_det,
classes=self.args.classes)
if not isinstance(orig_imgs, list):
orig_imgs = ops.convert_torch2numpy_batch(orig_imgs)
results = []
for i, pred in enumerate(preds):
orig_img = orig_imgs[i]
pred[:, :4] = ops.scale_boxes(img.shape[2:], pred[:, :4], orig_img.shape)
img_path = self.batch[0][i]
results.append(Results(orig_img, path=img_path, names=self.model.names, boxes=pred))
return results
这段代码定义了一个名为DetectionPredictor
的类,继承自BasePredictor
类。该类用于基于检测模型进行预测。其中,postprocess
方法用于对预测结果进行后处理,并返回一个Results
对象的列表。在postprocess
方法中,首先使用ops.non_max_suppression
函数对预测结果进行非最大抑制处理,然后将预测框的坐标进行缩放,最后将处理后的结果存储在Results
对象中,并添加到结果列表中。
该程序文件名为predict.py,是一个用于预测的程序文件。该文件是Ultralytics YOLO项目的一部分,使用AGPL-3.0许可证。
该文件定义了一个名为DetectionPredictor的类,该类继承自BasePredictor类,用于基于检测模型进行预测。
该文件还定义了一个postprocess方法,用于对预测结果进行后处理,并返回一个Results对象的列表。
在postprocess方法中,首先对预测结果进行非最大抑制处理,根据设定的置信度阈值和IOU阈值进行筛选。然后,将预测框的坐标进行缩放,以适应原始图像的尺寸。最后,将处理后的结果封装成Results对象,并添加到结果列表中。
该文件还包含一个示例代码,展示了如何使用DetectionPredictor类进行预测。首先导入相关的模块和资源,然后创建一个DetectionPredictor对象,并调用predict_cli方法进行预测。
总之,该文件定义了一个用于预测的类和相关的方法,用于对检测模型的预测结果进行后处理,并提供了一个示例代码。
5.2 train.py
# Ultralytics YOLO 🚀, AGPL-3.0 license
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
类包含了一些用于构建数据集、构建数据加载器、预处理数据等方法。它还定义了一些用于训练过程中的操作,如设置模型属性、获取模型、获取验证器等。
在main
函数中,首先定义了一些参数,包括模型文件路径、数据文件路径和训练轮数。然后创建了一个DetectionTrainer
对象,并调用其train
方法开始训练过程。
总的来说,这个程序文件是一个用于训练目标检测模型的脚本,使用了Ultralytics YOLO库提供的功能。
5.3 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,是一个用于深度学习的神经网络模型的实现。该文件包含了多个模块和函数。
模块和函数的概述如下:
-
get_gpu_states(fwd_gpu_devices)
: 获取GPU设备的状态。 -
get_gpu_device(*args)
: 获取输入参数中的GPU设备。 -
set_device_states(fwd_cpu_state, devices, states)
: 设置设备的状态。 -
detach_and_grad(inputs)
: 对输入进行分离和梯度计算。 -
get_cpu_and_gpu_states(gpu_devices)
: 获取CPU和GPU设备的状态。 -
ReverseFunction
: 反向函数,用于实现反向传播。 -
Fusion
: 融合模块,用于特征融合。 -
Level
: 层级模块,用于特征提取。 -
SubNet
: 子网络模块,由多个层级模块组成。 -
RevCol
: 反向残差网络模型,由多个子网络模块组成。
以上是对程序文件的概述,该文件实现了一个反向残差网络模型。
5.4 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模型的PyTorch模块。Swin Transformer是一种基于窗口的自注意力模型,用于图像分类任务。
该文件包含以下几个类和函数:
- Mlp类:多层感知机模块,用于处理输入特征。
- window_partition函数:将输入特征划分为窗口。
- window_reverse函数:将划分的窗口重新合并为特征。
- WindowAttention类:基于窗口的多头自注意力模块,支持相对位置偏置。
- SwinTransformerBlock类:Swin Transformer的基本模块,包含窗口注意力和多层感知机。
- PatchMerging类:用于将特征图像素合并的模块。
- BasicLayer类:Swin Transformer的一个阶段,包含多个SwinTransformerBlock模块和PatchMerging模块。
该文件还定义了一些辅助函数和变量,用于处理输入特征的形状和相对位置偏置。
总体而言,该文件实现了Swin Transformer模型的核心组件,包括窗口注意力和多层感知机模块,以及用于处理输入特征的划分和合并操作。
5.5 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,用于图像分类任务。模型包含了多个不同深度的Block模块,每个Block模块包含了卷积层、批归一化层、激活函数等。模型还包含了一个stem模块用于对输入图像进行预处理。模型可以根据输入的参数进行不同深度和步长的配置。模型还提供了一些预训练的模型,可以加载预训练权重。在程序的最后,对模型进行了简单的测试。
5.6 extra_modules\head.py
class Detect_DyHead(nn.Module):
"""YOLOv8 Detect head with DyHead for detection models."""
dynamic = False # force grid reconstruction
export = False # export mode
shape = None
anchors = torch.empty(0) # init
strides = torch.empty(0) # init
def __init__(self, nc=80, hidc=256, block_num=2, ch=()): # detection layer
super().__init__()
self.nc = nc # number of classes
self.nl = len(ch) # number of detection layers
self.reg_max = 16 # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
self.no = nc + self.reg_max * 4 # number of outputs per anchor
self.stride = torch.zeros(self.nl) # strides computed during build
c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], self.nc) # channels
self.conv = nn.ModuleList(nn.Sequential(Conv(x, hidc, 1)) for x in ch)
self.dyhead = nn.Sequential(*[DyHeadBlock(hidc) for i in range(block_num)])
self.cv2 = nn.ModuleList(
nn.Sequential(Conv(hidc, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for _ in ch)
self.cv3 = nn.ModuleList(nn.Sequential(Conv(hidc, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for _ in ch)
self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()
def forward(self, x):
"""Concatenates and returns predicted bounding boxes and class probabilities."""
for i in range(self.nl):
x[i] = self.conv[i](x[i])
x = self.dyhead(x)
shape = x[0].shape # BCHW
for i in range(self.nl):
x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
if self.training:
return x
elif self.dynamic or self.shape != shape:
self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
self.shape = shape
x_cat = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2)
if self.export and self.format in ('saved_model', 'pb', 'tflite', 'edgetpu', 'tfjs'): # avoid TF FlexSplitV ops
box = x_cat[:, :self.reg_max * 4]
cls = x_cat[:, self.reg_max * 4:]
else:
box, cls = x_cat.split((self.reg_max * 4, self.nc), 1)
dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides
y = torch.cat((dbox, cls.sigmoid()), 1)
return y if self.export else (y, x)
def bias_init(self):
"""Initialize Detect() biases, WARNING: requires stride availability."""
m = self # self.model[-1] # Detect() module
# cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1
# ncf = math.log(0.6 / (m.nc - 0.999999)) if cf is None else torch.log(cf / cf.sum()) # nominal class frequency
for a, b, s in zip(m.cv2, m.cv3, m.stride): # from
a[-1].bias.data[:] = 1.0 # box
b[-1].bias.data[:m.nc] = math.log(5 / m.nc / (640 / s) ** 2) # cls (.01 objects, 80 classes, 640 img)
class Detect_DyHeadWithDCNV3(Detect_DyHead):
def __init__(self, nc=80, hidc=256, block_num=2, ch=()):
super().__init__(nc, hidc, block_num, ch)
self.dyhead = nn.Sequential(*[DyHeadBlockWithDCNV3(hidc) for i in range(block_num)])
class Detect_AFPN_P345(nn.Module):
"""YOLOv8 Detect head with AFPN for detection models."""
dynamic = False # force grid reconstruction
export = False # export mode
shape = None
anchors = torch.empty(0) # init
strides = torch.empty(0) # init
def __init__(self, nc=80, hidc=256, ch=()): # detection layer
super().__init__()
self.nc = nc # number of classes
self.nl = len(ch) # number of detection layers
self.reg_max = 16 # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
self.no = nc + self.reg_max * 4 # number of outputs per anchor
self.stride = torch.zeros(self.nl) # strides computed during build
c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], self.nc) # channels
self.afpn = AFPN_P345(ch, hidc)
self.cv2 = nn.ModuleList(
nn.Sequential(Conv(hidc, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for _ in ch)
self.cv3 = nn.ModuleList(nn.Sequential(Conv(hidc, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3
这个程序文件是一个用于目标检测模型的头部网络结构。它包含了多个不同的头部网络结构,用于不同的目标检测模型。
文件中定义了以下类:
Detect_DyHead
: 使用DyHead的YOLOv8检测头部。Detect_DyHeadWithDCNV3
: 使用带有DCNV3的DyHead的YOLOv8检测头部。Detect_AFPN_P345
: 使用AFPN的YOLOv8检测头部。Detect_AFPN_P345_Custom
: 使用自定义AFPN的YOLOv8检测头部。Detect_AFPN_P2345
: 使用AFPN的YOLOv8检测头部。Detect_AFPN_P2345_Custom
: 使用自定义AFPN的YOLOv8检测头部。Detect_Efficient
: 使用Efficient头部的YOLOv8检测头部。
每个类都有一个forward
方法,用于前向传播计算预测的边界框和类别概率。这些类还定义了一些其他方法,用于初始化参数和计算偏置。
这个程序文件还导入了一些其他模块和函数,用于构建网络结构和计算边界框。
6.系统整体结构
根据以上分析,该程序是一个用于视觉项目中的矿物尺寸图像分析系统的工程。该工程包含了多个文件,每个文件有不同的功能,用于实现不同的模块和功能。
下面是每个文件的功能的整理:
文件路径 | 功能 |
---|---|
predict.py | 用于预测的程序文件 |
train.py | 用于训练目标检测模型的程序文件 |
backbone/lsknet.py | 实现了LSKNet神经网络模型 |
backbone/repvit.py | 实现了RepVGG和RepVGG-B模型 |
backbone/revcol.py | 实现了反向残差网络模型 |
backbone/SwinTransformer.py | 实现了Swin Transformer模型 |
backbone/VanillaNet.py | 实现了VanillaNet模型 |
extra_modules/head.py | 实现了目标检测模型的头部网络结构 |
extra_modules/kernel_warehouse.py | 存储和管理模型的卷积核 |
extra_modules/orepa.py | 实现了OrePA模块 |
extra_modules/rep_block.py | 实现了RepBlock模块 |
extra_modules/RFAConv.py | 实现了RFAConv模块 |
extra_modules/init.py | 初始化extra_modules模块 |
extra_modules/ops_dcnv3/setup.py | 安装DCNv3操作的脚本 |
extra_modules/ops_dcnv3/test.py | 测试DCNv3操作的脚本 |
extra_modules/ops_dcnv3/functions/dcnv3_func.py | 实现了DCNv3操作的函数 |
extra_modules/ops_dcnv3/functions/init.py | 初始化DCNv3函数模块 |
extra_modules/ops_dcnv3/modules/dcnv3.py | 实现了DCNv3操作的模块 |
extra_modules/ops_dcnv3/modules/init.py | 初始化DCNv3模块 |
models/common.py | 包含了一些通用的模型定义和函数 |
models/experimental.py | 包含了一些实验性的模型定义和函数 |
models/tf.py | 包含了一些TensorFlow模型定义和函数 |
models/yolo.py | 包含了YOLO模型的定义和函数 |
models/init.py | 初始化models模块 |
utils/activations.py | 包含了一些激活函数的定义 |
utils/augmentations.py | 包含了一些数据增强的函数 |
utils/autoanchor.py | 包含了自动锚框生成的函数 |
utils/autobatch.py | 包含了自动批处理的函数 |
utils/callbacks.py | 包含了一些回调函数的定义 |
utils/datasets.py | 包含了数据集的定义和函数 |
utils/downloads.py | 包含了下载数据集的函数 |
utils/general.py | 包含了一些通用的函数 |
utils/loss.py | 包含了一些损失函数的定义 |
utils/metrics.py | 包含了一些评估指标的定义 |
utils/plots.py | 包含了一些绘图函数的定义 |
utils/torch_utils.py | 包含了一些PyTorch工具函数的定义 |
utils/init.py | 初始化utils模块 |
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 | 初始化loggers模块 |
utils/loggers/wandb/log_dataset.py | WandB日志记录器的数据集日志记录 |
utils/loggers/wandb/sweep.py | WandB日志记录器的超参数搜索 |
utils/loggers/wandb/wandb_utils.py | WandB日志记录器的工具函数 |
utils/loggers/wandb/init.py | 初始化WandB日志记录器模块 |
以上是对每个文件功能的整理。这些文件一起构成了该视觉项目的工程,实现了不同的模块和功能,包括模型定义、训练、预测、数据处理、工具函数等。
7.YOLOv8简介
目前YOLO系列的SOTA模型是ultralytics公司于2023年发布的YOLOv8.按照模型宽度和深度不同分为YOLOv8n、YOLOv8s、YOLOv8m、YOLOv81、YOLOv8x五个版本。本文改进的是 YOLOv8n模型。
YOLOv8的 Backbone采用CSPDarknet结构,它是 Darknet 的一种改进,引入CSP改善网络结构。CSPDarknet把特征图分为两部分,一部分进行卷积操作,另一部分进行跳跃连接,在保持网络深度的同时减少参数量和计算量,提高网络效率。Neck 部分采用特征金字塔PANet[17],通过自顶向下路径结合和自底向上特征传播进行多尺度融合。损失函数采用了CIloU[18]。YOLOv8的网络结构如图所示。
8.ParC融合位置感知循环卷积简介
ParC:Position aware circular convolution
Position aware circular convolution
针对于全局信息的提取作者提出了Position aware circular convolution(也称作Global Circular Convolution)。图中左右实际是对于该操作水平竖直两方向的对称,理解时只看左边即可。对于维度为CHW的输入,作者先将维度为CB1的Position Embedding通过双线性插值函数F调整到适合input的维度CH1(以适应不同特征大小输入),并且将PE水平复制扩展到CHW维度与输入特征相加。这里作者将PE直接设置成为了可学习的参数。
接下来参考该博客将加入PE的特征图竖直方向堆叠,并且同样以插值的方式得到了适应输入维度的CH1大小的卷积核,进行卷积操作。对于这一步卷积,作者将之称为循环卷积,并给出了一个卷积示意图。
但个人感觉实际上这个示意图只是为了说明为什么叫循环卷积,对于具体的计算细节还是根据公式理解更好。
进一步,作者给出了这一步的伪代码来便于读者对这一卷积的理解:y=F.conv2D(torch.cat(xp,xp,dim=2),kV),实际上就是将xp堆叠之后使用了一个“条形(或柱形)”卷积核进行简单的卷积操作。(但这样会导致多一次重复卷积,因此在堆叠示意图中只取了前2*H-1行)
可以看到在示意图中特征维度变化如下:C*(2H-1)W —CH1—>CH*W,作者特意带上了通道数,并且并没有出现通道数的改变,那么这里所进行的卷积应该是depth wise卷积,通过对文章后续以及论文源码的阅读可以得知这一步进行的就是DW卷积。(we introduce group convolution and point wise convolution into these modules, which decreases number of parameters without hurting performance.)
由groups = channel可知使用的是DW卷积
通过上面就完成了一次竖直方向的全局信息交流,同样只要在水平方向进行同样的操作即可做到水平方向的全局信息交流。
ParC block
通过ParC成功解决了全局信息提取的问题,接下来就是针对2)3)两点进行改进。首先是Meta-Former模块,Meta-Former由Token Mixer和Channel Mixer构成,ParC首先满足了Token Mixer的全局信息提取的要求,并且相较于Attention在计算成本上更低。
这里①中的PWC即point wise conv,进一步验证了我们前面对于深度可分离卷积的想法,而GCC-H/V即是前面所说的ParC-H/V。
①构建了Meta-Former中的Token mixer模块,那么最后剩下的问题就是3),替换掉Attention模块之后模型不再data driven。为了解决这一点作者给出了一个channel wise attention,先将特征图(x,CHW)进行global average(a,C11)并输入一个MLP生成一个channel wise的权重(w,C11),再将权重与特征图在通道方向相乘得到输出(output = wx,CHW)。
ParC net
对于ParC net 的搭建,作者直接基于MobileViT,采用了分叉结构(c)完成了网络的搭建。
具体而言作者保留了MobileViT中浅层具有局部感受野的MobileNetV2结构,而将网络深层的ViT block替换成了ParC block,使网络变成了一个pure ConvNet。
9.训练结果可视化分析
评价指标
Epoch:训练纪元数。
训练损失:训练期间的框损失 ( train/box_loss)、对象损失 ( train/obj_loss) 和类损失 ( train/cls_loss)。
指标:精度 ( metrics/precision)、召回率 ( metrics/recall)、IoU 阈值 0.5 时的平均精度 (mAP) ( metrics/mAP_0.5),以及 IoU 阈值 0.5 到 0.95 时的 mAP ( metrics/mAP_0.5:0.95)。
验证损失:验证期间的框损失 ( val/box_loss)、对象损失 ( val/obj_loss) 和类损失 ( val/cls_loss)。
学习率:不同层或阶段的学习率 ( x/lr0, x/lr1, x/lr2)。
训练结果可视化
为了进行详细分析,我将可视化这些时期的指标和损失,以了解训练动态。重点将放在训练和验证损失的趋势和模式、精确度和召回率的提高、平均精确度的行为以及学习率的变化上。让我们首先绘制这些数据点。
import matplotlib.pyplot as plt
# Setting up the figure
plt.figure(figsize=(15, 10))
# Plotting training losses
plt.subplot(2, 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 Over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
# Plotting validation losses
plt.subplot(2, 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 Over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
# Plotting metrics: Precision, Recall, mAP
plt.subplot(2, 2, 3)
plt.plot(data['epoch'], data['metrics/precision'], label='Precision')
plt.plot(data['epoch'], data['metrics/recall'], label='Recall')
plt.plot(data['epoch'], data['metrics/mAP_0.5'], label='mAP@0.5')
plt.plot(data['epoch'], data['metrics/mAP_0.5:0.95'], label='mAP@0.5:0.95')
plt.title('Precision, Recall, and mAP Over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Metric Value')
plt.legend()
# Plotting learning rates
plt.subplot(2, 2, 4)
plt.plot(data['epoch'], data['x/lr0'], label='LR0')
plt.plot(data['epoch'], data['x/lr1'], label='LR1')
plt.plot(data['epoch'], data['x/lr2'], label='LR2')
plt.title('Learning Rates Over Epochs')
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.legend()
# Displaying the plots
plt.tight_layout()
plt.show()
可视化提供了对“矿物尺寸图像分析系统中改进的 YOLOv8”历次迭代的性能和学习动态的深入了解:
训练损失(左上):
Box Loss:显示稳定下降,表明框预测准确性提高。
对象丢失:也减少,反映出随着时间的推移更好的对象检测。
类别损失:逐渐减少,表明分类准确性提高。
验证损失(右上):
与训练损失类似,所有三种验证损失(框、对象、类)均呈现下降趋势。这是一个积极的信号,表明模型泛化良好并且没有过度拟合。
指标(左下):
Precision:显着增加,意味着预测阳性中真阳性的比例正在提高。
召回率:也增加,表明模型在识别所有相关案例方面越来越好。
mAP@0.5 和 mAP@0.5:0.95:两个指标都随着时间的推移而改进。不同 IoU(并集交集)阈值下的 mAP(平均平均精度)提供了模型准确性和可靠性的全面视图。
学习率(右下):
给出了不同层或阶段(LR0、LR1、LR2)的学习率。这些速率通常会随着时间的推移而降低,遵循学习速率计划或自适应学习速率方法等策略来优化训练。
从这些可视化中,我们可以得出结论,随着时间的推移,该模型在各种指标和损失函数上的预测能力不断提高。损失的减少以及精确度、召回率和 mAP 分数的增加表明模型的有效学习和适应。学习率调整在此过程中发挥着至关重要的作用。
10.系统整合
下图完整源码&数据集&环境部署视频教程&自定义UI界面
参考博客《【改进YOLOv8】矿物尺寸图像分析系统:融合位置感知循环卷积(ParC)改进YOLOv8》
11.参考文献
[1]佚名.Anisotropic nanowire growth via a self-confined amorphous template process: A reconsideration on the role of amorphous calcium carbonate[J].纳米研究(英文版).2016,9(5).DOI:10.1007/s12274-016-1029-6 .
[2]Ghazlan, Abdallah,Tuan Ngo,Tan, Ping,等.Inspiration from Nature’s body armours - A review of biological and bioinspired composites[J].Composites, Part B. Engineering.2021,205(Jan.15).108513.1-108513.22.DOI:10.1016/j.compositesb.2020.108513 .
[3]佚名.Biomimetic Mineralization to Fabricate Superhydrophilic and Underwater Superoleophobic Filter Mesh for Oil–Water Separations[J].Industrial & Engineering Chemistry Research.2020,59(13).6226-6235.DOI:10.1021/acs.iecr.0c00739 .
[4]Dai, Jiangdong,Wang, Lulu,Wang, Yi,等.Robust Nacrelike Graphene Oxide-Calcium Carbonate Hybrid Mesh with Underwater Superoleophobic Property for Highly Efficient Oil/Water Separation[J]…2020,12(4).4482-4493.DOI:10.1021/acsami.9b18664 .
[5]佚名.Nacre-Inspired Mineralized Films with High Transparency and Mechanically Robust Underwater Superoleophobicity[J].Advanced Materials.2020,32(11).1907413.1-1907413.7.DOI:10.1002/adma.201907413 .
[6]Rivera-Perez Crisalejandra,Flores-Sánchez Iliana Alejandra,Ojeda Ramírez de Areyano Josafat Jehu,等.A shell matrix protein of Pinctada mazatlanica produces nacre platelets in vitro[J].Scientific reports.2020,10(1).DOI:10.1038/s41598-020-77320-7 .
[7]Yan,Yang.A basic protein, N25, from a mollusk modifies calcium carbonate morphology and shell biomineralization[J].The Journal of biological chemistry.2019,294(21).8371-8383.DOI:10.1074/jbc.RA118.007338 .
[8]Le Ferrand, Hortense,Bouville, Florian,Studart, Andre R..Design of textured multi-layered structures via magnetically assisted slip casting[J].Soft matter.2019,15(19).3886-3896.DOI:10.1039/c9sm00390h .
[9]Chuanjin Huang,Jingsong Peng,Sijie Wan,等.Ultra‐Tough Inverse Artificial Nacre Based on Epoxy‐Graphene by Freeze‐Casting[J].Angewandte Chemie International Edition.2019,58(23).7636-7640.DOI:10.1002/anie.201902410 .
[10]Ianiro, Alessandro,Wu, Hanglong,van Rijt, Mark M. J.,等.Liquid-liquid phase separation during amphiphilic self-assembly[J].Nature Chemistry.2019,11(4).320-328.DOI:10.1038/s41557-019-0210-4 .