
简介这份资源面向从事图像复原、去模糊与双像素对焦研究的开发者与研究生提供双像素脱焦去模糊DPDD数据集的配套代码实现。数据集由500组拍摄场景构成包含500幅离焦模糊原图、1000幅双像素子孔径视图及500幅全焦点清晰参考图分辨率均为6720×4480全画幅规格可用于训练和评测散焦去模糊模型。压缩包共71个文件约54.47MB以gif动态对比图、py脚本、npy数据索引、md说明文档和png示意图为主其中脚本涵盖模型定义、数据加载、指标计算与图像分块处理等模块npy文件保存训练与验证集的源图、目标图索引gif则直观展示去模糊前后及子孔径视图的差异。已有94人学习关注。读者可据此复现DPDNet的训练与推理流程理解双像素数据组织方式并借助可视化结果快速评估算法效果适合作为相关课题的入门与实验参考。1. 双像素脱焦去模糊数据集为什么 6720×4480 的全分辨率子孔径视图值得单独拆一遍拿到 DPDDDual-Pixel Defocus Deblurring Dataset的第一反应往往是「不就是个去模糊数据集吗」但真正拆开目录会发现它和常见去模糊数据集不是一个路数。它给的是双像素相机拍出来的原始子孔径视图左视图和右视图在同一时刻记录同一场景只是相位略有差异两张图之间的视差直接编码了场景的深度和脱焦程度。单张图去模糊是病态问题而双像素把「这张图哪里糊了、糊了多远」变成了可测量的物理量这是它区别于普通图像去模糊数据集的核心。这份资源面向的是做图像复原、计算摄影、手机影像算法的从业者。6720×4480 的全分辨率意味着它不是那种裁成 256×256 小块喂给网络就完事的玩具数据集你要考虑显存、要考虑分块推理、要考虑子孔径视图对齐误差。适合已经跑过基础去模糊模型、想往真实双像素数据上迁移的人也适合做深度估计和去模糊联合优化的团队。新手照着步骤能跑通熟手能在这里看到全分辨率和子孔径对齐的真实边界。2. DPDD 的数据组织与子孔径视图读取从目录结构到张量形状2.1 目录结构与文件命名规律DPDD 的典型组织方式是按场景分文件夹每个场景下同时存放左子孔径视图、右子孔径视图和对应的全清晰参考图如果有的话。常见命名是scene_xx_left.png、scene_xx_right.png、scene_xx_gt.png这种形式但不同分发版本会有差异所以第一步永远是先扫一遍目录把文件对应关系确认清楚而不是直接写死路径。我一般会先跑一段扫描脚本把每个场景的文件清单打印出来确认左右视图和参考图是否齐全。这一步看着笨但能避免后面训练时才发现某个场景缺了右视图白跑几个小时。import os from pathlib import Path root Path(./DPDD) scenes sorted([d for d in root.iterdir() if d.is_dir()]) for scene in scenes[:5]: files sorted([f.name for f in scene.iterdir() if f.suffix.lower() in (.png, .jpg, .tif)]) print(scene.name, -, files)这段代码只做一件事把前 5 个场景的文件名列出来。参数上注意suffix的判断要覆盖实际格式DPDD 里常见的是 PNG但有些版本会混入 TIFF。逻辑说明先确认命名规律再决定后续怎么拼路径。如果发现左右视图命名不统一比如有的叫left有的叫L那就要在读取层做一次映射而不是在每个脚本里重复判断。2.2 全分辨率读取的显存与分块策略6720×4480 的单张图按 float32 算就是 6720×4480×3×4 字节大约 360MB。听起来还能接受但训练时一个 batch 放几张再加上网络中间激活显存会迅速吃满。常见做法是训练时随机裁块推理时再分块拼接。裁块不是随便裁要保证左右视图裁的是同一区域否则子孔径视差就废了。import numpy as np from PIL import Image def load_pair(left_path, right_path, patch_size512, seed0): left np.array(Image.open(left_path).convert(RGB)) right np.array(Image.open(right_path).convert(RGB)) h, w, _ left.shape rng np.random.default_rng(seed) y rng.integers(0, h - patch_size) x rng.integers(0, w - patch_size) left_patch left[y:ypatch_size, x:xpatch_size] right_patch right[y:ypatch_size, x:xpatch_size] return left_patch, right_patch关键参数是patch_size512 是常见起点显存紧张就降到 256但太小会丢失脱焦模糊的空间上下文。seed固定是为了让左右视图裁同一块实际训练时每个 epoch 换 seed。逻辑说明先读全图再裁而不是用 PIL 的 crop 直接读是因为有些格式的 crop 读取会触发全图解码反而更慢。如果显存实在不够可以考虑用内存映射或者提前把图切成块存盘但那样会丢失随机裁块的灵活性。2.3 子孔径视图的对齐检查双像素数据最容易被忽略的是左右视图的对齐。理论上同一时刻拍摄但传感器制造误差和镜头装配会让两张图有微小偏移。如果直接拿左右视图做视差计算偏移会污染深度估计。常见做法是先做一次全局对齐检查用相位相关或者简单的模板匹配估一个平移量如果平移量超过一两个像素就要在预处理里补偿。import cv2 import numpy as np def estimate_shift(left, right): left_gray cv2.cvtColor(left, cv2.COLOR_RGB2GRAY).astype(np.float32) right_gray cv2.cvtColor(right, cv2.COLOR_RGB2GRAY).astype(np.float32) shift, response cv2.phaseCorrelate(left_gray, right_gray) return shift, responsephaseCorrelate返回的shift是右图相对左图的平移量response是置信度。逻辑说明这个检查不用每张图都做抽几十张看看分布就行。如果发现系统性偏移就在读取层统一补偿如果是随机偏移那可能是拍摄时抖动这种样本要考虑是否剔除。参数上注意图像要先转灰度再转 float32否则相位相关会报类型错误。3. 脱焦去模糊模型的训练流程从数据加载到损失函数选择3.1 数据加载器的设计要点DPDD 的训练不是简单的输入模糊图输出清晰图而是可以利用左右视图的互补信息。常见做法是把左视图和右视图在通道维度拼接送进网络让网络自己学怎么融合。也有做法是分别提特征再融合但那样参数量会上去。我一般先用通道拼接的 baseline跑通了再考虑更复杂的融合结构。import torch from torch.utils.data import Dataset class DPDDDataset(Dataset): def __init__(self, root, patch_size512, is_trainTrue): self.root root self.patch_size patch_size self.is_train is_train self.scenes sorted([d for d in root.iterdir() if d.is_dir()]) def __len__(self): return len(self.scenes) def __getitem__(self, idx): scene self.scenes[idx] left np.array(Image.open(scene / left.png).convert(RGB)) right np.array(Image.open(scene / right.png).convert(RGB)) gt np.array(Image.open(scene / gt.png).convert(RGB)) if self.is_train: left, right, gt random_crop(left, right, gt, self.patch_size) else: left, right, gt center_crop(left, right, gt, self.patch_size) left torch.from_numpy(left).permute(2, 0, 1).float() / 255.0 right torch.from_numpy(right).permute(2, 0, 1).float() / 255.0 gt torch.from_numpy(gt).permute(2, 0, 1).float() / 255.0 return {left: left, right: right, gt: gt}逻辑说明训练时随机裁验证时中心裁保证验证结果可复现。参数上patch_size和前面读取层保持一致。注意归一化到 0 到 1 之间如果网络里有 BatchNorm这个范围是合适的如果用的是其他归一化要相应调整。random_crop要保证三个图裁同一位置这个函数要自己实现不能分别裁。3.2 损失函数的选择与组合去模糊任务常用的损失是 L1 加感知损失但 DPDD 有个额外信息可以用左右视图的一致性。理论上左视图去模糊后的结果和右视图去模糊后的结果应该只差一个视差如果差太多说明网络没学好。常见做法是加一个左右一致性损失约束两个分支的输出在视差补偿后尽量接近。import torch.nn.functional as F def consistency_loss(out_left, out_right, shift): # shift 是预估的视差单位像素 out_right_shifted warp(out_right, shift) return F.l1_loss(out_left, out_right_shifted) def total_loss(out_left, out_right, gt, shift, w_cons0.1): l1 F.l1_loss(out_left, gt) F.l1_loss(out_right, gt) cons consistency_loss(out_left, out_right, shift) return l1 w_cons * consw_cons是权重一般从 0.1 开始试太大网络会只顾一致性不顾清晰度。warp函数要根据视差做重采样可以用grid_sample实现。逻辑说明一致性损失不是必须的但在 DPDD 上通常能带来一点提升尤其是边缘区域。如果训练不稳定先把w_cons设成 0跑通 baseline 再加。3.3 训练配置与显存控制全分辨率训练不现实所以训练时用裁块但验证和推理时要面对全图。常见做法是推理时分块块之间留重叠最后加权融合。训练配置上batch size 通常只能放到 2 到 4取决于 patch size 和网络大小。优化器用 Adam学习率 1e-4 起步跑几十个 epoch 后降到 1e-5。python train.py \ --data_root ./DPDD \ --patch_size 512 \ --batch_size 2 \ --lr 1e-4 \ --epochs 100 \ --w_cons 0.1 \ --gpus 0参数说明patch_size和batch_size要一起调显存不够先降 batch 再降 patch。w_cons是左右一致性权重不确定就先用 0.1。gpus指定卡号多卡的话要改代码里的 DistributedDataParallel。逻辑说明这个命令是示意实际脚本名和参数名要根据你拿到的代码调整但核心参数就这几个。训练时盯着验证集的 PSNR 和 SSIM如果 PSNR 涨但 SSIM 不涨可能是过平滑了要检查损失权重。4. 全分辨率推理与分块拼接6720×4480 怎么跑才不爆显存4.1 分块推理的边界处理全分辨率推理不能直接送整图必须分块。分块有两个坑一是块与块之间的接缝二是边界块的处理。常见做法是块大小 1024重叠 128每个块单独推理然后按权重融合。权重可以用高斯窗中心高边缘低这样接缝会平滑很多。def infer_full_image(model, left, right, patch1024, overlap128): h, w, _ left.shape output np.zeros((h, w, 3), dtypenp.float32) weight np.zeros((h, w, 1), dtypenp.float32) step patch - overlap for y in range(0, h, step): for x in range(0, w, step): y2 min(y patch, h) x2 min(x patch, w) y1 max(0, y2 - patch) x1 max(0, x2 - patch) left_patch left[y1:y2, x1:x2] right_patch right[y1:y2, x1:x2] out_patch model_infer(model, left_patch, right_patch) win gaussian_window(out_patch.shape[:2]) output[y1:y2, x1:x2] out_patch * win weight[y1:y2, x1:x2] win return output / np.maximum(weight, 1e-6)逻辑说明y1和x1的计算保证边界块也能取到完整 patch不会因为越界而缩小。gaussian_window生成和 patch 同尺寸的权重图。参数上patch和overlap要一起调overlap 太小接缝明显太大推理时间翻倍。如果显存还是不够把patch降到 512但 overlap 要保持至少 64。4.2 推理速度与精度的权衡分块推理的时间大致和块数成正比6720×4480 按 1024 块、128 重叠算大概要跑几十个块。如果模型本身很重一张图可能要几十秒。常见优化是先用小模型快速出一版再用大模型精修或者用 TensorRT 加速。但加速之前先确认精度损失可接受别为了快把去模糊效果搞没了。import time start time.time() result infer_full_image(model, left, right, patch1024, overlap128) print(inference time:, time.time() - start)这段就是计时用来评估是否满足你的场景需求。逻辑说明先测 baseline再决定要不要优化。参数上注意patch越大块数越少但单块显存越高。如果 GPU 显存是 24G1024 块通常没问题如果是 8G可能要降到 512。4.3 结果保存与格式选择推理结果保存时要注意位深和格式。如果后续还要做评测建议存 PNG 16bit避免 8bit 量化损失。如果只是看效果8bit PNG 够了。保存路径最好和输入对应方便批量对比。from PIL import Image result_uint16 (result * 65535).astype(np.uint16) Image.fromarray(result_uint16).save(output/scene_01_deblur.png)逻辑说明result是 0 到 1 的 float乘 65535 转 uint16。参数上注意如果模型输出可能超出 0 到 1要先 clip。保存前确认目录存在不然会报错。5. 避坑与排查DPDD 实操中容易翻车的几个点5.1 左右视图裁块不对齐导致视差失效现象训练 loss 下降很慢验证集 PSNR 卡在一个较低的值上不去。原因随机裁块时左右视图用了不同的随机种子裁出来的区域不对应网络学到的左右信息是错位的。解决裁块函数必须接收同一个随机种子或者先裁左视图记录坐标再用同一坐标裁右视图和参考图。这个坑我踩过排查了半天才发现是数据层的问题。5.2 全分辨率推理接缝明显现象分块推理拼接后块与块之间有可见的亮线或暗线。原因块边缘的推理结果不可靠直接拼接会暴露差异。解决用高斯窗加权融合重叠区域至少 64 像素。如果还有接缝检查是不是每个块都做了归一化有些模型对输入范围敏感块之间统计量不同会导致输出不一致。5.3 显存溢出但不知道哪一层爆的现象训练时 CUDA out of memory但不知道是数据加载还是网络前向爆的。原因全分辨率图读进来就占了几百 MB再加上网络激活很容易超。解决先用torch.cuda.memory_summary()看显存分布确认是数据还是模型占大头。如果是数据改成裁块读取如果是模型降 batch 或 patch。常见做法是先把 patch 降到 256 跑通再逐步往上加。5.4 子孔径视差估计不准导致一致性损失起反作用现象加了左右一致性损失后验证集指标反而下降。原因视差估计不准warp后的右视图和左视图对不齐一致性损失在惩罚正确的结果。解决先单独验证视差估计的精度抽几十张图看phaseCorrelate的 response如果 response 低就说明该区域纹理少视差不靠谱。这种情况下要么降低w_cons要么只在 response 高的区域算一致性损失。5.5 参考图与输入图分辨率不一致现象训练时 loss 计算报形状不匹配。原因有些版本的 DPDD 参考图是裁过的和左右视图尺寸不一样。解决读取时先检查三张图的 shape不一致就统一裁到最小公共尺寸或者用 resize。但 resize 会引入额外模糊最好还是找原始对齐的版本。这个坑在混用不同来源数据时特别常见。6. 进阶技巧用子孔径视差做深度引导的去模糊跑通 baseline 之后可以试试把视差信息显式地用起来。DPDD 的左右视图本身就能估深度而脱焦模糊的程度和深度直接相关。常见做法是先估一个粗糙的视差图然后把它作为额外通道送进去模糊网络让网络知道每个像素大概离焦多远。这样网络不用从零学模糊核收敛更快边缘恢复也更好。def estimate_disparity(left, right): stereo cv2.StereoSGBM_create( minDisparity0, numDisparities64, blockSize5, P18 * 3 * 5 ** 2, P232 * 3 * 5 ** 2, disp12MaxDiff1, uniquenessRatio10, speckleWindowSize100, speckleRange32 ) gray_left cv2.cvtColor(left, cv2.COLOR_RGB2GRAY) gray_right cv2.cvtColor(right, cv2.COLOR_RGB2GRAY) disparity stereo.compute(gray_left, gray_right).astype(np.float32) / 16.0 return disparitynumDisparities要是 16 的倍数blockSize用奇数。逻辑说明SGBM 是传统方法快但粗糙够用作引导。参数上numDisparities根据实际视差范围调太大浪费算力太小会截断。得到视差图后归一化到 0 到 1和左右视图在通道维度拼接送进网络。训练时视差图可以预先算好存盘避免每个 epoch 重复计算。验证这个方法是否有效不能只看 PSNR。脱焦去模糊的难点在边缘和纹理区域建议单独统计边缘区域的 SSIM或者用一张有明显前后景的图做视觉对比。我一般会固定几张验证图每次改完都跑一遍肉眼确认边缘有没有变锐。如果 PSNR 涨了但边缘还是糊的说明指标在骗你要换评估方式。还有一个技巧是分阶段训练先只用左右视图训一个基础去模糊网络收敛后再把视差图加进来微调。这样网络先学会去模糊再学会用深度信息比一上来就多通道输入更稳。微调时学习率降到 1e-5跑十几个 epoch 就够。如果微调后指标掉了检查视差图是不是有太多无效区域无效区域可以用 mask 屏蔽掉不让它参与损失计算。从那以后我每次拿到双像素数据都先跑一遍视差估计和左右对齐检查确认数据本身没问题再动网络。这个习惯帮我省了很多次白跑训练的时间。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。