背景意义
随着现代战争的日益复杂化,空中作战力量的有效识别与分类变得愈发重要。直升机作为一种灵活多变的空中作战平台,其在战场上的作用不可小觑。为了提升对直升机及其他空中目标的识别能力,基于深度学习的目标检测技术应运而生。其中,YOLO(You Only Look Once)系列模型因其高效的实时检测能力而备受关注。YOLOv11作为该系列的最新版本,具备更强的特征提取能力和更快的推理速度,适合在复杂的战场环境中进行直升机类型的识别。
本研究旨在基于改进的YOLOv11模型,构建一个高效的直升机战机类型识别目标检测系统。该系统将利用"Helicopters-of-DC"数据集,该数据集包含5500张图像,涵盖了多种直升机型号,包括A139、AS50、CH47等共计3类目标。通过对这些图像的深入分析与处理,系统能够在多种环境下准确识别不同类型的直升机及其他空中目标,如气球、鸟类和飞机等。这不仅为军事侦察提供了技术支持,也为无人机等新型空中作战平台的应用提供了数据基础。
此外,随着人工智能技术的快速发展,基于YOLOv11的目标检测系统在实际应用中的潜力巨大。其不仅可以应用于军事领域,还可以扩展到民用航空安全、交通监控等多个领域。通过本研究的实施,将推动目标检测技术的进一步发展,为相关领域的研究提供新的思路和方法。综上所述,本项目不仅具有重要的学术价值,也具备广泛的应用前景。
图片效果
数据集信息
本项目所使用的数据集名为"Helicopters-of-DC",旨在为改进YOLOv11的直升机战机类型识别目标检测系统提供支持。该数据集包含丰富的直升机及相关飞行器图像,经过精心标注,以确保模型训练的准确性和有效性。数据集分为三个主要部分:训练集、验证集和测试集,分别存放于指定的路径下。训练集包含大量的图像数据,供模型学习和优化;验证集用于在训练过程中监测模型的性能;测试集则用于最终评估模型的泛化能力。
该数据集涵盖了24个类别,具体包括多种直升机型号,如A139、AS50、B06、B412、B429、CH47等,以及一些其他飞行器,如鸟类和飞机。这些类别的选择不仅考虑了直升机的多样性,还包括了一些可能在训练和测试过程中出现的干扰目标,以增强模型的鲁棒性。通过对这些类别的精确标注,数据集为目标检测任务提供了丰富的样本,帮助模型更好地识别和分类不同类型的直升机。
数据集的设计遵循了BY-NC-SA 4.0的许可协议,确保了其在学术研究和开发中的合理使用。数据集的版本为3,反映了其在不断更新和完善的过程中所积累的经验和反馈。通过利用"Helicopters-of-DC"数据集,研究人员能够有效地训练和评估YOLOv11模型,从而提升直升机战机类型识别的准确性,为相关领域的应用提供更为强大的技术支持。
核心代码
以下是经过简化并添加详细中文注释的核心代码部分:
import os
import torch
from ultralytics.engine.validator import BaseValidator
from ultralytics.utils.metrics import DetMetrics, box_iou
from ultralytics.utils import LOGGER, ops
class DetectionValidator(BaseValidator):
"""
继承自BaseValidator类,用于基于检测模型的验证。
"""
def __init__(self, dataloader=None, save_dir=None, args=None):
"""初始化检测模型所需的变量和设置。"""
super().__init__(dataloader, save_dir, args)
self.metrics = DetMetrics(save_dir=self.save_dir) # 初始化检测指标
self.iouv = torch.linspace(0.5, 0.95, 10) # 定义IoU向量用于mAP计算
def preprocess(self, batch):
"""对YOLO训练的图像批次进行预处理。"""
# 将图像数据转移到设备上并进行归一化处理
batch["img"] = batch["img"].to(self.device, non_blocking=True) / 255
# 将其他相关数据转移到设备上
for k in ["batch_idx", "cls", "bboxes"]:
batch[k] = batch[k].to(self.device)
return batch
def postprocess(self, preds):
"""对预测输出应用非极大值抑制(NMS)。"""
return ops.non_max_suppression(
preds,
self.args.conf,
self.args.iou,
multi_label=True,
max_det=self.args.max_det,
)
def update_metrics(self, preds, batch):
"""更新检测指标。"""
for si, pred in enumerate(preds):
npr = len(pred) # 当前预测的数量
pbatch = self._prepare_batch(si, batch) # 准备当前批次的数据
cls, bbox = pbatch.pop("cls"), pbatch.pop("bbox") # 获取真实标签
if npr == 0:
continue # 如果没有预测结果,跳过
# 处理预测结果
predn = self._prepare_pred(pred, pbatch) # 准备预测数据
stat = dict(conf=predn[:, 4], pred_cls=predn[:, 5]) # 记录置信度和预测类别
stat["tp"] = self._process_batch(predn, bbox, cls) # 计算真阳性
self.stats["tp"].append(stat["tp"]) # 更新统计信息
def _process_batch(self, detections, gt_bboxes, gt_cls):
"""
返回正确的预测矩阵。
"""
iou = box_iou(gt_bboxes, detections[:, :4]) # 计算IoU
return self.match_predictions(detections[:, 5], gt_cls, iou) # 匹配预测与真实标签
def get_stats(self):
"""返回指标统计信息和结果字典。"""
stats = {k: torch.cat(v, 0).cpu().numpy() for k, v in self.stats.items()} # 转换为numpy数组
if len(stats) and stats["tp"].any():
self.metrics.process(**stats) # 处理指标
return self.metrics.results_dict # 返回结果字典
def print_results(self):
"""打印每个类别的训练/验证集指标。"""
pf = "%22s" + "%11i" * 2 + "%11.3g" * len(self.metrics.keys) # 打印格式
LOGGER.info(pf % ("all", self.seen, self.nt_per_class.sum(), *self.metrics.mean_results())) # 打印总体结果
代码说明:
类的定义:DetectionValidator类用于YOLO模型的验证,继承自BaseValidator。
初始化方法:__init__方法中初始化了一些重要的变量和检测指标。
预处理方法:preprocess方法对输入的图像批次进行预处理,包括将图像数据归一化和转移到指定设备。
后处理方法:postprocess方法使用非极大值抑制(NMS)来过滤预测结果。
更新指标:update_metrics方法根据预测结果和真实标签更新检测指标。
处理批次:_process_batch方法计算IoU并匹配预测与真实标签。
获取统计信息:get_stats方法返回当前的指标统计信息。
打印结果:print_results方法打印每个类别的验证结果。
通过这些核心部分的注释,可以更好地理解YOLO模型验证的过程和各个方法的功能。
这个程序文件 val.py 是一个用于YOLO(You Only Look Once)目标检测模型验证的实现。它继承自 BaseValidator 类,提供了一系列方法来处理数据、计算指标和输出结果。
在初始化时,DetectionValidator 类接收一些参数,包括数据加载器、保存目录、进度条、参数和回调函数。它设置了一些必要的变量,如每类的目标数量、是否使用COCO数据集、类别映射等,并初始化了用于计算检测指标的 DetMetrics 实例。
preprocess 方法负责对输入的图像批次进行预处理,包括将图像转换为合适的格式和范围,并根据需要进行自动标注的准备。
init_metrics 方法初始化评估指标,确定数据集的类型(是否为COCO),并准备混淆矩阵和其他统计信息。
get_desc 方法返回一个格式化的字符串,用于总结YOLO模型的类指标。
postprocess 方法应用非极大值抑制(NMS)来处理模型的预测输出,以去除冗余的检测框。
_prepare_batch 和 _prepare_pred 方法分别用于准备输入批次和预测结果,以便进行后续的指标计算。
update_metrics 方法负责更新模型的检测指标,包括处理每个批次的预测结果和真实标签,并根据需要保存结果到文件。
finalize_metrics 方法设置最终的指标值和混淆矩阵。
get_stats 方法返回计算得到的指标统计信息,并更新每个类别的目标数量。
print_results 方法打印训练或验证集的每个类别的指标结果,并在需要时绘制混淆矩阵。
_process_batch 方法计算正确的预测矩阵,用于评估模型的性能。
build_dataset 和 get_dataloader 方法用于构建YOLO数据集和返回数据加载器,以便进行验证。
plot_val_samples 和 plot_predictions 方法用于绘制验证图像样本和预测结果,并将其保存为图像文件。
save_one_txt 方法将YOLO检测结果保存为文本文件,采用规范化的坐标格式。
pred_to_json 方法将YOLO的预测结果序列化为COCO格式的JSON文件,以便进行进一步的评估。
eval_json 方法评估YOLO输出的JSON格式结果,并返回性能统计信息,使用COCO API计算mAP(平均精度)。
整个程序的结构清晰,功能完备,涵盖了YOLO模型验证的各个方面,包括数据处理、指标计算、结果输出等,适合用于目标检测任务的模型评估。
10.3 kagn_conv.py
以下是经过简化并添加详细中文注释的核心代码部分:
import torch
import torch.nn as nn
from functools import lru_cache
class KAGNConvNDLayer(nn.Module):
def init (self, conv_class, norm_class, conv_w_fun, input_dim, output_dim, degree, kernel_size,
groups=1, padding=0, stride=1, dilation=1, dropout: float = 0.0, ndim: int = 2):
super(KAGNConvNDLayer, self).init()
# 初始化参数
self.inputdim = input_dim # 输入维度
self.outdim = output_dim # 输出维度
self.degree = degree # 多项式的度数
self.kernel_size = kernel_size # 卷积核大小
self.padding = padding # 填充
self.stride = stride # 步幅
self.dilation = dilation # 膨胀
self.groups = groups # 分组卷积的组数
self.base_activation = nn.SiLU() # 基础激活函数
self.conv_w_fun = conv_w_fun # 卷积权重函数
self.ndim = ndim # 数据的维度
self.dropout = None # Dropout层
# 根据维度选择合适的Dropout层
if dropout > 0:
if ndim == 1:
self.dropout = nn.Dropout1d(p=dropout)
elif ndim == 2:
self.dropout = nn.Dropout2d(p=dropout)
elif ndim == 3:
self.dropout = nn.Dropout3d(p=dropout)
# 验证输入参数的有效性
if groups <= 0:
raise ValueError('groups must be a positive integer')
if input_dim % groups != 0:
raise ValueError('input_dim must be divisible by groups')
if output_dim % groups != 0:
raise ValueError('output_dim must be divisible by groups')
# 创建基础卷积层和归一化层
self.base_conv = nn.ModuleList([conv_class(input_dim // groups,
output_dim // groups,
kernel_size,
stride,
padding,
dilation,
groups=1,
bias=False) for _ in range(groups)])
self.layer_norm = nn.ModuleList([norm_class(output_dim // groups) for _ in range(groups)])
# 多项式权重的形状
poly_shape = (groups, output_dim // groups, (input_dim // groups) * (degree + 1)) + tuple(
kernel_size for _ in range(ndim))
# 初始化多项式权重和beta权重
self.poly_weights = nn.Parameter(torch.randn(*poly_shape))
self.beta_weights = nn.Parameter(torch.zeros(degree + 1, dtype=torch.float32))
# 使用Kaiming均匀分布初始化卷积层权重
for conv_layer in self.base_conv:
nn.init.kaiming_uniform_(conv_layer.weight, nonlinearity='linear')
nn.init.kaiming_uniform_(self.poly_weights, nonlinearity='linear')
nn.init.normal_(
self.beta_weights,
mean=0.0,
std=1.0 / ((kernel_size ** ndim) * self.inputdim * (self.degree + 1.0)),
)
def beta(self, n, m):
# 计算beta值,用于Legendre多项式
return (
((m + n) * (m - n) * n ** 2) / (m ** 2 / (4.0 * n ** 2 - 1.0))
) * self.beta_weights[n]
@lru_cache(maxsize=128) # 使用缓存避免重复计算Legendre多项式
def gram_poly(self, x, degree):
# 计算Legendre多项式
p0 = x.new_ones(x.size()) # p0 = 1
if degree == 0:
return p0.unsqueeze(-1)
p1 = x # p1 = x
grams_basis = [p0, p1] # 存储多项式基
for i in range(2, degree + 1):
p2 = x * p1 - self.beta(i - 1, i) * p0 # 递归计算
grams_basis.append(p2)
p0, p1 = p1, p2 # 更新p0和p1
return torch.cat(grams_basis, dim=1) # 返回所有多项式基
def forward_kag(self, x, group_index):
# 前向传播过程
basis = self.base_conv[group_index](self.base_activation(x)) # 计算基础卷积
x = torch.tanh(x).contiguous() # 将输入标准化到[-1, 1]
if self.dropout is not None:
x = self.dropout(x) # 应用Dropout
grams_basis = self.base_activation(self.gram_poly(x, self.degree)) # 计算Gram多项式基
# 使用卷积权重函数计算输出
y = self.conv_w_fun(grams_basis, self.poly_weights[group_index],
stride=self.stride, dilation=self.dilation,
padding=self.padding, groups=1)
y = self.base_activation(self.layer_norm[group_index](y + basis)) # 归一化并激活
return y
def forward(self, x):
# 前向传播,处理输入
split_x = torch.split(x, self.inputdim // self.groups, dim=1) # 按组分割输入
output = []
for group_ind, _x in enumerate(split_x):
y = self.forward_kag(_x.clone(), group_ind) # 计算每组的输出
output.append(y.clone())
y = torch.cat(output, dim=1) # 合并所有组的输出
return y
代码说明:
KAGNConvNDLayer: 这是一个自定义的卷积层,支持多维卷积(1D、2D、3D),结合了Legendre多项式的计算和分组卷积的特性。
初始化参数: 在构造函数中,初始化输入输出维度、卷积参数、激活函数等,并创建相应的卷积和归一化层。
beta和gram_poly方法: 计算Legendre多项式和相关的beta值,用于后续的卷积计算。
forward_kag方法: 实现了每个组的前向传播逻辑,包括基础卷积、Dropout、Gram多项式计算和最终输出的归一化。
forward方法: 将输入按组分割,依次计算每组的输出,并合并结果。
这个程序文件 kagn_conv.py 定义了一组用于卷积神经网络的自定义层,主要包括多维卷积层的实现。程序中使用了 PyTorch 框架,利用其提供的模块和功能来构建卷积层。核心类是 KAGNConvNDLayer,它是一个通用的多维卷积层,可以根据需要扩展为一维、二维或三维卷积层。
在 KAGNConvNDLayer 的构造函数中,首先初始化了一些参数,包括输入和输出维度、卷积核大小、步幅、填充、扩张、分组等。根据输入的维度,选择相应的 dropout 方法。接着,程序会检查分组数是否为正整数,并确保输入和输出维度可以被分组数整除。
该类还定义了基础卷积层和归一化层,使用 nn.ModuleList 来存储多个卷积和归一化层,以支持分组卷积。接下来,程序定义了多项式权重和 beta 权重,并使用 Kaiming 均匀分布初始化这些权重,以提高训练的起始效果。
beta 方法用于计算与 Legendre 多项式相关的 beta 值,而 gram_poly 方法则用于计算给定输入和多项式阶数的 Gram 多项式。为了避免重复计算,gram_poly 方法使用了 LRU 缓存。
在 forward_kag 方法中,首先对输入应用基础激活函数,然后进行线性变换。接着,将输入归一化到 [-1, 1] 的范围,以便进行稳定的 Legendre 多项式计算。随后,如果设置了 dropout,则对输入应用 dropout。接下来,计算 Gram 基础并通过卷积权重函数进行卷积操作,最后通过归一化层和激活函数得到输出。
forward 方法将输入张量按组分割,并对每个组调用 forward_kag 方法进行处理,最后将所有组的输出拼接在一起。
此外,程序还定义了三个具体的卷积层类:KAGNConv3DLayer、KAGNConv2DLayer 和 KAGNConv1DLayer,分别用于三维、二维和一维卷积。这些类通过调用 KAGNConvNDLayer 的构造函数,传入相应的卷积和归一化类来实现具体的卷积操作。
整体而言,这个程序文件提供了一种灵活的方式来实现多维卷积层,结合了多项式特性和分组卷积的优势,适用于各种深度学习任务。
10.4 shiftwise_conv.py
以下是代码中最核心的部分,并附上详细的中文注释:
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
def get_conv2d(in_channels, out_channels, kernel_size, stride, padding, dilation, groups, bias):
创建一个2D卷积层
return nn.Conv2d(
in_channels, out_channels, kernel_size, stride, padding, dilation, groups, bias
)
def get_bn(channels):
创建一个批归一化层
return nn.BatchNorm2d(channels)
class Mask(nn.Module):
def init (self, size):
super().init ()
初始化权重参数,范围在-1到1之间
self.weight = torch.nn.Parameter(data=torch.Tensor(*size), requires_grad=True)
self.weight.data.uniform_(-1, 1)
def forward(self, x):
# 通过sigmoid函数对权重进行归一化,并与输入x相乘
w = torch.sigmoid(self.weight)
masked_wt = w.mul(x)
return masked_wt
class ReparamLargeKernelConv(nn.Module):
def init (self, in_channels, out_channels, kernel_size, small_kernel=5, stride=1, groups=1, small_kernel_merged=False, Decom=True, bn=True):
super(ReparamLargeKernelConv, self).init ()
self.kernel_size = kernel_size
self.small_kernel = small_kernel
self.Decom = Decom
padding = kernel_size // 2 # 计算填充大小
if small_kernel_merged: # 如果合并小卷积
self.lkb_reparam = get_conv2d(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=kernel_size,
stride=stride,
padding=padding,
dilation=1,
groups=groups,
bias=True,
)
else:
if self.Decom: # 如果使用分解
self.LoRA = conv_bn(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=(kernel_size, small_kernel),
stride=stride,
padding=padding,
dilation=1,
groups=groups,
bn=bn
)
else:
self.lkb_origin = conv_bn(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=kernel_size,
stride=stride,
padding=padding,
dilation=1,
groups=groups,
bn=bn,
)
if (small_kernel is not None) and small_kernel < kernel_size:
self.small_conv = conv_bn(
in_channels=in_channels,
out_channels=out_channels,
kernel_size=small_kernel,
stride=stride,
padding=small_kernel // 2,
groups=groups,
dilation=1,
bn=bn,
)
self.bn = get_bn(out_channels) # 批归一化层
self.act = nn.SiLU() # 激活函数
def forward(self, inputs):
# 前向传播
if hasattr(self, "lkb_reparam"):
out = self.lkb_reparam(inputs) # 使用重参数化卷积
elif self.Decom:
out = self.LoRA(inputs) # 使用LoRA卷积
if hasattr(self, "small_conv"):
out += self.small_conv(inputs) # 加上小卷积的输出
else:
out = self.lkb_origin(inputs) # 使用原始卷积
if hasattr(self, "small_conv"):
out += self.small_conv(inputs) # 加上小卷积的输出
return self.act(self.bn(out)) # 返回经过激活和归一化的输出
def get_equivalent_kernel_bias(self):
# 获取等效的卷积核和偏置
eq_k, eq_b = fuse_bn(self.lkb_origin.conv, self.lkb_origin.bn)
if hasattr(self, "small_conv"):
small_k, small_b = fuse_bn(self.small_conv.conv, self.small_conv.bn)
eq_b += small_b # 累加偏置
eq_k += nn.functional.pad(small_k, [(self.kernel_size - self.small_kernel) // 2] * 4) # 对小卷积核进行填充
return eq_k, eq_b
def switch_to_deploy(self):
# 切换到部署模式
if hasattr(self, 'lkb_origin'):
eq_k, eq_b = self.get_equivalent_kernel_bias() # 获取等效卷积核和偏置
self.lkb_reparam = get_conv2d(
in_channels=self.lkb_origin.conv.in_channels,
out_channels=self.lkb_origin.conv.out_channels,
kernel_size=self.lkb_origin.conv.kernel_size,
stride=self.lkb_origin.conv.stride,
padding=self.lkb_origin.conv.padding,
dilation=self.lkb_origin.conv.dilation,
groups=self.lkb_origin.conv.groups,
bias=True,
)
self.lkb_reparam.weight.data = eq_k # 设置卷积核权重
self.lkb_reparam.bias.data = eq_b # 设置偏置
self.__delattr__("lkb_origin") # 删除原始卷积属性
if hasattr(self, "small_conv"):
self.__delattr__("small_conv") # 删除小卷积属性
代码说明
get_conv2d 和 get_bn: 这两个函数分别用于创建卷积层和批归一化层。
Mask: 该类定义了一个掩码机制,通过sigmoid函数对权重进行归一化,并与输入相乘。
ReparamLargeKernelConv: 这是一个自定义的卷积层,支持大卷积核和小卷积核的组合。根据参数选择不同的卷积方式(重参数化、分解或原始卷积),并在前向传播中处理输入。
前向传播: 根据模型的配置,选择合适的卷积层进行计算,并在最后应用激活函数和批归一化。
get_equivalent_kernel_bias: 该方法用于获取等效的卷积核和偏置,方便在部署时使用。
switch_to_deploy: 切换到部署模式,优化模型以提高推理速度。
这个程序文件 shiftwise_conv.py 实现了一个用于深度学习卷积操作的模块,主要包括了大核卷积和小核卷积的组合,以及通过参数重参数化(Reparameterization)来优化卷积层的性能。文件中定义了多个类和函数,下面对其进行逐一说明。
首先,文件导入了必要的库,包括 math、torch 及其子模块 torch.nn 和 torch.nn.functional。这些库提供了构建神经网络所需的基础功能。
接下来,定义了一个函数 get_conv2d,用于创建一个二维卷积层。该函数接受多个参数,如输入通道数、输出通道数、卷积核大小、步幅、填充、扩张、分组和是否使用偏置等。它会根据给定的卷积核大小计算填充,并返回一个 nn.Conv2d 对象。
get_bn 函数用于创建一个批归一化层,接受通道数作为参数。
Mask 类是一个自定义的模块,它的构造函数初始化了一个可训练的权重参数,并在前向传播中通过 Sigmoid 函数对权重进行激活,随后将其与输入相乘,形成一个掩码效果。
conv_bn_ori 函数用于创建一个包含卷积层和可选的批归一化层的序列。根据传入的参数,构建相应的卷积层并添加批归一化层。
LoRAConvsByWeight 类实现了一个特殊的卷积结构,它结合了大核和小核卷积的特性,并通过权重进行通道的混洗。该类的构造函数中,首先计算了填充和索引,然后定义了多个卷积层和掩码层。在前向传播中,输入数据经过小卷积层后,输出被分成多个部分,并通过 forward_lora 方法进行处理,最终将处理后的结果相加。
forward_lora 方法负责处理每个分组的输出,计算数据的重新排列和填充,并根据需要进行批归一化。
rearrange_data 方法用于重新排列数据,计算填充和滑动窗口的起始位置,以确保卷积操作的正确性。
shift 方法用于计算填充、窗口的起始索引等参数,以确保卷积操作不改变特征图的大小。
conv_bn 函数是一个工厂函数,根据传入的卷积核大小创建相应的卷积和批归一化层,支持大核和小核的组合。
fuse_bn 函数用于将卷积层和批归一化层融合,返回融合后的卷积核和偏置。
ReparamLargeKernelConv 类是该文件的核心部分,它实现了一个重参数化的大核卷积层。构造函数中根据输入参数创建卷积层,并根据需要添加小卷积层。前向传播方法根据条件选择不同的卷积路径,并在最后应用激活函数和批归一化。
该类还包含 get_equivalent_kernel_bias 方法,用于获取融合后的卷积核和偏置,以及 switch_to_deploy 方法,用于在部署时将卷积层转换为重参数化的形式,以提高推理效率。
整体来看,这个文件实现了一个灵活且高效的卷积模块,支持多种卷积核组合和重参数化策略,适用于深度学习模型的构建和优化。
源码文件

源码获取
欢迎大家点赞、收藏、关注、评论 啦 、查看👇🏻获取联系方式👇🏻