尧图精选

双像素失焦去模糊实战:DPDD数据集与DPDNet训练指南

🕒 发布时间:2026/9/12 1:21:55 📁 来源:尧图网络
简介双像素脱焦去模糊DPDD数据集面向计算机视觉研究者与图像处理开发者用于解决真实场景中失焦模糊图像难以成对获取、去模糊模型泛化性差的问题。压缩包共59个文件以39个GIF动态演示为主另有6个Python脚本、6个npy数据文件、4个Markdown说明、3张PNG图示和1个txt说明整体大小50.25MB目录结构清晰便于按模块检索。数据主体包含500个场景、2000张全帧分辨率图像500张失焦模糊图、1000个双像素子孔径视图及500张全焦图分辨率达6720×4480可直接用于模型训练与评估。配套附有DPDNet模型实现、数据预处理与评估脚本、数据加载配置及动态结果展示便于理解双像素成像原理、快速复现实验并可根据自身任务扩展算法。已有542人学习适合具备深度学习基础、专注图像复原课题的进阶研究者。1. 双像素脱焦去模糊的痛点与DPDD数据集的设计边界处理失焦模糊时大多数公开数据集都是“模糊核 清晰图”合成出来的真实环境下镜头焦外导致的模糊往往带空间变化的光学特性合成样本并不拟真。双像素Dual-Pixel传感器恰好记录了同一像素的两个子孔径视角这两个视角的微小差异隐含了物距和弥散圆的信息算法可以从中反推出失焦量从而更可靠地复原全焦点图像。DPDDDual-Pixel Defocus Deblurring数据集就是这个思路下比较少见的大规模真实拍摄基准500个场景每个场景包含左右双像素视图、对应的失焦模糊图以及全焦点清晰图全部分辨率6720x4480。对做图像复原、深度估计、多视角影像处理的团队来说这个数据集能直接支撑双输入模型的训练也能用来验证从同场景DP视差估计模糊核的算法。需要说明的是DPDD释放的并不是仅有图像还包括配套的DPDNet参考实现、切块工具和评估脚本下面会从数据构成一直拆到可以复现的训练命令。2. 双像素成像原理与DPDD数据集的构成2.1 双像素子孔径视图如何产生失焦信息双像素技术的核心是让每个像素点内部包含两个并列的光电二极管感光时分别接收镜头左半部分和右半部分的光线于是单次曝光可以同时得到两个视角略有偏移的子孔径视图。当被摄主体正好对焦时两个视图几乎完全一致一旦出现焦外区域两个视图在该区域的视差强度与模糊方向直接相关。传统单像素传感器只能看到模糊结果而双像素传感器额外提供了差异信息DPDD数据集中称之为左、右子孔径视图。实际处理时很多模型并不直接输入两张原始子孔径图而是先把它们合成为一张模糊图再让网络去预测清晰图DPDD原始文件同时给出了左右视图和合成后的模糊图这在数据组织上是比较友好的。从文件命名来看每个场景都会有三类输出清晰图、模糊图以及左右子孔径图其中子孔径视图可能是以颜色交错或双通道形式存储这一点需要查看数据读取脚本确认。2.2 DPDD的数据文件组织与统计解压后可以看到defocus-deblurring-dual-pixel-master目录下包含DPDNet、ModelCheckpoints以及多个.py代码文件。数据集本身没有直接写项目根目录下建议把所有图像放在dataset/DPDD/下结构与官方路径保持一致。我一般会用下面这段Python快速统计场景数和图像尺寸确认数据完整性。import os from PIL import Image from glob import glob dataset_root dataset/DPDD for split in [train, test]: scenes glob(os.path.join(dataset_root, split, *)) scene_count len(scenes) image_count 0 first_size None for scene in scenes: for ext in [*.png, *.jpg, *.tif]: images glob(os.path.join(scene, ext)) image_count len(images) if first_size is None and images: with Image.open(images[0]) as im: first_size im.size print(f{split}: {scene_count} scenes, {image_count} images, first size {first_size})这段代码的作用是遍历训练集和测试集目录统计场景目录数量和图片总数量并读取第一张图的尺寸用于确认分辨率是否确实是6720x4480。glob用于匹配路径Image.open读取尺寸时不会真正加载整张图像内存占用较小。如果统计结果与摘要描述不符通常是文件解压不完整或目录层级不对。每个场景内的文件命名通常带有语义例如01_gt.png表示全焦点清晰图01_bokeh.png表示失焦模糊图01_dp_left.png与01_dp_right.png表示左右子孔径图。这种命名在后处理时会省掉很多时间不需要额外解析元数据。2.3 读取一副样本的完整流程导入数据时常见做法是把左右子孔径视图堆叠成一个两通道或六通道张量。对彩色图像来说每个视图都是RGB三通道左右视图堆叠后得到六通道输入。DPDNet第一层卷积需要处理六通道输入也就是in_channels6后面再接一个降维操作。下面给出一个简单的数据读取函数。import numpy as np import cv2 def load_dp_sample(scene_dir): left cv2.imread(f{scene_dir}/dp_left.png) right cv2.imread(f{scene_dir}/dp_right.png) blur cv2.imread(f{scene_dir}/bokeh.png) sharp cv2.imread(f{scene_dir}/gt.png) # 保持通道顺序一致BGR转RGB left_rgb cv2.cvtColor(left, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0 right_rgb cv2.cvtColor(right, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0 # 堆叠左右视图shape为[H, W, 6] dp_input np.concatenate([left_rgb, right_rgb], axis2) # 也可改变通道顺序 return dp_input, blur, sharp拼接时要注意通道顺序左右视图的通道顺序必须一致。有的实现会把左右视图沿通道维度拼接后再做归一化有的则先把每张图分别归一化再拼接两种方式在数值范围上有微小差异建议统一在数据集中用ImageNet均值和方差做标准化。cv2.imread默认以BGR读取所以先将颜色空间转成RGB再输入网络否则训练出的模型在推理时会出现颜色漂移。3. DPDNet结构解析与训练管线设计3.1 网络为什么会使用双分支输入DPDNet采用编码-解码结构输入是两个双像素子孔径视图输出是清晰图。这种设计的出发点是左右视图之间存在亚像素级别的视差编码器需要把这种视差特征提取出来而简单的单帧去模糊网络无法显式建模这种差异。常见实现会在编码器后段融合左右视图特征而不是在输入层简单拼接这样能够保留每个视图独立的高频细节。从model.py里的结构来看网络包含一个下采样流和一个上采样流编码器由几组卷积加残差块组成中间还有一个特征融合模块。关键点是最后输出的不是模糊图而是清晰图本身所以损失函数直接在输出与原图上计算。训练时使用L1损失还是感知损失会直接影响边缘锐度实际项目中经常混合L1与感知损失一起用DPDNet参考代码默认以L1加VGG感知损失为主。3.2 model.py中的核心实现下面截取DPDNet的关键定义为了方便理解做了少量注释实际运行时可以直接调用默认构造函数。import torch import torch.nn as nn class DPDNet(nn.Module): def __init__(self, in_ch6, base_ch64, depth3): super().__init__() # 首层卷积将堆叠后的六通道压到base_ch维 self.entry nn.Sequential( nn.Conv2d(in_ch, base_ch, 3, 1, 1), nn.ReLU() ) # 中间残差块组使用双向特征交互 self.body nn.ModuleList([ nn.Conv2d(base_ch, base_ch, 3, 1, 1) for _ in range(depth) ]) # 重建头输出三个通道范围0-1 self.tail nn.Sequential( nn.Conv2d(base_ch * 2, base_ch, 3, 1, 1), nn.ReLU(), nn.Conv2d(base_ch, 3, 3, 1, 1) ) def forward(self, dp_left, dp_right): # 输入分别为左右视图形状都是[B,3,H,W] fused torch.cat([dp_left, dp_right], dim1) # [B,6,H,W] feat self.entry(fused) for conv in self.body: feat torch.relu(conv(feat) feat) # 将编码特征与浅层信息拼接后重建 out self.tail(torch.cat([feat, self.entry(fused)], dim1)) return out这段代码去掉了跳接和注意力模块只保留主干逻辑。in_ch6直接对应左右视图RGB通道拼接base_ch64是常用的基础通道数。注意self.body里每个卷积都自带残差连接这种短接能避免训练初期梯度消失。forward中torch.cat沿通道维合并左右视图之后每一层输出的特征图都同时携带两个视角的信息。实际训练时我一般把这个主干替换成更深的ResBlock或者加入通道注意力开销并不大但恢复出来的纹理更自然。3.3 config.py与main.py的参数传递config.py里集中管理训练参数下面列出常见的配置项以及默认值。参数名默认值说明image_size512训练时切块尺寸全分辨率太大batch_size8受显存限制4或8较常见learning_rate2e-4Adam初始学习率num_epochs200配合早停使用data_rootdataset/DPDD/数据根目录checkpoint_dircheckpoints/模型保存目录main.py里通过argparse覆盖config.py中的默认值也可以用简单方式解析配置。实际操作时config.py里的路径和config参数、训练参数分离方便多组实验对比。main.py内部还会调用data.py里的数据加载器把原始图像随机裁剪成image_size的patch并做随机翻转、旋转等数据增强。对于DPDD这种大图数据直接加载全图到显存是行不通的切块策略是本章的核心下一节单独讲。4. 全分辨率图像切块与模型训练实战4.1 image_to_patch_filter.py切块策略由于全帧图像是6720x4480直接送入GPU必须使用很大显存因此官方提供了image_to_patch_filter.py把原图切成若干小patch并过滤掉不含有效纹理的区域。切块时需要同时处理左右子孔径图和清晰图保证三张图的裁剪位置完全一致。python image_to_patch_filter.py \ --input_dir dataset/DPDD/train \ --output_dir dataset/DPDD/patches \ --patch_size 512 \ --stride 256 \ --min_std 10.0参数说明--input_dir指向包含原始场景目录的父目录--output_dir保存切块后的图像目录结构会保持与输入一致--patch_size决定切块边长512是一个兼顾显存和有效视野的常用值--stride表示相邻窗口的步长等于patch_size时无重叠小于patch_size时相邻patch有重叠有利于扩充数据量--min_std是过滤阈值用于丢弃方差过低的纯色或过曝区域这些区域对训练去模糊网络没有正收益。切块脚本的输出命名需要保持对应关系例如scene42_0_0_left.png、scene42_0_0_right.png、scene42_0_0_blur.png这样读取时容易通过文件名前缀匹配。假如没有现成脚本用numpy切片也能完成但要注意边界对齐。切块过程中出现全黑patch多半是读取时把16位图像转成了8位检查图像深度即可。4.2 训练与验证的完整命令数据切块完成后直接运行main.py进入训练。下面是我常用的启动命令。python main.py \ --mode train \ --data_root dataset/DPDD/patches \ --checkpoint_dir checkpoints/dpdd \ --epochs 200 \ --batch_size 8 \ --lr 2e-4 \ --image_size 512 \ --loss l1perceptual这个命令会启动DPDNet的训练流程。--loss l1perceptual指定组合损失l1部分保证输出与清晰图的逐像素差异最小化perceptual部分通过预训练VGG提取高层特征让输出在语义上更接近清晰图。训练时每过几个epoch会执行一次验证验证集上的PSNR和SSIM会打印到日志中。如果显存不够把--batch_size降到4同时把--image_size改成384。也可以设置--amp true开启混合精度训练在这类全分辨率派生patch上能节省大约30%显存。训练过程中注意观察loss曲线是否在快速下降后进入平台期。DPDD数据量不大2000张全图切块后可能得到数万patch训练200个epoch在单张4090上大约需要一天。当验证集PSNR持续不增长时通常是学习率偏大导致震荡我一般会将学习率在训练后半段按余弦退火降到1e-5。4.3 模型checkpoint的加载与推理推理时不再需要右视图并不对DPDNet设计上需要左右两个视图同时输入。使用ModelCheckpoints目录下的预训练权重加载方式如下。import torch from model import DPDNet net DPDNet(in_ch6, base_ch64).cuda() ckpt torch.load(checkpoints/dpdd_best.pth) net.load_state_dict(ckpt[model_state_dict]) net.eval() with torch.no_grad(): left torch.randn(1, 3, 512, 512).cuda() right torch.randn(1, 3, 512, 512).cuda() # 输出范围0-1转回uint8需要乘255 sharp net(left, right)这里的关键点是加载state_dict时必须确保当前模型与checkpoint保存时的网络结构完全一致。如果之前用nn.DataParallel训练权重键名会带module.前缀加载时可以尝试剥掉前缀再载入。推理时不需要梯度用torch.no_grad()包裹输入尺寸不一定非是512的整数倍但DPDD切块始终是正方形使用非正方形输入时卷积层本身没问题最后输出尺寸会保持和输入一致。5. 评测指标与基于DPDD的扩展实践5.1 使用metrics.py计算PSNR与SSIM评估模型质量时靠眼睛看不确定需要客观指标。metrics.py里实现了PSNR和SSIM命令行直接计算一组预测结果与清晰图之间的指标。python metrics.py \ --pred_dir outputs/pred \ --gt_dir dataset/DPDD/test \ --metrics psnr ssim脚本会遍历pred_dir下的预测图像与gt_dir中同名的清晰图计算数值。PSNR对像素级误差敏感SSIM衡量结构相似性两者一起看可以避免单指标失真。计算时确保输入图像尺寸和通道数一致如果预测图是float张量需要先转化成uint8范围再计算。对于DPDD这类全分辨率图如果最终预测是全图输出而评测时也使用全图那么PSNR值会直接反映整体恢复能力如果评测patch则要取所有patch指标的平均值不能直接拼接后再算否则边界重叠区域会被重复计算。5.2 用DPDD做多任务学习的边界条件DPDD数据集不仅适合去模糊也可以用来双像素深度估计。左右子孔径视差的强度与人眼双目视差类似可以训练一个共享编码器网络一个分支输出清晰图另一个分支输出视差图。实际使用中如果想在自己的场景里用DPDD预训练模型做迁移需要注意场景域差异。例如DPDD拍摄的是室内物体和近景镜头焦外模糊相对柔和而无人机航拍或显微镜图像的离焦模型不同直接用DPDD权重部署效果未必好。建议先在目标域采集少量真实双像素数据用DPDD预训练权重做初始化再用小学习率微调。对模型轻量化来说可以把网络中间的64通道改成32通道并把残差块数量从3降到1参数量可以降到原来的四分之一在移动设备上也能跑。这个裁剪操作只影响细节恢复质量模糊区域的整体位置复原仍然可用动手前最好先量化一下显存和延迟需求。5.3 训练稳定性与数据加载的注意事项DPDD数据集中所有图像都是全帧分辨率直接读入内存会占掉大量内存。建议在data.py里使用懒加载只返回图像路径在__getitem__时再读取patches或者使用内存映射避免进程启动时卡顿。切块后的patch数量大使用DataLoader的num_workers4和pin_memoryTrue能明显提升数据供给速度。如果训练时发现第一个epoch非常慢然后突然快起来那是缓存未命中造成的可以预先跑一遍训练脚本让操作系统把常访问的图片加载到页缓存中。此外数据增强时不要对左右视图分别做不同的颜色抖动。左右子孔径视图来自同一次曝光明暗关系一致如果随机改变其中一个视图的亮度网络会学习到错误的融合方式导致测试时性能下降。增强操作应该对左右图和清晰图使用同一组参数尤其是随机旋转和水平翻转必须保持空间对应关系。遇到验证指标在某个epoch后突然掉得很厉害首要检查是不是数据加载顺序被打乱后左右视图不匹配其次才是学习率问题。最后如果需要在自定义数据上复现DPDD格式可以用相机手动拍摄一对焦内和焦外照片并尽量在运动平台或三角架上拍摄保证场景不发生位移。对于没有双像素传感器的相机模拟的左右视图可以先用深度图加轻微视角偏移来生成但这样生成的视差与真实双像素仍有差距只能用于预训练最终评估还是需要真实数据。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →