yolov26改进 | 图像复原篇 | 低照度增强网络PE-YOLO改进主干(改进暗光条件下的物体检测模型,全网独家首发改进)

📅 2026/7/4 19:39:30
yolov26改进 | 图像复原篇 | 低照度增强网络PE-YOLO改进主干(改进暗光条件下的物体检测模型,全网独家首发改进)
开始讲解之前推荐一下我的专栏本专栏的内容支持(分类、检测、分割、追踪、关键点检测),专栏目前为限时折扣欢迎大家订阅本专栏本专栏每周更新3-5篇最新机制更有包含我所有改进的文件和交流群提供给大家。一、本文介绍本文给大家带来的改进机制是将PE-YOLO 中的 PENet 低照度图像增强网络融合到 YOLOv26 中主要针对夜间、弱光、曝光不足、画面发暗、目标细节不清楚等场景下的检测问题进行优化。实际检测任务中很多低照度图片并不是单纯“变暗”这么简单往往还会伴随边缘模糊、纹理丢失、噪声增多、小目标不明显等问题直接送入 YOLOv26 进行检测时模型很容易出现漏检、误检或者定位不准。PENet 的优势在于它会通过拉普拉斯金字塔将图像拆成不同层次的信息一边增强目标的边缘和细节一边补充整体亮度和低频语义信息让暗光图片变得更加清楚、更有层次感。其中DPM 细节处理模块可以帮助模型恢复更多细节和边缘信息LEF 低频增强滤波器则可以加强低频特征并减少无用噪声相当于在 YOLOv26 检测前先帮图片完成一次“提亮、去噪、补细节”。这个改进非常适合低照度检测、夜间检测、监控检测、隧道道路检测以及图像质量较差的数据集同时它属于前端图像增强思路不影响 YOLOv26 的主干网络、Neck、检测头以及其它注意力机制的继续改进后续还可以叠加其它模块做融合创新。并且原始发布版本并不完善存在一定Bug 和二次创新空间我这里已经对相关问题进行了修复方便大家直接接入自己的数据集进行实验用来丰富论文改进点、增加工作量并提升复杂低光场景下的检测效果。欢迎大家订阅我的专栏一起学习YOLO专栏链接YOLOv26有效涨点专栏包含Conv、注意力机制、主干/Backbone、损失函数、优化器、后处理等改进机制目录一、本文介绍二、PE-YOLO算法原理2.1 PE-YOLO的基本原理2.2 金字塔增强网络2.3 细节处理模块2.4 低频增强滤波器三、PE-YOLO的核心代码四、PE-YOLO的添加方式4.1 修改一4.2 修改二4.3 修改三4.4 关闭混合精度验证4.5 打印计算量的问题五、PE-YOLO的yaml文件和运行记录5.1 PE-YOLO的yaml文件5.2 PE-YOLO的训练过程截图五、本文总结二、PE-YOLO算法原理论文地址官方论文地址代码地址官方代码地址​2.1 PE-YOLO的基本原理PE-YOLO是一种改进的暗光条件下的物体检测模型。它结合了金字塔增强网络PENet和YOLOv3。PENet通过拉普拉斯金字塔将图像分解成多个分辨率的组件增强图像细节和低频信息。它包括一个细节处理模块DPM用于通过上下文分支和边缘分支增强图像细节以及一个低频增强滤波器LEF以捕获低频语义并减少高频噪声。PE-YOLO采用端到端的训练方法简化训练过程。PE-YOLO的基本原理可以分为几个关键点1. 金字塔增强网络PENet:使用拉普拉斯金字塔将图像分解为不同分辨率的组件以提升细节和低频信息。2. 细节处理模块DPM:包含上下文分支和边缘分支专门用于增强图像的细节。3. 低频增强滤波器LEF:用于捕获低频语义信息同时减少高频噪声。下面为大家展示了PE-YOLO系统的总览​它说明了如何通过拉普拉斯金字塔将输入图像分解为不同层级L0到L3并通过PENet进行处理最终提升图像质量以便进行物体检测。图中的细节处理模块DPM和低频增强滤波器LEF协同工作以增强图像。2.2金字塔增强网络金字塔增强网络Pyramid Enhancement Network是PE-YOLO的关键组成部分用于增强模型对不同尺度的目标的检测能力。金字塔增强网络主要包括以下几个关键要点1. 多尺度特征金字塔金字塔增强网络使用多个不同尺度的特征金字塔这些金字塔包含了来自不同层级的特征图。这允许PE-YOLO同时检测不同大小的目标从小尺寸物体到大尺寸物体都可以有效地检测。2. 特征融合金字塔增强网络通过特征融合的方式将来自不同尺度的特征图进行组合。这有助于提高模型对目标的定位和检测准确性因为不同尺度的信息被有效地整合在一起。3. 上采样和下采样金字塔增强网络还包括上采样和下采样操作以进一步调整特征金字塔的尺度。上采样用于增加分辨率以更好地捕捉小目标的细节信息而下采样则用于减小分辨率以更好地捕捉大目标的全局信息。4. 注意力机制金字塔增强网络还引入了注意力机制以使模型能够集中注意力在最重要的特征上从而进一步提高检测性能。这有助于减少误检和漏检的情况。总之金字塔增强网络是PE-YOLO的关键创新之一通过多尺度特征金字塔、特征融合、上采样、下采样和注意力机制等技术提高了PE-YOLO模型在目标检测任务中的性能使其能够更好地应对不同大小和尺度的目标。2.3 细节处理模块细节处理模块Detail Processing Module简称DPM是PE-YOLO目标检测算法的一个关键组件旨在增强模型对目标的细节信息的感知和处理能力。DPM的主要任务是通过上下文分支和边缘分支来对目标进行更详细的处理。我为大家总结了PE-YOLO中细节处理模块DPM的主要特点和功能1. 上下文分支Context Branch上下文分支负责获取上下文信息通过捕捉远程依赖关系来理解目标周围的环境。这有助于模型更好地理解目标与其周围环境的关系从而提高目标检测的准确性。上下文信息的引入可以使模型更好地分辨目标和背景之间的区别。2. 边缘分支Edge Branch边缘分支使用两个Sobel算子Sobel operators在不同方向上计算图像的梯度从而获得目标的边缘信息。这有助于模型更好地识别目标的轮廓和边缘特征并增强目标组件的纹理信息。边缘信息对于目标的细节识别和检测非常重要。3. 组件增强DPM的综合作用是增强目标的各个组件包括上下文信息的增强和边缘信息的增强。这使得模型更能够准确地捕捉目标的细节特征从而提高目标检测性能。下图展示的是DPM的结构包括上下文分支CB和边缘分支EB​上下文分支通过捕捉远程依赖关系来获取上下文信息并全局增强组件。边缘分支使用两个不同方向的Sobel运算符来计算图像梯度以获取边缘并增强组件的纹理。2.4低频增强滤波器低频增强滤波器Low-Frequency Enhancement Filter简称LEF用于捕捉和增强图像中的低频信息这些低频信息通常包含了图像的大部分语义和关键信息对于检测器的预测非常重要。PE-YOLO中低频增强滤波器LEF的主要特点和功能总结如下1. 自适应平均池化LEF使用不同尺寸的自适应平均池化操作来截取低频分量。这意味着LEF可以动态地适应不同尺度和语义的低频信息以确保最大程度地捕捉图像中的关键细节。2. 低频信息捕捉LEF的主要任务是捕捉和增强图像中的低频信息这些信息包含了图像的主要语义和关键细节。通过使用低通滤波器来过滤特征LEF只允许低于截止频率的信息通过从而增强了低频成分。3. 多尺度处理考虑到Inception的多尺度结构LEF在不同的尺寸上应用自适应平均池化以适应不同语义和尺度的低频信息。这有助于提高模型对图像细节的理解和捕捉。4. 通道分离LEF将特征f分为四个部分即{f1, f2, f3, f4}通过通道分离的方式每个部分都可以独立处理以进一步增强低频信息。下图展示了低频增强滤波器LEF的详细信息。LEF由不同大小的自适应平均池化组成用于截取低频分量。​考虑到Inception的多尺度结构我们使用了大小分别为1×1、2×2、3×3、6×6的自适应平均池化并在每个尺度的末尾使用上采样来恢复特征的原始大小。不同核大小的平均池化形成了一个低通滤波器。我们通过通道分离将f分成四个部分即{f1, f2, f3, f4}。每个部分都使用不同尺寸的池化进行处理描述如下​其中​是分割在通道上的f的一部分Up是双线性插值采样​是不同尺寸s × s的自适应平均池化。最后在张量拼接每个{​, i 1, 2, 3, 4}之后我们将它们还原为f ∈​。三、PE-YOLO的核心代码这个代码的使用方式比较特殊大家需要注意看后面的使用教程import torch import torch.nn as nn import torch.nn.functional as F class Lap_Pyramid_Conv(nn.Module): def __init__(self, num_high3, kernel_size5, channels3): super().__init__() self.num_high num_high self.kernel self.gauss_kernel(kernel_size, channels) def gauss_kernel(self, kernel_size, channels): kernel cv2.getGaussianKernel(kernel_size, 0).dot( cv2.getGaussianKernel(kernel_size, 0).T) kernel torch.FloatTensor(kernel).unsqueeze(0).repeat( channels, 1, 1, 1) kernel torch.nn.Parameter(datakernel, requires_gradFalse) return kernel def conv_gauss(self, x, kernel): n_channels, _, kw, kh kernel.shape x torch.nn.functional.pad(x, (kw // 2, kh // 2, kw // 2, kh // 2), modereflect) # replicate # reflect x torch.nn.functional.conv2d(x, kernel, groupsn_channels) return x def downsample(self, x): return x[:, :, ::2, ::2] def pyramid_down(self, x): return self.downsample(self.conv_gauss(x, self.kernel)) def upsample(self, x): up torch.zeros((x.size(0), x.size(1), x.size(2) * 2, x.size(3) * 2), devicex.device) up[:, :, ::2, ::2] x * 4 return self.conv_gauss(up, self.kernel) def pyramid_decom(self, img): self.kernel self.kernel.to(img.device) current img pyr [] for _ in range(self.num_high): down self.pyramid_down(current) up self.upsample(down) diff current - up pyr.append(diff) current down pyr.append(current) return pyr def pyramid_recons(self, pyr): image pyr[0] for level in pyr[1:]: up self.upsample(image) image up level return image class ResidualBlock(nn.Module): def __init__(self, in_features, out_features): super().__init__() self.conv_x nn.Conv2d(in_features, out_features, 3, padding1) self.block nn.Sequential( nn.Conv2d(in_features, in_features, 3, padding1), nn.LeakyReLU(True), nn.Conv2d(in_features, in_features, 3, padding1), ) def forward(self, x): return self.conv_x(x self.block(x)) class PENet(nn.Module): def __init__(self, num_high3, gauss_kernel5): super().__init__() self.num_high num_high self.lap_pyramid Lap_Pyramid_Conv(num_high, gauss_kernel) for i in range(0, self.num_high 1): self.__setattr__(AE_{}.format(i), AE(3)) def forward(self, x): pyrs self.lap_pyramid.pyramid_decom(imgx) trans_pyrs [] for i in range(self.num_high 1): trans_pyr self.__getattr__(AE_{}.format(i))( pyrs[-1 - i]) trans_pyrs.append(trans_pyr) out self.lap_pyramid.pyramid_recons(trans_pyrs) return out class DPM(nn.Module): def __init__(self, inplanes, planes, actnn.LeakyReLU(negative_slope0.2, inplaceTrue), biasFalse): super(DPM, self).__init__() self.conv_mask nn.Conv2d(inplanes, 1, kernel_size1, biasbias) self.softmax nn.Softmax(dim2) self.sigmoid nn.Sigmoid() self.channel_add_conv nn.Sequential( nn.Conv2d(inplanes, planes, kernel_size1, biasbias), act, nn.Conv2d(planes, inplanes, kernel_size1, biasbias) ) def spatial_pool(self, x): batch, channel, height, width x.size() input_x x # [N, C, H * W] input_x input_x.view(batch, channel, height * width) # [N, 1, C, H * W] input_x input_x.unsqueeze(1) # [N, 1, H, W] context_mask self.conv_mask(x) # [N, 1, H * W] context_mask context_mask.view(batch, 1, height * width) # [N, 1, H * W] context_mask self.softmax(context_mask) # [N, 1, H * W, 1] context_mask context_mask.unsqueeze(3) # [N, 1, C, 1] context torch.matmul(input_x, context_mask) # [N, C, 1, 1] context context.view(batch, channel, 1, 1) return context def forward(self, x): # [N, C, 1, 1] context self.spatial_pool(x) # [N, C, 1, 1] channel_add_term self.channel_add_conv(context) x x channel_add_term return x import cv2 from torchvision import transforms def sobel(img): device img.device add_x_total torch.zeros(img.shape) for i in range(img.shape[0]): x img[i, :, :, :].squeeze(0).cpu().detach().numpy().transpose(1, 2, 0) x x * 255 x_x cv2.Sobel(x, cv2.CV_64F, 1, 0) x_y cv2.Sobel(x, cv2.CV_64F, 0, 1) add_x cv2.addWeighted(x_x, 0.5, x_y, 0.5, 0) add_x transforms.ToTensor()(add_x).unsqueeze(0) add_x_total[i, :, :, :] add_x add_x_total add_x_total.to(device) return add_x_total class AE(nn.Module): def __init__(self, n_feat, reduction8, biasFalse, actnn.LeakyReLU(negative_slope0.2, inplaceTrue), groups1): super(AE, self).__init__() self.n_feat n_feat self.groups groups self.reduction reduction self.agg nn.Conv2d(6, 3, 1, stride1, padding0, biasFalse) self.conv_edge nn.Conv2d(3, 3, kernel_size1, biasbias) self.res1 ResidualBlock(3, 32) self.res2 ResidualBlock(32, 3) self.dpm nn.Sequential(DPM(32, 32)) self.conv1 nn.Conv2d(3, 32, kernel_size1) self.conv2 nn.Conv2d(32, 3, kernel_size1) self.lpm LowPassModule(32) self.fusion nn.Conv2d(6, 3, kernel_size1) def forward(self, x): s_x sobel(x) s_x self.conv_edge(s_x) res self.res1(x) res self.dpm(res) res self.res2(res) out torch.cat([res, s_x x], dim1) out self.agg(out) low_fea self.conv1(x) low_fea self.lpm(low_fea) low_fea self.conv2(low_fea) out torch.cat([out, low_fea], dim1) out self.fusion(out) return out class LowPassModule(nn.Module): def __init__(self, in_channel, sizes(1, 2, 3, 6)): super().__init__() self.stages [] self.stages nn.ModuleList([self._make_stage(size) for size in sizes]) self.relu nn.ReLU() ch in_channel // 4 self.channel_splits [ch, ch, ch, ch] def _make_stage(self, size): prior nn.AdaptiveAvgPool2d(output_size(size, size)) return nn.Sequential(prior) def forward(self, feats): h, w feats.size(2), feats.size(3) feats torch.split(feats, self.channel_splits, dim1) priors [F.upsample(inputself.stages[i](feats[i]), size(h, w), modebilinear) for i in range(4)] bottle torch.cat(priors, 1) return self.relu(bottle) if __name__ __main__: # Generating Sample image image_size (1, 3, 224, 224) image torch.rand(*image_size) # Model mobilenet_v1 PENet() out mobilenet_v1(image) print(out.size())四、PE-YOLO的添加方式这个添加方式和之前的变了一下以后的添加方法都按照这个来了是为了和群内的文件适配。4.1 修改一第一还是建立文件我们找到如下ultralytics/nn/modules文件夹下建立一个目录名字呢就是Addmodules文件夹(用群内的文件的话已经有了无需新建)然后在其内部建立一个新的py文件将核心代码复制粘贴进去即可。​4.2 修改二第二步我们在该目录下创建一个新的py文件名字为__init__.py(用群内的文件的话已经有了无需新建)然后在其内部导入我们的检测头如下图所示。​4.3 修改三第三步我门中到如下文件ultralytics/nn/tasks.py进行导入和注册我们的模块(用群内的文件的话已经有了无需重新导入直接开始第四步即可)​到此就完事了注册的工作该模型无需添加任何参数是一种无参的机制所以导入进来即可该网络本身存在一个设备定义的错误我解决以后需要关闭混合精度验证这是必须的否则会报错其次该网络修改完打印不出来参数都可以通过下面的步骤来解决。大家注意看4.4 关闭混合精度验证找到ultralytics/engine/validator.py文件找到 class BaseValidator: 然后在其__call__中 self.args.half self.device.type ! cpu # force FP16 val during training的一行代码下面加上self.args.half False4.5 打印计算量的问题计算的GFLOPs计算异常不打印所以需要额外修改一处 我们找到如下文件ultralytics/utils/torch_utils.py文件内有如下的代码按照如下的图片进行修改有一个get_flops的函数我们直接用我给的代码全部替换​def get_flops(model, imgsz640): Return a YOLO models FLOPs. if not thop: return 0.0 # if not installed return 0.0 GFLOPs try: model de_parallel(model) p next(model.parameters()) if not isinstance(imgsz, list): imgsz [imgsz, imgsz] # expand if int/float try: # Use stride size for input tensor stride 640 im torch.empty((1, 3, stride, stride), devicep.device) # input image in BCHW format flops thop.profile(deepcopy(model), inputs[im], verboseFalse)[0] / 1e9 * 2 # stride GFLOPs return flops * imgsz[0] / stride * imgsz[1] / stride # imgsz GFLOPs except Exception: # Use actual image size for input tensor (i.e. required for RTDETR models) im torch.empty((1, p.shape[1], *imgsz), devicep.device) # input image in BCHW format return thop.profile(deepcopy(model), inputs[im], verboseFalse)[0] / 1e9 * 2 # imgsz GFLOPs except Exception: return 0.0五、PE-YOLO的yaml文件和运行记录5.1 PE-YOLO的yaml文件训练信息YOLO26-PENet summary: 354 layers, 2,597,343 parameters, 2,597,268 gradients, 28.2 GFLOPs# Ultralytics AGPL-3.0 License - https://ultralytics.com/license # Ultralytics YOLO26 object detection model with P3/8 - P5/32 outputs # Model docs: https://docs.ultralytics.com/models/yolo26 # Task docs: https://docs.ultralytics.com/tasks/detect # Parameters nc: 80 # number of classes end2end: True # whether to use end-to-end mode reg_max: 1 # DFL bins scales: # model compound scaling constants, i.e. modelyolo26n.yaml will call yolo26.yaml with scale n # [depth, width, max_channels] n: [0.50, 0.25, 1024] # summary: 260 layers, 2,572,280 parameters, 2,572,280 gradients, 6.1 GFLOPs s: [0.50, 0.50, 1024] # summary: 260 layers, 10,009,784 parameters, 10,009,784 gradients, 22.8 GFLOPs m: [0.50, 1.00, 512] # summary: 280 layers, 21,896,248 parameters, 21,896,248 gradients, 75.4 GFLOPs l: [1.00, 1.00, 512] # summary: 392 layers, 26,299,704 parameters, 26,299,704 gradients, 93.8 GFLOPs x: [1.00, 1.50, 512] # summary: 392 layers, 58,993,368 parameters, 58,993,368 gradients, 209.5 GFLOPs # YOLO26n backbone backbone: # [from, repeats, module, args] - [-1, 1, PENet, []] # 0-P1/2 - [-1, 1, Conv, [64, 3, 2]] # 1-P1/2 - [-1, 1, Conv, [128, 3, 2]] # 2-P2/4 - [-1, 2, C3k2, [256, False, 0.25]] - [-1, 1, Conv, [256, 3, 2]] # 4-P3/8 - [-1, 2, C3k2, [512, False, 0.25]] - [-1, 1, Conv, [512, 3, 2]] # 6-P4/16 - [-1, 2, C3k2, [512, True]] - [-1, 1, Conv, [1024, 3, 2]] # 8-P5/32 - [-1, 2, C3k2, [1024, True]] - [-1, 1, SPPF, [1024, 5, 3, True]] # 10 - [-1, 2, C2PSA, [1024]] # 11 # YOLO26n head head: - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 7], 1, Concat, [1]] # cat backbone P4 - [-1, 2, C3k2, [512, True]] # 14 - [-1, 1, nn.Upsample, [None, 2, nearest]] - [[-1, 5], 1, Concat, [1]] # cat backbone P3 - [-1, 2, C3k2, [256, True]] # 17 (P3/8-small) - [-1, 1, Conv, [256, 3, 2]] - [[-1, 14], 1, Concat, [1]] # cat head P4 - [-1, 2, C3k2, [512, True]] # 20 (P4/16-medium) - [-1, 1, Conv, [512, 3, 2]] - [[-1, 11], 1, Concat, [1]] # cat head P5 - [-1, 1, C3k2, [1024, True, 0.5, True]] # 23 (P5/32-large) - [[17, 20, 23], 1, Detect, [nc]] # Detect(P3, P4, P5)5.2 PE-YOLO的训练过程截图​五、本文总结到此本文的正式分享内容就结束了在这里给大家推荐我的YOLOv26改进有效涨点专栏本专栏目前为新开的平均质量分98分后期我会根据各种最新的前沿顶会进行论文复现也会对一些老的改进机制进行补充如果大家觉得本文帮助到你了订阅本专栏关注后续更多的更新~专栏链接YOLOv26有效涨点专栏包含Conv、注意力机制、主干/Backbone、损失函数、优化器、后处理等改进机制