赞
踩
项目参考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模型的槟榔叶病虫害识别系统具有重要的实际意义。该系统可以提高识别准确率和效率,为槟榔种植者提供科学决策支持,促进槟榔产业的健康发展。
迁移DAMO-YOLO中的GFPN的改进YOLOv5的槟榔叶病虫害的识别系统_哔哩哔哩_bilibili
首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集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列表
我们需要将数据集整理为以下结构:
-----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
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
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模型导出为其他格式的工具文件,提供了多种导出格式的选项,并提供了一些辅助函数和工具函数来支持导出过程。
这个程序文件名为extra_modules.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.'
......
这个程序文件是用来训练一个YOLOv5模型的。它可以接受一些命令行参数,包括数据集配置文件、模型权重文件、输入图像尺寸等。训练过程中会使用指定的超参数进行模型训练,并保存训练过程中的权重文件和日志信息。程序文件中还包含了一些辅助函数和工具类,用于数据加载、模型构建、优化器设置等。训练过程中还会使用一些回调函数来记录训练过程中的指标和生成可视化图表。
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
这个程序文件是一个使用PyQt5和OpenCV实现的槟榔病虫害识别系统。程序的主要功能是通过选择图片文件或实时摄像头输入,使用预训练的YOLOv5模型进行目标检测和识别,并在界面上显示检测结果。
程序的主要结构如下:
load_model
函数,用于加载模型和设置相关参数。run
函数,用于运行模型进行目标检测和识别。det
函数,用于处理图像并调用run
函数进行识别。Thread_1
类,继承自QThread
,用于创建一个线程来执行识别操作。Ui_MainWindow
类,用于创建程序的主窗口界面,并实现了相关的按钮点击事件和界面显示方法。__main__
函数中,加载模型并创建应用程序对象,创建主窗口对象并显示。整个程序的功能是通过界面上的按钮选择图片文件或实时摄像头输入,然后调用det
函数进行识别,并将识别结果显示在界面上。
这个程序文件是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的网络结构。
这个程序文件是YOLOv5的实验模块。它包含了一些实验性的网络层和模型。
文件中定义了以下几个类:
Sum:实现了多个层的加权求和操作。可以选择是否对层应用权重。
MixConv2d:实现了混合深度卷积操作。可以选择是否在每个卷积组中使用相同数量的通道。
Ensemble:模型的集合,可以同时对多个模型进行推理。
文件还定义了一个辅助函数attempt_load,用于加载模型权重。可以加载单个模型或多个模型的集合。
总体来说,这个程序文件包含了一些实验性的网络层和模型,用于YOLOv5的实验和研究。
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
这是一个YOLOv5模型的程序文件。它包含了YOLOv5模型的各个组件和功能。
程序文件首先导入了必要的库和模块。然后定义了一些全局变量和常量。接下来定义了YOLOv5模型的一些特定模块,如Detect
和Segment
。Detect
是YOLOv5检测模型的头部,用于生成检测结果。Segment
是YOLOv5分割模型的头部,用于生成分割结果。这些模块都继承自nn.Module
类。
BaseModel
是YOLOv5基础模型,包含了模型的前向传播方法和一些辅助方法。DetectionModel
是YOLOv5检测模型,继承自BaseModel
,定义了模型的构建和初始化方法。
整个程序文件的结构是模块的定义和初始化,然后是模型的前向传播方法和一些辅助方法的定义。最后是模型的构建和初始化方法的定义。
这个程序文件实现了YOLOv5模型的各个组件和功能,可以用于检测和分割任务。
根据以上分析,该程序是一个用于槟榔叶病虫害识别系统的工程。它使用了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.py | YOLOv5模型的组件和功能的定义 |
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 | 包含了下载数据集和模型权重的函数 |
DAMO-YOLO是一个兼顾速度与精度的目标检测框架,其效果超越了目前的一众YOLO系列方法,在实现SOTA的同时,保持了很高的推理速度。DAMO-YOLO是在YOLO框架基础上引入了一系列新技术,对整个检测框架进行了大幅的修改。具体包括:基于NAS搜索的新检测backbone结构,更深的neck结构,精简的head结构,以及引入蒸馏技术实现效果的进一步提升。模型之外,DAMO-YOLO还提供高效的训练策略以及便捷易用的部署工具,帮助您快速解决工业落地中的实际问题!
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对比
在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,远远低于只增加额外下采样算子的精度/时延收益,因此在最终设计上我们摒弃了额外的上采样算子。
在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 声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/770325
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。