赞
踩
项目参考AAAI Association for the Advancement of Artificial Intelligence
研究背景与意义:
近年来,随着计算机视觉技术的快速发展,目标检测成为了计算机视觉领域的一个重要研究方向。目标检测技术在许多领域中具有广泛的应用,如智能交通、安防监控、无人驾驶等。在水产养殖领域,鱼虾残饵量检测是一个重要的任务,可以帮助养殖户实时监测饵料的消耗情况,从而更好地管理养殖过程。
目前,基于深度学习的目标检测方法已经取得了很大的进展,其中YOLO(You Only Look Once)系列是一种非常流行的目标检测算法。YOLOv5是YOLO系列的最新版本,相比于之前的版本,YOLOv5在精度和速度上都有了显著的提升。然而,对于鱼虾残饵量检测这样的特定任务,YOLOv5仍然存在一些问题。
首先,YOLOv5在处理小目标时存在一定的困难。由于鱼虾残饵量通常比较小,因此对于小目标的检测,YOLOv5的性能可能会受到限制。其次,YOLOv5在处理目标形变时也存在一定的挑战。在水产养殖过程中,鱼虾残饵量的形状可能会因为各种因素而发生变化,如水流、饵料浸泡等。这就要求目标检测算法能够对目标的形变进行有效的识别和检测。
为了解决上述问题,本研究引入了可变形卷积(Deformable Convolutional Networks,DCN)来改进YOLOv5的鱼虾残饵量检测系统。可变形卷积是一种能够自适应地调整卷积核形状的卷积操作,可以有效地捕捉目标的形变信息。通过引入DCN,我们希望能够提高YOLOv5在小目标和形变目标上的检测性能,从而更准确地检测鱼虾残饵量。
本研究的意义主要体现在以下几个方面:
首先,改进YOLOv5的鱼虾残饵量检测系统可以提高水产养殖过程中的管理效率。通过实时监测饵料的消耗情况,养殖户可以更好地掌握饵料的使用情况,从而合理调整饵料的投放量,减少浪费,提高养殖效益。
其次,本研究的方法可以为其他类似的目标检测任务提供借鉴。鱼虾残饵量检测是一种特定的目标检测任务,但是目标形变和小目标检测是目标检测领域中普遍存在的问题。通过引入可变形卷积来改进目标检测算法,可以为其他类似任务的解决方案提供参考和借鉴。
最后,本研究的成果对于推动水产养殖行业的智能化发展具有积极的意义。随着人工智能技术的不断进步,智能化养殖已经成为水产养殖行业的发展趋势。改进鱼虾残饵量检测系统可以为智能化养殖提供基础支持,为养殖户提供更加智能、高效的管理方式。
综上所述,引入可变形卷积DCN改进YOLOv5的鱼虾残饵量检测系统具有重要的研究意义和实际应用价值。通过提高目标检测算法在小目标和形变目标上的性能,可以更准确地监测鱼虾残饵量,提高养殖效益,推动水产养殖行业的智能化发展。
引入可变形卷积DCN改进YOLOv5的鱼虾残饵量检测系统_哔哩哔哩_bilibili
首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集BaitDatasets。
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
class YOLOv5Exporter:
def __init__(self, weights, include):
self.weights = weights
self.include = include
self.FILE = Path(__file__).resolve()
self.ROOT = self.FILE.parents[0] # YOLOv5 root directory
if str(self.ROOT) not in sys.path:
sys.path.append(str(self.ROOT)) # add ROOT to PATH
self.ROOT = Path(os.path.relpath(self.ROOT, Path.cwd())) # relative
def export_torchscript(self, model, im, file, optimize, prefix=colorstr('TorchScript:')):
# YOLOv5 TorchScript model export
try:
print(f'\n{prefix} starting export with torch {torch.__version__}...')
f = file.with_suffix('.torchscript.pt')
ts = torch.jit.trace(model, im, strict=False)
(optimize_for_mobile(ts) if optimize else ts).save(f)
print(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)')
except Exception as e:
print(f'{prefix} export failure: {e}')
def export_onnx(self, model, im, file, opset, train, dynamic, simplify, prefix=colorstr('ONNX:')):
# YOLOv5 ONNX export
try:
check_requirements(('onnx',))
import onnx
print(f'\n{prefix} starting export with onnx {onnx.__version__}...')
f = file.with_suffix('.onnx')
torch.onnx.export(model, im, f, verbose=False, opset_version=opset,
training=torch.onnx.TrainingMode.TRAINING if train else torch.onnx.TrainingMode.EVAL,
do_constant_folding=not train,
input_names=['images'],
output_names=['output'],
dynamic_axes={'images': {0: 'batch', 2: 'height', 3: 'width'}, # shape(1,3,640,640)
'output': {0: 'batch', 1: 'anchors'} # shape(1,25200,85)
} if dynamic else None)
# Checks
model_onnx = onnx.load(f) # load onnx model
onnx.checker.check_model(model_onnx) # check onnx model
# print(onnx.helper.printable_graph(model_onnx.graph)) # print
# Simplify
if simplify:
try:
check_requirements(('onnx-simplifier',))
import onnxsim
print(f'{prefix} simplifying with onnx-simplifier {onnxsim.__version__}...')
model_onnx, check = onnxsim.simplify(
model_onnx,
dynamic_input_shape=dynamic,
input_shapes={'images': list(im.shape)} if dynamic else None)
assert check, 'assert check failed'
onnx.save(model_onnx, f)
except Exception as e:
print(f'{prefix} simplifier failure: {e}')
print(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)')
print(f"{prefix} run --dynamic ONNX model inference with: 'python detect.py --weights {f}'")
except Exception as e:
print(f'{prefix} export failure: {e}')
def export_coreml(self, model, im, file, prefix=colorstr('CoreML:')):
# YOLOv5 CoreML export
ct_model = None
try:
check_requirements(('coremltools',))
import coremltools as ct
print(f'\n{prefix} starting export with coremltools {ct.__version__}...')
f = file.with_suffix('.mlmodel')
model.train() # CoreML exports should be placed in model.train() mode
ts = torch.jit.trace(model, im, strict=False) # TorchScript model
ct_model = ct.convert(ts, inputs=[ct.ImageType('image', shape=im.shape, scale=1 / 255.0, bias=[0, 0, 0])])
ct_model.save(f)
print(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)')
except Exception as e:
print(f'\n{prefix} export failure: {e}')
return ct_model
def export_saved_model(self, model, im, file, dynamic,
tf_nms=False, agnostic_nms=False, topk_per_class=100, topk_all=100, iou_thres=0.45,
conf_thres=0.25, prefix=colorstr('TensorFlow saved_model:')):
# YOLOv5 TensorFlow saved_model export
keras_model = None
try:
import tensorflow as tf
from tensorflow import keras
from models.tf import TFModel, TFDetect
print(f'\n{prefix} starting export with tensorflow {tf.__version__}...')
f = str(file).replace('.pt', '_saved_model')
batch_size, ch, *imgsz = list(im.shape) # BCHW
tf_model = TFModel(cfg=model.yaml, model=model, nc=model.nc, imgsz=imgsz)
im = tf.zeros((batch_size, *imgsz, 3)) # BHWC order for TensorFlow
y = tf_model.predict(im, tf_nms, agnostic_nms, topk_per_class, topk_all, iou_thres, conf_thres)
inputs = keras.Input(shape=(*imgsz, 3), batch_size=None if dynamic else batch_size)
outputs = tf_model.predict(inputs, tf_nms, agnostic_nms, topk_per_class, topk_all, iou_thres, conf_thres)
keras_model = keras.Model(inputs=inputs, outputs=outputs)
keras_model.trainable = False
keras_model.summary()
keras_model.save(f, save_format='tf')
print(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)')
except Exception as e:
print(f'\n{prefix} export failure: {e}')
return keras_model
def export_pb(self, keras_model, im, file, prefix=colorstr('TensorFlow GraphDef:')):
# YOLOv5 TensorFlow GraphDef *.pb export https://github.com/leimao/Frozen_Graph_TensorFlow
try:
import tensorflow as tf
from tensorflow.python.framework.convert_to_constants import convert_variables_to_constants_v2
print(f'\n{prefix} starting export with tensorflow {tf.__version__}...')
f = file.with_suffix('.pb')
m = tf.function(lambda x: keras_model(x)) # full model
m = m.get_concrete_function(tf.TensorSpec(keras_model.inputs[0].shape, keras_model.inputs[0].dtype))
frozen_func = convert_variables_to_constants_v2(m)
frozen_func.graph.as_graph_def()
tf.io.write_graph(graph_or_graph_def=frozen_func.graph, logdir=str(f.parent), name=f.name, as_text=False)
print(f'{prefix} export success, saved as {f} ({file_size(f):.1f} MB)')
except Exception as e:
print(f'\n{prefix} export failure: {e}')
def export_tflite(self, keras_model, im, file, int8, data, ncalib, prefix=colorstr('TensorFlow Lite:')):
# YOLOv5 TensorFlow Lite export
try:
import tensorflow as tf
from models.tf import representative_dataset_gen
print(f'\n{prefix} starting export with tensorflow {tf.__version__}...')
batch_size, ch, *imgsz = list(im.shape) # BCHW
f = str(file).replace('.pt', '-fp16.tflite')
converter = tf.lite.TFLiteConverter.from_keras_model(keras_model)
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS]
converter.target_spec.supported_types = [tf.float16]
converter.optimizations = [tf.lite.Optimize.DEFAULT]
if int8:
dataset = LoadImages(check_dat
该程序文件是用于将YOLOv5 PyTorch模型导出为TorchScript、ONNX、CoreML、TensorFlow等格式的工具。通过命令行参数指定要导出的模型权重文件和导出格式,可以将模型导出为指定格式的文件。
程序文件首先导入了必要的库和模块,然后定义了一些导出函数。其中,export_torchscript
函数用于将模型导出为TorchScript格式,export_onnx
函数用于将模型导出为ONNX格式,export_coreml
函数用于将模型导出为CoreML格式,export_saved_model
函数用于将模型导出为TensorFlow saved_model格式,export_pb
函数用于将模型导出为TensorFlow GraphDef格式,export_tflite
函数用于将模型导出为TensorFlow Lite格式。
程序文件还定义了一些辅助函数和全局变量,用于处理命令行参数、加载模型、检查依赖库等操作。
最后,程序文件通过命令行参数指定要导出的模型权重文件和导出格式,并调用相应的导出函数进行导出操作。
def train(hyp, # path/to/hyp.yaml or hyp dictionary
opt,
device,
callbacks
):
save_dir, epochs, batch_size, weights, single_cls, evolve, data, cfg, resume, noval, nosave, workers, freeze, = \
Path(opt.save_dir), opt.epochs, opt.batch_size, opt.weights, opt.single_cls, opt.evolve, opt.data, opt.cfg, \
opt.resume, opt.noval, opt.nosave, opt.workers, opt.freeze
# Directories
w = save_dir / 'weights' # weights dir
(w.parent if evolve else w).mkdir(parents=True, exist_ok=True) # make dir
last, best = w / 'last.pt', w / 'best.pt'
# Hyperparameters
if isinstance(hyp, str):
with open(hyp, errors='ignore') as f:
hyp = yaml.safe_load(f) # load hyps dict
LOGGER.info(colorstr('hyperparameters: ') + ', '.join(f'{k}={v}' for k, v in hyp.items()))
# Save run settings
with open(save_dir / 'hyp.yaml', 'w') as f:
yaml.safe_dump(hyp, f, sort_keys=False)
with open(save_dir / 'opt.yaml', 'w') as f:
yaml.safe_dump(vars(opt), f, sort_keys=False)
data_dict = None
# Loggers
if RANK in [-1, 0]:
loggers = Loggers(save_dir, weights, opt, hyp, LOGGER) # loggers instance
if loggers.wandb:
data_dict = loggers.wandb.data_dict
if resume:
weights, epochs, hyp = opt.weights, opt.epochs, opt.hyp
# Register actions
for k in methods(loggers):
callbacks.register_action(k, callback=getattr(loggers, k))
# Config
plots = not evolve # create plots
cuda = device.type != 'cpu'
init_seeds(1 + RANK)
with torch_distributed_zero_first(LOCAL_RANK):
data_dict = data_dict or check_dataset(data) # check if None
train_path, val_path = data_dict['train'], data_dict['val']
nc = 1 if single_cls else int(data_dict['nc']) # number of classes
names = ['item'] if single_cls and len(data_dict['names']) != 1 else data_dict['names'] # class names
assert len(names) == nc, f'{len(names)} names found for nc={nc} dataset in {data}' # check
is_coco = data.endswith('coco.yaml') and nc == 80 # COCO dataset
# Model
check_suffix(weights, '.pt') # check weights
pretrained = weights.endswith('.pt')
if pretrained:
with torch_distributed_zero_first(LOCAL_RANK):
weights = attempt_download(weights) # download if not found locally
ckpt = torch.load(weights, map_location=device) # load checkpoint
model = Model(cfg or ckpt['model'].yaml, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
exclude = ['anchor'] if (cfg or hyp.get('anchors')) and not resume else [] # exclude keys
csd = ckpt['model'].float().state_dict() # checkpoint state_dict as FP32
csd = intersect_dicts(csd, model.state_dict(), exclude=exclude) # intersect
model.load_state_dict(csd, strict=False) # load
LOGGER.info(f'Transferred {len(csd)}/{len(model.state_dict())} items from {weights}') # report
else:
model = Model(cfg, ch=3, nc=nc, anchors=hyp.get('anchors')).to(device) # create
# Freeze
freeze = [f'model.{x}.' for x in range(freeze)] # layers to freeze
for k, v in model.named_parameters():
v.requires_grad = True # train all layers
if any(x in k for x in freeze):
print(f'freezing {k}')
v.requires_grad = False
# Image size
gs = max(int(model.stride.max()), 32) # grid size (max stride)
imgsz = check_img_size(opt.imgsz, gs, floor=gs * 2) # verify imgsz is gs-multiple
# Batch size
if RANK == -1 and batch_size == -1: # single-GPU only, estimate best batch size
batch_size = check_train_batch_size(model, imgsz)
# Optimizer
nbs = 64 # nominal batch size
accumulate = max(round(nbs / batch_size), 1) # accumulate loss before optimizing
hyp['weight_decay'] *= batch_size * accumulate / nbs # scale weight_decay
LOGGER.info(f"Scaled weight_decay = {hyp['weight_decay']}")
g0, g1, g2 = [], [], [] # optimizer parameter groups
for v in model.modules():
if hasattr(v, 'bias') and isinstance(v.bias, nn.Parameter): # bias
g2.append(v.bias)
if isinstance(v, nn.BatchNorm2d): # weight (no decay)
g0.append(v.weight)
elif hasattr(v, 'weight') and isinstance(v.weight, nn.Parameter): # weight (with decay)
g1.append(v.weight)
if opt.adam:
optimizer = Adam(g0, lr=hyp['lr0'], betas=(hyp['momentum'], 0.999)) # adjust beta1 to momentum
else:
optimizer = SGD(g0, lr=hyp['lr0'], momentum=hyp['momentum'], nesterov=True)
optimizer.add_param_group({'params': g1, 'weight_decay': hyp['weight_decay']}) # add g1 with weight_decay
optimizer.add_param_group({'params': g2}) # add g2 (biases)
LOGGER.info(f"{colorstr('optimizer:')} {type(optimizer).__name__} with parameter groups "
f"{len(g0)} weight, {len(g1)} weight (no decay), {len(g2)} bias")
del g0, g1, g2
# Scheduler
if opt.linear_lr:
lf = lambda x: (1 - x / (epochs - 1)) * (
这个程序文件是用来训练一个YOLOv5模型的。它包含了训练模型所需的各种功能,如数据加载、模型创建、优化器、学习率调度器等。程序的入口函数是train(),它接受一些参数,如超参数、设备、回调函数等。在训练过程中,程序会加载预训练的权重,创建模型,并根据数据集进行训练。训练过程中会使用优化器和学习率调度器来更新模型的参数。训练过程中还会进行一些日志记录和模型保存的操作。程序还支持断点续训功能,可以从之前训练的模型继续训练。
class ObjectDetection:
def __init__(self):
self.model, self.stride, self.names, self.pt, self.jit, self.onnx, self.engine = self.load_model()
def load_model(self,
weights=ROOT / 'best.pt', # model.pt path(s)
data=ROOT / 'data/coco128.yaml', # dataset.yaml path
device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu
half=False, # use FP16 half-precision inference
dnn=False, # use OpenCV DNN for ONNX inference
):
# Load model
device = select_device(device)
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data)
stride, names, pt, jit, onnx, engine = model.stride, model.names, model.pt, model.jit, model.onnx, model.engine
# Half
half &= (pt or jit or onnx or engine) and device.type != 'cpu' # FP16 supported on limited backends with CUDA
if pt or jit:
model.model.half() if half else model.model.float()
return model, stride, names, pt, jit, onnx, engine
def run(self, img, stride, pt,
imgsz=(640, 640), # inference size (height, width)
conf_thres=0.25, # confidence threshold
iou_thres=0.05, # NMS IOU threshold
max_det=1000, # maximum detections per image
device='', # cuda device, i.e. 0 or 0,1,2,3 or cpu
classes=None, # filter by class: --class 0, or --class 0 2 3
agnostic_nms=False, # class-agnostic NMS
augment=False, # augmented inference
half=False, # use FP16 half-precision inference
):
cal_detect = []
device = select_device(device)
names = self.model.module.names if hasattr(self.model, 'module') else self.model.names # get class names
# Set Dataloader
im = letterbox(img, imgsz, stride, pt)[0]
# Convert
im = im.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
im = np.ascontiguousarray(im)
im = torch.from_numpy(im).to(device)
im = im.half() if half else im.float() # uint8 to fp16/32
im /= 255 # 0 - 255 to 0.0 - 1.0
if len(im.shape) == 3:
im = im[None] # expand for batch dim
pred = self.model(im, augment=augment)
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
# Process detections
for i, det in enumerate(pred): # detections per image
if len(det):
# Rescale boxes from img_size to im0 size
det[:, :4] = scale_coords(im.shape[2:], det[:, :4], img.shape).round()
# Write results
for *xyxy, conf, cls in reversed(det):
c = int(cls) # integer class
label = f''
lbl = names[int(cls)]
#print(lbl)
#if lbl not in ['bus','truck','car']:
#continue
cal_detect.append([label, xyxy,float(conf)])
return cal_detect
def detect(self, info1):
image = cv2.imread(info1)
results = self.run(self.model, image, self.stride, self.pt) # 识别, 返回多个数组每个第一个为结果,第二个为坐标位置
count = 0
for i in results:
count += 1
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(count), (120, 220),
cv2.FONT_HERSHEY_SIMPLEX, 10, (0, 0, 255), 10)
ui.showimg(image)
ui.printf('检测到 ' + str(count) + ' 鱼饵')
QApplication.processEvents()
class Thread_1(QThread):
def __init__(self, info1):
super().__init__()
self.info1 = info1
self.detector = ObjectDetection()
def run(self):
self.detector.detect(self.info1)
class Ui_MainWindow(object):
def __init__(self):
这个程序文件是一个基于PyQt5的图形用户界面(GUI)程序,用于鱼虾残饵量检测系统。程序中使用了目标检测库YOLOv5来进行鱼虾残饵的检测。
程序的主要功能包括:
程序的界面由一个主窗口和几个控件组成,包括一个标签(label)用于显示标题,一个标签(label_2)用于显示图像,一个文本浏览器(textBrowser)用于显示检测结果,以及几个按钮(pushButton_4、pushButton_5、pushButton、pushButton_3)用于触发不同的功能。
程序的运行流程是先加载模型,然后显示主窗口,等待用户操作。用户可以选择文件进行检测,也可以打开摄像头进行实时检测。检测结果会显示在图像和文本浏览器中。用户可以随时退出系统。
总体来说,这个程序文件实现了一个简单的鱼虾残饵量检测系统的图形界面,并通过YOLOv5模型进行目标检测。
这个程序文件是一个使用了DCNv2(Deformable Convolutional Networks)的模型。DCNv2是一种改进的卷积操作,可以自适应地学习卷积核的形状,从而提高模型在目标检测等任务中的性能。
该程序文件定义了两个类:DCNv2和Bottleneck_DCN。DCNv2类是DCNv2操作的实现,包括了卷积操作、偏置、偏移和掩码等参数。Bottleneck_DCN类是一个标准的瓶颈结构,包括了一个1x1卷积层和一个DCNv2层。
另外,程序文件还定义了一个C3_DCN类,它是一个使用DCNv2的C3模块。C3模块是一种常用的卷积神经网络模块,由多个Bottleneck_DCN组成。
这个程序文件主要用于构建使用DCNv2的模型,可以在目标检测等任务中应用。
整体功能和构架概述:
该项目是一个鱼虾残饵量检测系统,基于YOLOv5模型,并引入了可变形卷积DCN进行改进。程序包含了训练、验证、导出模型和图形用户界面等功能。具体构架如下:
训练部分:train.py文件负责训练YOLOv5模型,包括数据加载、模型创建、优化器、学习率调度器等。通过命令行参数指定超参数和设备等配置。
验证部分:val.py文件用于验证训练好的模型在自定义数据集上的准确性,计算模型的准确率和mAP等指标。
导出部分:export.py文件用于将训练好的模型导出为TorchScript、ONNX、CoreML、TensorFlow等格式,方便在其他平台上部署和使用。
图形用户界面部分:ui.py文件是一个基于PyQt5的图形用户界面(GUI)程序,用于鱼虾残饵量检测系统。用户可以选择文件进行检测,也可以打开摄像头进行实时检测。
模型构建部分:yolov5-DCN.py文件实现了使用可变形卷积DCN的YOLOv5模型,通过定义DCNv2和Bottleneck_DCN等类来构建网络结构。
其他模块:models目录下的common.py和experimental.py文件定义了一些常用的模块和实验性的网络模块,用于构建YOLOv5模型。utils目录下的各个文件包含了一些辅助函数和工具函数,用于数据处理、模型操作、日志记录等。
下面是每个文件的功能整理:
文件路径 | 功能 |
---|---|
export.py | 将YOLOv5模型导出为不同格式的文件 |
train.py | 训练YOLOv5模型 |
ui.py | 图形用户界面程序,用于鱼虾残饵量检测系统 |
val.py | 在自定义数据集上验证模型的准确性 |
yolov5-DCN.py | 使用可变形卷积DCN的YOLOv5模型 |
models\common.py | 定义了一些常用的模块和函数,用于构建网络结构 |
models\experimental.py | 定义了一些实验性的网络模块和辅助函数 |
models\tf.py | TensorFlow模型相关的函数和类 |
models\yolo.py | YOLOv5模型的定义和相关函数 |
models_init_.py | 模型模块的初始化文件 |
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\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 | 日志记录模块的初始化文件 |
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日志记录模块的初始化文件 |
卷积神经网络由于其构建模块中固定的几何结构,本质上受限于模型几何变换。为了提高卷积神经网络的转换建模能力,《Deformable Convolutional Networks》作者提出了两个模块:可变形卷积(deformable convolution)和可变形RoI池(deformable RoI pooling)。这两个模块均基于用额外的偏移来增加模块中的空间采样位置以及从目标任务中学习偏移的思想,而不需要额外的监督。
第一次证明了在深度神经网络中学习密集空间变换(dense spatial transformation)对于复杂的视觉任务是有效的
视觉识别中的一个关键挑战是如何适应对象比例、姿态、视点和零件变形中的几何变化或模型几何变换。一般有两种方法实现:
1)建立具有足够期望变化的训练数据集。这通常通过增加现有的数据样本来实现,例如通过仿射变换。但是训练成本昂贵而且模型参数庞大。
2)使用变换不变(transformation-invariant)的特征和算法。比如比较有名的SIFT(尺度不变特征变换)便是这一类的代表算法。
但以上的方法有两个缺点:
1)几何变换被假定为固定的和已知的,这些先验知识被用来扩充数据,设计特征和算法。为此,这个假设阻止了对具有未知几何变换的新任务的推广,从而导致这些几何变换可能没有被正确建模。
2)对于不变特征和算法进行手动设计,对于过于复杂的变换可能是困难的或不可行的。
卷积神经网络本质上局限于模拟大型未知转换。局限性源于CNN模块的固定几何结构:卷积单元在固定位置对输入特征图进行采样;池化层以固定比率降低特征矩阵分辨率;RoI(感兴趣区域)池化层将RoI分成固定的空间箱(spatial bins)等。缺乏处理几何变换的内部机制。
这种内部机制的缺乏会导致一些问题,举个例子。同一个CNN层中所有激活单元的感受野大小是相同的,但是这是不可取的。因为不同的位置可能对应于具有不同尺度或变形的对象,所以尺度或感受野大小的自适应确定对于具有精细定位的视觉识别是渴望的。
对于这些问题,作者提出了两个模块提高CNNs对几何变换建模的能力。
deformable convolution(可变形卷积)
将2D偏移量添加到标准卷积中的常规网格采样位置,使得采样网格能够自由变形。通过额外的卷积层,从前面的特征映射中学习偏移。因此,变形采用局部、密集和自适应的方式取决于输入特征。
deformable RoI pooling(可变形RoI池化)
为先前RoI池化的常规库(bin)分区中的每个库位置(bin partition)增加了一个偏移量。类似地,偏移是从前面的特征图和感兴趣区域中学习的,从而能够对具有不同形状的对象进行自适应部件定位(adaptive part localization)。
Deformable Convolution
2D卷积由两个步骤组成:
1)在输入特征图x xx上使用规则网格R RR进行采样。
2)把这些采样点乘不同权重w ww后相加。
网格R定义感受野大小和扩张程度,比如内核大小为3x3,扩张程度为1的网格R可以表示为:
R = { ( − 1 , − 1 ) , ( − 1 , 0 ) , … , ( 0 , 1 ) , ( 1 , 1 ) } R = {(-1,-1),(-1,0),\dots,(0,1),(1,1)}
R={(−1,−1),(−1,0),…,(0,1),(1,1)}
一般为小数,使用双线性插值进行处理。(把小数坐标分解到相邻的四个整数坐标点来计算结果)
具体操作如图所示:
首先对输入特征层进行一个普通的3x3卷积处理得到偏移域(offset field)。偏移域特征图具有与输入特征图相同的空间分辨率,channels维度2N对应于N个2D(xy两个方向)偏移。其中的N是原输入特征图上所具有的N个channels,也就是输入输出channels保持不变,这里xy两个channels分别对输出特征图上的一个channels进行偏移。确定采样点后就通过与相对应的权重w点乘相加得到输出特征图上该点最终值。
前面也提到过,由于这里xy两个方向所训练出来的偏移量一般来说是一个小数,那么为了得到这个点所对应的数值,会采用双线性插值的方法,从最近的四个邻近坐标点中计算得到该偏移点的数值,公式如下:
具体推理过程见:双线性插值原理
所有基于区域提议(RPN)的对象检测方法都使用RoI池话处理,将任意大小的输入矩形区域转换为固定大小的特征图。
一般为小数,需要使用双线性插值进行处理。
具体操作如图所示:
当时看这个部分的时候觉得有些突兀,明明RoI池化会将特征层转化为固定尺寸的区域。其实,我个人觉得,这个部分与上述的可变性卷积操作是类似的。这里同样是使用了一个普通的RoI池化操作,进行一些列处理后得到了一个偏移域特征图,然后重新作用于原来的w × H w \times Hw×H的RoI。只不过这里不再是规律的逐行逐列对每个格子进行池化,而是对于格子进行偏移后再池化处理。
除此之外,论文还提出一种PS RoI池化(Postion﹣Sensitive RoI Pooling)。不同于上述可变形RoI池化中的全连接过程,这里使用全卷积替换。
具体操作如图所示:
首先,对于原来的特征图来说,原本是将输入特征图上的RoI区域分成k × k k\times kk×k个bin。而在这里,则是将输入特征图进行卷积操作,分别得到一个channels为k 2 ( C + 1 ) k^{2}(C+1)k (C+1)的得分图(score maps)和一个channels为2 k 2 ( C + 1 ) 2k{2}(C+1)2k 2 (C+1)的偏移域(offset fields),这两个特征矩阵的宽高是与输入特征矩阵相同的。其中,得分图的channels中,k × k k \times kk×k分别表示的是每一个网格,C CC表示的检测对象的类别数目,1表示背景。而在偏移域中的2表示xy两个方向的偏移。
也就是说,在PS RoI池化中,对于RoI的每一个网格都独自占一个通道形成一层得分图,然后其对于的偏移量占两个通道。offset fields得到的偏移是归一化后的偏移,需要通过和deformable RoI pooling中一样的变换方式得到∆ p i j ∆p_{ij}∆p ij,然后对每层得分图进行偏移池化处理。最后处理完的结果就对应着最后输出的一个网格。所以其包含了位置信息。
原文论述为:
当可变形卷积叠加时,复合变形的效果是深远的。如图所示:
ps:a是标准卷积的固定感受野,b是可变形卷积的适应性感受野。
感受野和标准卷积中的采样位置在整个顶部特征图上是固定的(左)。在可变形卷积中,它们根据对象的比例和形状进行自适应调整(右)。
在深度学习和计算机领域,YOLO(You Only Look Once)系列是一种非常流行的实时工件检测算法。YOLOv5是这个系列中的一个版本,以实现快速和准确着称。然而,尽管YOLOv5在任务上表现,它在处理形变明显增大的物体时可能会遇到困难。这是因为YOLOv5的神经网络(CNN)结构在设计时假设物体的形态是相对固定和规则的。
为了解决这个问题,近期我们引入了可变形卷积网络(Deformable Convolutional Networks,简称DCN),旨在提高YOLOv5在检测形状变化更大或姿势多样的物体时的准确性。以下是对“引入”可变形DCN改进YOLOv5”网络结构改进部分的详细描述。
可变形的视觉网络是一种特殊的视觉网络,它可以动态地改变其视觉视觉的形状以适应视觉的形态。在标准的视觉视觉中,视觉视觉的尺寸和形状是固定的,例如3x3或5x5的格局。然而,在DCN中,表面清晰的点每个都可以根据输入数据的特征进行偏移,允许表面清晰以非常灵活的方式适应物体的形状和结构。增强了网络对目标形状变化的预见性,从而提高了检测的准确性。
在改进YOLOv5时,我们将DCN整合到了网络结构的多个层次。传统YOLOv5的架构主要由格式层、批量归一化层和激活层组成。在改进后的模型中,特定的标准卷这些改变主要发生在网络的中间和晚期层,因为这些层负责捕获更高级别的抽象特征,这对于理解视线的形态和结构至关重要。
除了替换DCN层,中断们还引入了新的点生成策略,以适应可变形层的输出。由于可变形图层能够更好地捕捉物体的形状,某个点生成策略也需要调整利用这些精确的特征。某一点物体是检测中的一个概念,指预定义的框架,它们作为网络预测物体位置的更基准。
# YOLOv5-DCN
# 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
# YOLOv5 v6.0 backbone
backbone:
# [from, number, module, args]
[[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2
[-1, 1, Conv, [128, 3, 2]], # 1-P2/4
[-1, 3, C3, [128]],
[-1, 1, Conv, [256, 3, 2]], # 3-P3/8
[-1, 6, C3, [256]],
[-1, 1, Conv, [512, 3, 2]], # 5-P4/16
[-1, 9, C3, [512]],
[-1, 1, Conv, [1024, 3, 2]], # 7-P5/32
[-1, 3, C3_DCN, [1024]],
[-1, 1, SPPF, [1024, 5]], # 9
]
# YOLOv5 v6.0 head
head:
[[-1, 1, Conv, [512, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 6], 1, Concat, [1]], # cat backbone P4
[-1, 3, C3, [512, False]], # 13
[-1, 1, Conv, [256, 1, 1]],
[-1, 1, nn.Upsample, [None, 2, 'nearest']],
[[-1, 4], 1, Concat, [1]], # cat backbone P3
[-1, 3, C3, [256, False]], # 17 (P3/8-small)
[-1, 1, Conv, [256, 3, 2]],
[[-1, 14], 1, Concat, [1]], # cat head P4
[-1, 3, C3, [512, False]], # 20 (P4/16-medium)
[-1, 1, Conv, [512, 3, 2]],
[[-1, 10], 1, Concat, [1]], # cat head P5
[-1, 3, C3, [1024, False]], # 23 (P5/32-large)
[[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5)
]
框损失(训练和验证中):表示模型预测目标周围边界框的效果如何。
对象损失(训练和验证中):反映模型检测对象的准确性。
类别损失(训练和验证中):表示对检测到的对象进行分类的性能。
Precision、Recall、mAP(平均平均精度)为 0.5,mAP 为 0.5:0.95:这些是对象检测模型的关键指标,其中精度衡量正预测的准确性,召回衡量正确识别的正预测的比例,以及mAP 提供了模型在不同阈值下的准确性的总体衡量标准。
学习率(lr0、lr1、lr2):这些值表示训练过程中使用的学习率。
为了进行全面分析,我将创建几个可视化来说明这些指标在训练过程中如何演变。这些可视化将有助于理解模型随时间的表现和行为。生成这些可视化后,我将提供详细的分析。让我们从创建可视化开始。
import matplotlib.pyplot as plt
import seaborn as sns
# Set the aesthetic style of the plots
sns.set_style("whitegrid")
def plot_metrics(df, metric_names, title):
plt.figure(figsize=(12, 6))
for metric in metric_names:
plt.plot(df['epoch'], df[metric], label=metric)
plt.title(title)
plt.xlabel('Epoch')
plt.ylabel('Value')
plt.legend()
plt.show()
# Plotting the loss metrics for training and validation
plot_metrics(data, ['train/box_loss', 'train/obj_loss', 'train/cls_loss'], 'Training Loss Metrics')
plot_metrics(data, ['val/box_loss', 'val/obj_loss', 'val/cls_loss'], 'Validation Loss Metrics')
# Plotting precision, recall, and mAP metrics
plot_metrics(data, ['metrics/precision', 'metrics/recall', 'metrics/mAP_0.5', 'metrics/mAP_0.5:0.95'], 'Precision, Recall, and mAP Metrics')
# Plotting the learning rate changes
plot_metrics(data, ['x/lr0', 'x/lr1', 'x/lr2'], 'Learning Rate Changes over Epochs')
可视化提供了实验进度和性能的详细视图。让我们深入分析这些结果:
Box Loss、Object Loss 和 Class Loss:所有这些指标都显示出随时间推移而下降的趋势,表明模型正在有效地学习。框损失的减少表明预测边界框的准确性得到提高。同样,随着时间的推移,减少对象损失和类别损失意味着更好的对象检测和分类。
验证损失指标反映了训练损失指标,显示出持续的下降。这是一个积极的信号,因为它表明模型具有良好的泛化能力,并且没有过度拟合训练数据。
精度:该指标从零开始,并显示出显着的增加,表明模型正确识别正实例的能力随着训练的进展而提高。
召回率:召回率的逐渐增加表明模型越来越有能力找到数据中的所有相关实例。
mAP(平均平均精度):mAP_0.5 和 mAP_0.5:0.95 均呈现上升趋势。mAP_0.5 通常高于 mAP_0.5:0.95,这是预期的,因为后者是更严格的措施。这些指标的增加表明模型准确性和稳健性的整体提高。
学习率(lr0、lr1、lr2)似乎是恒定的或遵循特定的时间表。学习率的变化会显着影响训练过程,影响收敛的速度和稳定性。
混淆矩阵显示“提要”的真阳性率很高,表明该模型在正确识别提要方面非常有效。假阴性极少,这表明该模型很少会错过存在的提要。“背景”没有误报意味着该模型非常精确,并且不会错误地将背景识别为提要。背景真阴性的满分 1.00 表明该模型在识别非饲料区域方面表现出色,没有任何错误。
F1 分数是精确率和召回率的调和平均值,提供平衡这两个问题的单一分数。您提供的曲线显示 F1 分数在某个置信度阈值处达到峰值,表明精确度和召回率达到平衡的最佳点。该模型的最高 F1 分数表明其预测的稳健性,因为它保持较高的真阳性率,同时保持较低的误报和漏报率。
精度曲线显示了精度和置信度之间的关系。精确度是指相关结果的百分比。在这种情况下,在较低的置信水平下,精度开始较低,这表明当模型置信度较低时,更有可能出现误报错误。随着置信度的提高,精度也会提高,这意味着模型的预测更有可能是正确的。高置信水平的稳定表明该模型对其预测的很大一部分具有很强的确定性。
召回曲线表明模型在数据集中查找所有相关案例(提要)的能力。即使置信度阈值较低,召回率也很高,这意味着即使模型不太确定大多数提要,它也能够检测到它们。这是一个理想的特性,特别是在缺少目标对象实例(在本例中为提要)可能会产生严重后果的应用程序中。
该曲线是不同阈值的精度(y 轴)和召回率(x 轴)的图。这是评估二元分类模型性能的另一个工具。它显示了不同阈值的精度和召回率之间的权衡。曲线下面积大代表高召回率和高准确率,其中高准确率与低假阳性率相关,高召回率与低假阴性率相关。所提供的精度-召回率曲线中模型的性能表明,随着召回率的增加,它保持了较高的精度,这表明模型具有良好的预测性能。
模型性能:损失指标和准确性指标(精度、召回率、mAP)的不断改进表明,通过可变形卷积网络(DCN)增强的YOLOv5模型正在有效地学习检测和分类鱼虾饵料残留。训练和验证指标的提高表明了良好的泛化能力。
学习率:学习率策略似乎是有效的,模型性能指标的持续改进就证明了这一点。探索不同的学习率方案是否可以导致更快的收敛或更高的最终精度将会很有趣。
未来的改进:为了进一步增强模型性能,可以考虑尝试不同的架构、数据增强技术或更复杂的学习率计划。此外,进行错误分析以了解模型所产生的错误类型(误报、误报)可以为进一步细化提供见解。
结果表明,DCN 成功应用于改进 YOLOv5,用于检测鱼虾饵料残留。该模型在学习效率和准确性方面显示出可喜的结果,使其成为环境监测或水产养殖管理等相关应用中潜在有价值的工具。在现实场景中的进一步优化和测试可以更深入地了解其实际效果。
下图[完整源码&数据集&环境部署视频教程&自定义UI界面]
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。