1.研究背景与意义
项目参考AAAI Association for the Advancement of Artificial Intelligence
研究背景与意义:
随着电子商务的快速发展,快递业务的规模和复杂性也在不断增加。为了满足人们对快速、准确、高效的快递服务的需求,快递包裹分拣成为了一个关键的环节。传统的快递包裹分拣方式主要依靠人工操作,但是由于人工操作的局限性,如疲劳、错误率高等问题,使得分拣效率和准确性无法满足快速发展的需求。
因此,引入自动化技术来提高快递包裹分拣的效率和准确性成为了一个研究热点。目前,基于计算机视觉的物体检测和识别技术在快递包裹分拣中得到了广泛应用。其中,YOLO(You Only Look Once)是一种非常流行的物体检测算法,其具有实时性和准确性的优势,被广泛应用于快递包裹分拣领域。
然而,传统的YOLO算法在处理快递包裹分拣任务时存在一些问题。首先,由于快递包裹的形状和尺寸多样,传统的YOLO算法在包裹分割和信息提取方面存在一定的困难。其次,快递包裹在分拣过程中往往会出现遮挡、变形等问题,这也给物体检测和识别带来了一定的挑战。因此,如何改进YOLO算法,提高其在快递包裹分拣中的性能,成为了一个亟待解决的问题。
为了解决上述问题,本研究提出了一种引入ODConv的改进YOLO快递包裹分拣分割信息提取系统。ODConv是一种基于目标检测的卷积神经网络,其能够有效地提取包裹的分割信息,进而提高快递包裹分拣的准确性和效率。通过引入ODConv,我们可以在YOLO算法的基础上增加一个分割模块,用于对快递包裹进行分割,从而更好地识别和提取包裹的相关信息。
本研究的意义主要体现在以下几个方面:首先,通过引入ODConv,可以提高快递包裹分拣的准确性和效率,减少人工操作的依赖,从而提高整个快递分拣系统的自动化程度。其次,本研究的方法可以应用于其他领域的物体检测和识别任务,如工业自动化、智能交通等,具有一定的推广价值。最后,本研究对于深入理解和探索计算机视觉技术在快递包裹分拣中的应用具有一定的理论和实践意义。
综上所述,引入ODConv的改进YOLO快递包裹分拣分割信息提取系统在提高快递包裹分拣效率和准确性方面具有重要的研究意义和应用价值。通过本研究的探索和实践,可以为快递行业的发展和提升提供有力的支持和参考。
2.图片演示
3.视频演示
引入ODConv的改进YOLO快递包裹分拣分割信息提取系统_哔哩哔哩_bilibili
4.数据集的采集&标注和整理
图片的收集
首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集KDDatasets。
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 export.py
class YOLOv5Exporter:
def __init__(self, weights, include):
self.weights = weights
self.include = include
def export(self):
if 'torchscript' in self.include:
self.export_torchscript()
if 'onnx' in self.include:
self.export_onnx()
if 'openvino' in self.include:
self.export_openvino()
if 'engine' in self.include:
self.export_tensorrt()
if 'coreml' in self.include:
self.export_coreml()
if 'saved_model' in self.include:
self.export_saved_model()
if 'pb' in self.include:
self.export_graphdef()
if 'tflite' in self.include:
self.export_tflite()
if 'edgetpu' in self.include:
self.export_edgetpu()
if 'tfjs' in self.include:
self.export_tfjs()
if 'paddle' in self.include:
self.export_paddle()
def export_torchscript(self):
# 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
def export_onnx(self):
# YOLOv5 ONNX export
check_requirements('onnx>=1.12.0')
import onnx
LOGGER.info(f'\n{prefix} starting export with onnx {onnx.__version__}...')
f = str(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, # WARNING: DNN inference with torch>=1.12 may require do_constant_folding=False
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 = onnxsim.simplify(model_onnx)
assert check, 'assert check failed'
onnx.save(model_onnx, f)
except Exception as e:
LOGGER.info(f'{prefix} simplifier failure: {e}')
return f, model_onnx
def export_openvino(self):
# YOLOv5 OpenVINO export
check_requirements('openvino-dev>=2023.0') # requires openvino-dev: https://pypi.org/project/openvino-dev/
import openvino.runtime as ov # noqa
from openvino.tools import mo # noqa
LOGGER.info(f'\n{prefix} starting export with openvino {ov.__version__}...')
f = str(file).replace(file.suffix, f'_openvino_model{os.sep}')
f_onnx = file.with_suffix('.onnx')
f_ov = str(Path(f) / file.with_suffix('.xml').name)
if int8:
check_requirements('nncf>=2.4.0') # requires at least version 2.4.0 to use the post-training quantization
import nncf
import numpy as np
from openvino.runtime import Core
from utils.dataloaders import create_dataloader
core = Core()
onnx_model = core.read_model(f_onnx) # export
def prepare_input_tensor(image: np.ndarray):
input_tensor = image.astype(np.float32) # uint8 to fp16/32
input_tensor /= 255.0 # 0 - 255 to 0.0 - 1.0
if input_tensor.ndim == 3:
input_tensor = np.expand_dims(input_tensor, 0)
return input_tensor
def gen_dataloader(yaml_path, task='train', imgsz=640, w
export.py是一个用于将YOLOv5 PyTorch模型导出为其他格式的程序文件。它支持导出的格式包括PyTorch、TorchScript、ONNX、OpenVINO、TensorRT、CoreML、TensorFlow SavedModel、TensorFlow GraphDef、TensorFlow Lite、TensorFlow Edge TPU、TensorFlow.js和PaddlePaddle。通过运行export.py文件,可以将YOLOv5模型导出为所需的格式。
在使用export.py文件之前,需要先安装相应的依赖库。根据CPU或GPU环境,可以选择安装不同的依赖库。然后,可以使用以下命令运行export.py文件并指定要导出的模型权重文件和要导出的格式。
导出的模型文件将保存在指定的路径中,并显示导出的结果和所花费的时间。
此外,export.py文件还包含了一些辅助函数和装饰器,用于导出模型的不同格式。这些函数包括export_torchscript、export_onnx和export_openvino等。这些函数会根据指定的格式将模型导出为相应的文件,并返回导出的文件路径和模型对象。
总之,export.py文件是一个用于将YOLOv5模型导出为其他格式的工具文件,可以根据需要选择导出的格式,并使用相应的命令进行导出。
5.2 odconv.py
class Attention(nn.Module):
def __init__(self, in_planes, out_planes, kernel_size, groups=1, reduction=0.0625, kernel_num=4, min_channel=16):
super(Attention, self).__init__()
attention_channel = max(int(in_planes * reduction), min_channel)
self.kernel_size = kernel_size
self.kernel_num = kernel_num
self.temperature = 1.0
self.avgpool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Conv2d(in_planes, attention_channel, 1, bias=False)
self.bn = nn.BatchNorm2d(attention_channel)
self.relu = nn.ReLU(inplace=True)
self.channel_fc = nn.Conv2d(attention_channel, in_planes, 1, bias=True)
self.func_channel = self.get_channel_attention
if in_planes == groups and in_planes == out_planes: # depth-wise convolution
self.func_filter = self.skip
else:
self.filter_fc = nn.Conv2d(attention_channel, out_planes, 1, bias=True)
self.func_filter = self.get_filter_attention
if kernel_size == 1: # point-wise convolution
self.func_spatial = self.skip
else:
self.spatial_fc = nn.Conv2d(attention_channel, kernel_size * kernel_size, 1, bias=True)
self.func_spatial = self.get_spatial_attention
if kernel_num == 1:
self.func_kernel = self.skip
else:
self.kernel_fc = nn.Conv2d(attention_channel, kernel_num, 1, bias=True)
self.func_kernel = self.get_kernel_attention
self._initialize_weights()
def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
if isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
def update_temperature(self, temperature):
self.temperature = temperature
@staticmethod
def skip(_):
return 1.0
def get_channel_attention(self, x):
channel_attention = torch.sigmoid(self.channel_fc(x).view(x.size(0), -1, 1, 1) / self.temperature)
return channel_attention
def get_filter_attention(self, x):
filter_attention = torch.sigmoid(self.filter_fc(x).view(x.size(0), -1, 1, 1) / self.temperature)
return filter_attention
def get_spatial_attention(self, x):
spatial_attention = self.spatial_fc(x).view(x.size(0), 1, 1, 1, self.kernel_size, self.kernel_size)
spatial_attention = torch.sigmoid(spatial_attention / self.temperature)
return spatial_attention
def get_kernel_attention(self, x):
kernel_attention = self.kernel_fc(x).view(x.size(0), -1, 1, 1, 1, 1)
kernel_attention = F.softmax(kernel_attention / self.temperature, dim=1)
return kernel_attention
def forward(self, x):
x = self.avgpool(x)
x = self.fc(x)
x = self.bn(x)
x = self.relu(x)
return self.func_channel(x), self.func_filter(x), self.func_spatial(x), self.func_kernel(x)
class ODConv2d(nn.Module):
def __init__(self, in_planes, out_planes, kernel_size, stride=1, padding=0, dilation=1, groups=1,
reduction=0.0625, kernel_num=4):
super(ODConv2d, self).__init__()
self.in_planes = in_planes
self.out_planes = out_planes
self.kernel_size = kernel_size
self.stride = stride
self.padding = padding
self.dilation = dilation
self.groups = groups
self.kernel_num = kernel_num
self.attention = Attention(in_planes, out_planes, kernel_size, groups=groups,
reduction=reduction, kernel_num=kernel_num)
self.weight = nn.Parameter(torch.randn(kernel_num, out_planes, in_planes//groups, kernel_size, kernel_size),
requires_grad=True)
self._initialize_weights()
if self.kernel_size == 1 and self.kernel_num == 1:
self._forward_impl = self._forward_impl_pw1x
else:
self._forward_impl = self._forward_impl_common
def _initialize_weights(self):
for i in range(self.kernel_num):
nn.init.kaiming_normal_(self.weight[i], mode='fan_out', nonlinearity='relu')
def update_temperature(self, temperature):
self.attention.update_temperature(temperature)
def _forward_impl_common(self, x):
channel_attention, filter_attention, spatial_attention, kernel_attention = self.attention(x)
batch_size, in_planes, height, width = x.size()
x = x * channel_attention
x = x.reshape(1, -1, height, width)
aggregate_weight = spatial_attention * kernel_attention * self.weight.unsqueeze(dim=0)
aggregate_weight = torch.sum(aggregate_weight, dim=1).view(
[-1, self.in_planes // self.groups, self.kernel_size, self.kernel_size])
output = F.conv2d(x, weight=aggregate_weight, bias=None, stride=self.stride, padding=self.padding,
dilation=self.dilation, groups=self.groups * batch_size)
output = output.view(batch_size, self.out_planes, output.size(-2), output.size(-1))
output = output * filter_attention
return output
def _forward_impl_pw1x(self, x):
channel_attention, filter_attention, spatial_attention, kernel_attention = self.attention(x)
x = x * channel_attention
output = F.conv2d(x, weight=self.weight.squeeze(dim=0), bias=None, stride=self.stride, padding=self.padding,
dilation=self.dilation, groups=self.groups)
output = output * filter_attention
return output
def forward(self, x):
return self._forward_impl(x)
这个程序文件是一个用于目标检测的卷积神经网络模型。它包含两个主要的类:Attention和ODConv2d。
Attention类是一个注意力模块,用于计算通道注意力、滤波器注意力、空间注意力和卷积核注意力。它接受输入特征图x,并通过一系列的卷积和激活函数操作计算不同类型的注意力。注意力的计算结果会被用于ODConv2d类中。
ODConv2d类是一个自定义的二维卷积层。它接受输入特征图x,并根据输入的参数进行卷积操作。在卷积过程中,它会使用Attention类计算不同类型的注意力,并将注意力应用于卷积操作中。根据卷积核的大小和数量,ODConv2d类会选择不同的前向传播方法。
整个程序文件的目的是通过引入注意力机制来增强卷积神经网络的表示能力,从而提高目标检测的性能。
5.3 od_mobilenetv2.py
class OD_MobileNetV2(nn.Module):
def __init__(self,
num_classes=1000,
width_mult=1.0,
inverted_residual_setting=None,
round_nearest=8,
block=InvertedResidual,
norm_layer=nn.BatchNorm2d,
dropout=0.2,
reduction=0.0625,
kernel_num=1,
**kwargs):
super(OD_MobileNetV2, self).__init__()
input_channel = 32
last_channel = 1280
if inverted_residual_setting is None:
inverted_residual_setting = [
[1, 16, 1, 1],
[6, 24, 2, 2],
[6, 32, 3, 2],
[6, 64, 4, 2],
[6, 96, 3, 1],
[6, 160, 3, 2],
[6, 320, 1, 1],
]
if len(inverted_residual_setting) == 0 or len(inverted_residual_setting[0]) != 4:
raise ValueError("inverted_residual_setting should be non-empty "
"or a 4-element list, got {}".format(inverted_residual_setting))
input_channel = _make_divisible(input_channel * width_mult, round_nearest)
self.last_channel = _make_divisible(last_channel * max(1.0, width_mult), round_nearest)
features = [ConvBNReLU(3, input_channel, stride=2, norm_layer=norm_layer)]
for t, c, n, s in inverted_residual_setting:
output_channel = _make_divisible(c * width_mult, round_nearest)
for i in range(n):
stride = s if i == 0 else 1
features.append(block(input_channel, output_channel, stride, expand_ratio=t, norm_layer=norm_layer,
reduction=reduction, kernel_num=kernel_num))
input_channel = output_channel
features.append(ODConvBNReLU(input_channel, self.last_channel, kernel_size=1, norm_layer=norm_layer,
reduction=reduction, kernel_num=kernel_num))
self.features = nn.Sequential(*features)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
nn.init.zeros_(m.bias)
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
nn.init.ones_(m.weight)
nn.init.zeros_(m.bias)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.zeros_(m.bias)
self.channel = [i.size(1) for i in self.forward(torch.randn(2, 3, 640, 640))]
def net_update_temperature(self, temperature):
for m in self.modules():
if hasattr(m, "update_temperature"):
m.update_temperature(temperature)
def forward(self, x):
input_size = x.size(2)
scale = [4, 8, 16, 32]
features = [None, None, None, None]
for idx, layer in enumerate(self.features):
x = layer(x)
if input_size // x.size(2) in scale:
features[scale.index(input_size // x.size(2))] = x
return features
该程序文件是一个实现了MobileNetV2网络结构的模型文件。该模型文件定义了几个类,包括ConvBNReLU、ODConvBNReLU、InvertedResidual和OD_MobileNetV2。其中,ConvBNReLU和ODConvBNReLU是卷积、批归一化和ReLU激活函数的组合层;InvertedResidual是MobileNetV2中的倒残差结构;OD_MobileNetV2是整个MobileNetV2网络的主类。
OD_MobileNetV2类是MobileNetV2的主要实现,其中包含了网络的前向传播方法forward和网络的初始化方法__init__。在__init__方法中,首先定义了一些网络的超参数,如输入通道数、最后输出通道数、宽度乘数等。然后根据给定的超参数构建了网络的各个层,并将它们组合成一个nn.Sequential对象。最后,对网络的权重进行了初始化。
除了OD_MobileNetV2类之外,该程序文件还定义了一些辅助函数,如_make_divisible函数用于确保通道数是8的倍数,update_weight函数用于加载预训练权重。
此外,该程序文件还定义了几个函数od_mobilenetv2_050、od_mobilenetv2_075和od_mobilenetv2_100,它们分别返回不同宽度乘数的MobileNetV2模型实例。这些函数还可以选择加载预训练权重,并将其应用于模型实例。
5.4 od_resnet.py
class OD_ResNet(nn.Module):
def __init__(self, block, layers, num_classes=1000, dropout=0.1, reduction=0.0625, kernel_num=1):
super(OD_ResNet, self).__init__()
self.inplanes = 64
self.conv1 = nn.Conv2d(3, self.inplanes, kernel_size=7, stride=2, padding=3,
bias=False)
self.bn1 = nn.BatchNorm2d(self.inplanes)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = self._make_layer(block, 64, layers[0], reduction=reduction, kernel_num=kernel_num)
self.layer2 = self._make_layer(block, 128, layers[1], stride=2, reduction=reduction, kernel_num=kernel_num)
self.layer3 = self._make_layer(block, 256, layers[2], stride=2, reduction=reduction, kernel_num=kernel_num)
self.layer4 = self._make_layer(block, 512, layers[3], stride=2, reduction=reduction, kernel_num=kernel_num)
for m in self.modules():
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, (nn.BatchNorm2d, nn.GroupNorm)):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.zeros_(m.bias)
self.channel = [i.size(1) for i in self.forward(torch.randn(2, 3, 640, 640))]
def net_update_temperature(self, temperature):
for m in self.modules():
if hasattr(m, "update_temperature"):
m.update_temperature(temperature)
def _make_layer(self, block, planes, blocks, stride=1, reduction=0.625, kernel_num=1):
downsample = None
if stride != 1 or self.inplanes != planes * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.inplanes, planes * block.expansion, kernel_size=1, stride=stride, padding=0, bias=False),
nn.BatchNorm2d(planes * block.expansion),
)
layers = []
layers.append(block(self.inplanes, planes, stride, downsample, reduction=reduction, kernel_num=kernel_num))
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
layers.append(block(self.inplanes, planes, reduction=reduction, kernel_num=kernel_num))
return nn.Sequential(*layers)
def forward(self, x):
x = self.conv1(x)
x = self.bn1(x)
x1 = self.relu(x)
x = self.maxpool(x1)
x2 = self.layer1(x)
x3 = self.layer2(x2)
x4 = self.layer3(x3)
x5 = self.layer4(x4)
return [x1, x2, x3, x4, x5]
该程序文件是一个用于目标检测的ResNet模型的实现。该文件定义了四个不同的ResNet模型:od_resnet18、od_resnet34、od_resnet50和od_resnet101。这些模型都是基于ResNet的基本块(BasicBlock)和瓶颈块(Bottleneck)构建的。
在文件中,还定义了一些辅助函数和类,包括odconv3x3和odconv1x1函数用于创建ODConv2d卷积层,BasicBlock和Bottleneck类用于定义ResNet的基本块和瓶颈块,以及OD_ResNet类用于定义整个ResNet模型。
在OD_ResNet类中,定义了模型的前向传播过程,包括卷积、批归一化、ReLU激活函数和池化操作。模型的层数和通道数都可以根据输入参数进行配置。
在文件的最后,定义了一些辅助函数,用于加载预训练权重和更新模型的权重。
总体来说,该程序文件实现了目标检测任务中常用的ResNet模型,并提供了加载预训练权重的功能。
5.5 train.py
class YOLOv5Trainer:
def __init__(self, hyp, opt, device, callbacks):
self.hyp = hyp
self.opt = opt
self.device = device
self.callbacks = callbacks
def train(self):
# Code for training the YOLOv5 model
pass
该程序文件是用于训练一个YOLOv5模型的。文件名为train.py。该程序文件包含了训练YOLOv5模型所需的各种功能和参数设置。
程序文件的主要功能包括:
- 加载和解析命令行参数
- 设置训练相关的参数和超参数
- 创建模型和数据加载器
- 定义损失函数和优化器
- 训练模型并保存权重
- 进行模型评估和绘制训练曲线
程序文件中的代码主要包括以下部分:
- 导入所需的库和模块
- 定义全局变量和常量
- 定义训练函数和相关辅助函数
- 解析命令行参数
- 加载模型和数据集
- 配置训练参数和超参数
- 创建日志记录器和回调函数
- 执行训练过程
该程序文件可以通过命令行参数来指定训练所需的配置文件、权重文件、图像大小等参数。训练过程中会自动下载所需的模型和数据集文件。训练完成后,会保存训练好的模型权重和训练日志。
该程序文件还支持多GPU训练和分布式训练,并提供了一些额外的功能,如模型评估、模型融合、模型剪枝等。
总之,该程序文件是一个完整的YOLOv5模型训练脚本,提供了丰富的功能和参数选项,方便用户进行自定义数据集的训练。
5.6 ui.py
class YOLOv5Model:
def __init__(self, weights='./best.pt', data=ROOT / 'data/coco128.yaml', 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.load_model()
def load_model(self):
device = select_device
......
这个程序文件是一个基于PyQt5的图形用户界面(GUI)程序。它实现了一个快递包裹分拣分割信息提取系统。程序文件名为ui.py。
该程序文件导入了一些必要的库和模块,包括PyQt5、PyTorch、NumPy等。它定义了一些函数和类,用于加载模型、运行模型进行目标检测、处理图像等操作。
程序的主要功能是实现快递包裹的分拣、分割和信息提取。它提供了实时识别、选择图片和图片识别等功能按钮。用户可以通过实时识别按钮进行实时目标检测,选择图片按钮选择要识别的图片,图片识别按钮对选择的图片进行目标检测。
程序界面由几个标签、按钮和文本框组成。标签用于显示系统名称、原图和输出图像。按钮用于触发不同的功能操作,如实时识别、选择图片、图片识别和退出系统。文本框用于显示识别结果和其他信息。
程序通过创建一个应用对象和主窗口对象,将界面和功能逻辑进行了封装和组织。在主窗口对象中,通过连接按钮的点击事件和对应的槽函数,实现了按钮的功能操作。
在程序的主函数中,加载了模型,并创建了应用对象和主窗口对象。然后通过调用
6.系统整体结构
根据以上分析,该项目的整体功能是一个视觉项目,主要包括目标检测、分类和分割等任务。它使用了YOLOv5模型作为目标检测的基础模型,并引入了ODConv的改进,以增强模型的表示能力。此外,还使用了MobileNetV2和ResNet作为分类和分割任务的基础模型。
下面是每个文件的功能的整理:
文件路径 | 功能 |
---|---|
export.py | 将YOLOv5模型导出为其他格式的工具文件 |
odconv.py | 实现了Attention和ODConv2d类,用于增强卷积神经网络的表示能力 |
od_mobilenetv2.py | 实现了基于MobileNetV2的目标检测模型 |
od_resnet.py | 实现了基于ResNet的目标检测模型 |
train.py | YOLOv5模型的训练脚本 |
ui.py | 基于PyQt5的图形用户界面程序,用于快递包裹分拣分割信息提取系统 |
val.py | YOLOv5模型的验证脚本 |
yolo.py | YOLOv5模型的核心实现 |
classify/predict.py | 分类任务的预测脚本 |
classify/train.py | 分类任务的训练脚本 |
classify/val.py | 分类任务的验证脚本 |
models/common.py | 包含一些通用的模型组件和函数 |
models/experimental.py | 包含一些实验性的模型组件和函数 |
models/odconv.py | 包含ODConv2d类的实现 |
models/od_mobilenetv2.py | 基于ODConv2d的MobileNetV2模型实现 |
models/od_resnet.py | 基于ODConv2d的ResNet模型实现 |
models/tf.py | TensorFlow模型的相关函数和类 |
models/yolo.py | YOLOv5模型的实现 |
models/init.py | 模型模块的初始化文件 |
models/ODConv/odconv.py | ODConv2d类的实现 |
models/ODConv/od_mobilenetv2.py | 基于ODConv2d的MobileNetV2模型实现 |
models/ODConv/od_resnet.py | 基于ODConv2d的ResNet模型实现 |
ODConv/odconv.py | ODConv2d类的实现 |
ODConv/od_mobilenetv2.py | 基于ODConv2d的MobileNetV2模型实现 |
ODConv/od_resnet.py | 基于ODConv2d的ResNet模型实现 |
segment/predict.py | 分割任务的预测脚本 |
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/downloads.py | 包含下载数据集和模型的函数 |
utils/general.py | 包含一些通用的 |
7.全维动态卷积ODConv
鉴于上述讨论,我们的ODConv引入了一种多维注意机制,该机制具有并行策略,用于学习卷积核在核空间的所有四个维度上的不同注意。图提供了CondConv、DyConv和ODConv的示意性比较。
ODConv的公式:根据等式1中的符号,ODConv可定义为
将注意力标量分配给整个卷积核。图2示出了将这四种类型的关注乘以n个卷积核的过程。原则上,这四种类型的关注是相互补充的,并且以位置、信道、滤波器和核的顺序将它们逐步乘以卷积核
,使得卷积运算不同w.r.t.所有空间位置、所有输入信道、所有滤波器和输入x的所有核,提供捕获丰富上下文线索的性能保证。因此,ODConv可以显著增强CNN基本卷积运算的特征提取能力。此外,具有单个卷积核的ODConv可以与标准CondConv和DyConv竞争或优于它们,为最终模型引入的额外参数大大减少。提供了大量实验来验证这些优点。通过比较等式1和等式2,我们可以清楚地看到,ODConv是一种更广义的动态卷积。此外,当设置n=1且 所有分量均为1时,只关注滤波器方向 的ODConv将减少为:将基于输入特征的SE变量应用于卷积滤波器,然后进行卷积运算(注意原始SE(Hu等人,2018b)基于输出特征,并且用于重新校准输出特征本身)。这种SE变体是ODConv的特例。
图:将ODConv中的四种注意类型逐步乘以卷积核的示例。(a) 沿空间维度的逐位置乘法运算,(b)沿输入信道维度的逐信道乘法运算、(c)沿输出信道维度的按滤波器乘法运算,以及(d)沿卷积核空间的核维度的按核乘法运算。方法部分对符号进行了说明
实现:对于ODConv,一个关键问题是如何计算卷积核的四种关注度 。继CondConv和DyConv之后,我们还使用SE型注意力模块(Hu等人,2018b),但将多个头部作为来计算它们,其结构如图所示。具体而言,首先通过逐通道全局平均池(GAP)运算将输入压缩到具有长度的特征向量中。随后,存在完全连接(FC)层和四个头部分支。ReLU(Krizhevsky等人,2012)位于FC层之后。FC层将压缩特征向量映射到具有缩减比的低维空间(根据消融实验,我们在所有主要实验中设置 ,避免了高模型复杂度)。对于四个头部分支,每个分支都有一个输出大小如图。
8.引入ODConv的改进YOLO
参考这篇博客涵盖了引入ODConv的改进YOLOv5系统的内容,ODConv采用多维注意机制,在卷积核空间的四个维度上学习不同的注意。结合了CondConv和DyConv的优势,ODConv通过图示的四种注意类型逐步与卷积核相乘,以捕获丰富的上下文线索,提升特征提取能力。
ODConv结构与方法
ODConv的公式和图示展示了其关注力分配给卷积核的方式,其中四种类型的关注以位置、信道、滤波器和核的顺序逐步与卷积核相乘。这种结构保证了卷积运算不同于标准的Conv操作,能够捕获更多上下文信息,从而增强了CNN的特征提取能力。另外,单个卷积核的ODConv在性能上能够与CondConv和DyConv相竞争,并且引入的额外参数大幅减少。
ODConv的特殊之处在于其广义的动态卷积性质,同时在特定条件下(n=1且所有分量为1),它可以退化为一种特例,即只关注滤波器方向,这类似于基于输入特征的SE变体,但不同于原始SE,它基于输出特征。
ODConv的实现
关键问题在于如何计算卷积核的四种关注度。ODConv采用了SE型注意力模块,结合了多个头部来计算这些关注度。具体实现上,通过逐通道全局平均池运算和完全连接层,将输入压缩为特征向量,随后使用四个头部分支来计算四种不同类型的关注。这样的结构能在保持模型复杂度可控的情况下,提升了特征的表征能力。
ODConv的引入为YOLOv5带来了显著的性能提升,并且通过大量实验证明了其在特征提取方面的优越性。其结合了多维注意机制和卷积操作,为目标检测和分拣系统的提升带来了新的思路和性能突破。
yolov5-ODConv的网络结构
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license
# Parameters
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
# 0-P1/2
# 1-P2/4
# 2-P3/8
# 3-P4/16
# 4-P5/32
# YOLOv5 v6.0 backbone
backbone:
# [from, number, module, args]
[[-1, 1, od_mobilenetv2_050, [False]], # 4
[-1, 1, SPPF, [1024, 5]], # 5
]
# YOLOv5 v6.0 head
head:
[[-1, 1, Conv, [512, 1, 1]], # 6
[-1, 1, nn.Upsample, [None, 2, 'nearest']], # 7
[[-1, 3], 1, Concat, [1]], # cat backbone P4 8
[-1, 3, C3, [512, False]], # 9
[-1, 1, Conv, [256, 1, 1]], # 10
[-1, 1, nn.Upsample, [None, 2, 'nearest']], # 11
[[-1, 2], 1, Concat, [1]], # cat backbone P3 12
[-1, 3, C3, [256, False]], # 13 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]], # 14
[[-1, 10], 1, Concat, [1]], # cat head P4 15
[-1, 3, C3, [512, False]], # 16 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]], # 17
[[-1, 5], 1, Concat, [1]], # cat head P5 18
[-1, 3, C3, [1024, False]], # 19 (P5/32-large)
[[13, 16, 19], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
9.训练结果可视化分析
评价指标
Epoch:表示训练纪元。
训练损失:包括训练阶段的框、分割、对象和类损失。
训练指标:框 (B) 和掩模 (M) 在不同 IoU 阈值下的精度、召回率和平均精度 (mAP)。
验证损失:验证阶段的框、分段、对象和类损失。
学习率(x/lr0、x/lr1、x/lr2):训练期间使用的学习率。
训练结果可视化
为了进行详细的分析(2000字),我们将从各个方面对数据进行可视化和解读:
趋势分析:检查损失和指标在不同时期如何演变。
性能评估:使用精度、召回率和 mAP 评估模型的性能。
过度拟合检查:比较训练和验证损失以检测过度拟合。
学习率影响:分析学习率的变化如何影响模型性能。
让我们首先为这些方面创建可视化。
import matplotlib.pyplot as plt
import seaborn as sns
# Setting the style for the plots
sns.set(style="whitegrid")
# Creating a function for plotting
def plot_metrics(data, title, ylabel, metrics):
plt.figure(figsize=(12, 6))
for metric in metrics:
plt.plot(data['epoch'], data[metric], label=metric)
plt.title(title)
plt.xlabel('Epoch')
plt.ylabel(ylabel)
plt.legend()
plt.show()
# Plotting training and validation losses
plot_metrics(data, 'Training and Validation Losses Over Epochs', 'Loss',
['train/box_loss', 'train/seg_loss', 'train/obj_loss', 'train/cls_loss',
'val/box_loss', 'val/seg_loss', 'val/obj_loss', 'val/cls_loss'])
# Plotting precision and recall for boxes and masks
plot_metrics(data, 'Precision and Recall for Boxes and Masks Over Epochs', 'Value',
['metrics/precision(B)', 'metrics/recall(B)', 'metrics/precision(M)', 'metrics/recall(M)'])
# Plotting mAP for boxes and masks
plot_metrics(data, 'Mean Average Precision (mAP) for Boxes and Masks Over Epochs', 'mAP',
['metrics/mAP_0.5(B)', 'metrics/mAP_0.5:0.95(B)', 'metrics/mAP_0.5(M)', 'metrics/mAP_0.5:0.95(M)'])
# Plotting Learning Rates
plot_metrics(data, 'Learning Rates Over Epochs', 'Learning Rate', ['x/lr0', 'x/lr1', 'x/lr2'])
可视化为模型训练和性能的各个方面提供了有价值的见解:
训练和验证损失:
训练和验证阶段的损失(框、分割、对象、类)随着时间的推移而减少,这表明模型学习有效。
训练和验证损失之间的密切一致性表明该模型没有过度拟合并且可以很好地推广到未见过的数据。
盒子和面具的精确度和召回率:
框 (B) 和掩模 (M) 的精确度和召回率指标通常会随着时间的推移而增加,这表明识别和分割地块的准确性有所提高。
各个时期的精确率和召回率值的波动表明模型在不同的学习阶段面临不同程度的挑战。
盒子和掩模的平均精度 (mAP):
mAP 指标在 IoU 阈值 0.5 和 0.5:0.95 下均呈现总体上升趋势,这有力地表明了模型准确预测包裹箱和掩模的能力不断提高。
与 IoU=0.5:0.95 相比,IoU=0.5 的 mAP 值更高,这是预期的,因为后者是更严格的精度度量。
学习率:
学习率(x/lr0、x/lr1、x/lr2)在历元内发生变化,可能是由于学习率计划或自适应学习率算法所致。
这些学习率变化的影响可以与性能指标相关联,以了解它们如何影响模型的学习。
混淆矩阵(confusion_matrix.png):
通常用于评估分类算法的性能。矩阵中的每个单元格代表针对实际分类的预测计数。在理想情况下,从左上角到右下角的对角线将包含所有预测(表示完美预测)。如果图像中的混淆矩阵在对角线上显示较高的值,则表明这些类的模型性能良好。
标签实例 (labels.jpg):
这可以表示数据集中每个类或标签的实例计数。它有助于理解类别分布,这对于识别可能会扭曲模型训练的类别不平衡至关重要。
标签相关图 (labels_correlogram.jpg) :
相关图显示不同变量之间的相关性。在对象检测或分割的背景下,它可能表示检测到的对象的位置或大小之间的相关性。
F1-置信曲线 (MaskF1_curve.png):
F1 分数是精确率和召回率的调和平均值。该曲线可能根据检测的不同置信度阈值绘制 F1 分数。跨置信水平的 F1 得分较高表明模型稳健。
精度-置信曲线 (MaskP_curve.png):
该曲线展示了精度如何随不同置信度阈值而变化。在整个置信水平上保持高精度的模型被认为是准确的。
精确率-召回率曲线 (MaskPR_curve.png):
它显示了不同阈值的精度和召回率之间的权衡。该曲线下面积 (AUC) 较高表示精确度和召回率之间具有良好的平衡,这在大多数情况下都是可取的。
召回置信曲线 (MaskR_curve.png):
这说明了不同置信度阈值下的召回率。跨置信度保持高召回率的模型擅长识别所有相关对象。
训练批次图像 (train_batch0.jpg):
这些是输入图像与模型的叠加预测的示例。它们提供了模型执行情况的视觉确认,突出显示了边界框的正确检测。
10.系统整合
参考博客《引入ODConv的改进YOLO快递包裹分拣分割信息提取系统》
11.参考文献
[1]刘腾,于会龙,田滨,等.智能车的智能指挥与控制:基本方法与系统结构[J].指挥与控制学报.2018,(1).DOI:10.3969/j.issn.2096-0204.2018.01.0022 .
[2]袁勇,王飞跃.平行区块链:概念、方法与内涵解析[J].自动化学报.2017,(10).DOI:10.16383/j.aas.2017.c170543 .
[3]白天翔,王帅,沈震,等.平行机器人与平行无人系统:框架、结构、过程、平台及其应用[J].自动化学报.2017,(2).DOI:10.16383/j.aas.2017.y000002 .
[4]孟祥冰,王蓉,张梅,等.平行感知:ACP理论在视觉SLAM技术中的应用[J].指挥与控制学报.2017,(4).DOI:10.3969/j.issn.2096-0204.2017.04.0350 .
[5]李力,林懿伦,曹东璞,等.平行学习-机器学习的一个新型理论框架[J].自动化学报.2017,(1).DOI:10.16383/j.aas.2017.y000001 .
[6]Fei-Yue Wang,Nan-Ning Zheng,Dongpu Cao,等.Parallel Driving in CPSS: A Unified Approach for Transport Automation and Vehicle Intelligence[J].自动化学报(英文版).2017,(4).DOI:10.1109/JAS.2017.7510598 .
[7]刘昕,王晓,张卫山,等.平行数据:从大数据到数据智能[J].模式识别与人工智能.2017,(8).DOI:10.16451/j.cnki.issn1003-6059.201708001 .
[8]袁勇,王飞跃.区块链技术发展现状与展望[J].自动化学报.2016,(4).DOI:10.16383/j.aas.2016.c160158 .
[9]王飞跃.从激光到激活:钱学森的情报理念与平行情报体系[J].自动化学报.2015,(6).DOI:10.16383/j.aas.2015.c150216 .
[10]王飞跃.情报5.0:平行时代的平行情报体系[J].情报学报.2015,(6).DOI:10.3772/j.issn.1000-0135.2015.006.001 .