
简介这份资源是论文《Deep learning super-resolution electron microscopy based on deep residual attention network》配套的显微图像「蝴蝶」数据集面向从事显微图像处理、深度学习超分与去噪研究的开发者与研究生。由于原论文提供的下载链接已失效、访问与下载速度极慢作者将其重新整理上传方便读者直接获取并复现相关实验。压缩包共205个文件以200张png显微图像为主体另含少量.m脚本与系统索引文件整体约94.39MBpng图像可直接用于超分辨率、去噪等任务的训练与测试m脚本则便于快速搭建数据准备流程。目前已有165人学习下载适合作为显微图像超分、去噪等任务的基准数据帮助读者省去四处寻找失效链接的时间快速投入模型训练与对比实验。1. 显微图像下的蝴蝶数据集为什么它成了超分与去噪任务的试金石显微图像下的蝴蝶数据集这个标题指向的是一类专门在显微镜视野下采集的蝴蝶鳞片、翅脉或整体标本图像集合核心用途是给超分辨率重建和图像去噪这类底层视觉任务提供训练与评测数据。它解决的是一个很具体的痛点自然图像超分数据集已经卷到饱和但显微成像场景下的退化模型完全不同——光学衍射极限、低数值孔径物镜带来的模糊、荧光染色引入的泊松噪声、以及扫描电镜下的散粒噪声这些退化在常规DIV2K或Set5上根本模拟不出来。适合谁用做医学影像超分、材料显微图像增强、生物切片去噪的算法工程师以及想找一个“干净但有挑战”的benchmark来验证自己新损失函数的研究生。显微图像蝴蝶数据集的价值不在于蝴蝶本身而在于它把显微成像的物理退化链路保留了下来让你调参时面对的是真实噪声分布而不是高斯白噪声的玄学。2. 显微图像蝴蝶数据集的技术底子从鳞片结构到退化建模2.1 为什么蝴蝶鳞片适合做显微超分与去噪的载体蝴蝶翅膀鳞片在显微镜下呈现准周期性的脊-肋结构周期大约在0.5到2微米之间这个尺度刚好落在光学显微镜分辨率极限附近。用20倍物镜采集时单像素对应约0.3微米鳞片脊线会出现明显的混叠和模糊用100倍油镜则能拍到接近衍射极限的细节但景深极浅稍微离焦就糊成一片。这种“天然多尺度可控退化”的特性让蝴蝶鳞片成为检验超分算法是否真的恢复了周期结构、而不是靠纹理合成糊弄过去的理想对象。去噪任务同理鳞片表面的颗粒感在低曝光下会被读出噪声淹没算法如果只会均值滤波脊线直接消失。常见做法是采集同一视野下的多帧低曝光图像做平均得到近似无噪的参考图再对单帧加噪或降采样构造低质-高质配对。我一般会保留原始位深12bit或16bit不做8bit量化因为显微图像的暗部细节一旦截断去噪后也补不回来。2.2 超分与去噪任务在显微图像上的退化差异自然图像超分的退化通常是双三次下采样加压缩伪影但显微图像的超分退化必须考虑点扩散函数PSF。PSF不是理想高斯而是艾里斑加像差旁瓣这导致高频信息丢失的方式和自然图像完全不同。去噪方面显微图像的噪声模型更接近泊松-高斯混合光子到达服从泊松分布读出电路引入高斯噪声。如果你直接用DnCNN在AWGN上训好的权重跑显微蝴蝶图暗区会出现明显的伪影和过度平滑。一个可操作的退化建模流程如下import numpy as np from scipy.ndimage import gaussian_filter def microscope_degrade(hr, scale4, psf_sigma1.2, poisson_peak30, read_std2.0): hr: 高分辨率显微图像, 归一化到[0,1], float32 scale: 下采样倍数 psf_sigma: 高斯近似PSF的sigma, 对应物镜模糊 poisson_peak: 泊松噪声峰值光子数, 越小噪声越强 read_std: 读出噪声标准差, 单位与hr量级一致 # 1. 光学模糊 blurred gaussian_filter(hr, sigmapsf_sigma) # 2. 下采样, 模拟低分辨率采集 lr blurred[::scale, ::scale] # 3. 泊松噪声: 先缩放至光子数域 lr_photon np.random.poisson(lr * poisson_peak) / poisson_peak # 4. 读出噪声 lr_noisy lr_photon np.random.randn(*lr.shape) * read_std return np.clip(lr_noisy, 0, 1).astype(np.float32)这段代码的关键参数是psf_sigma和poisson_peak。psf_sigma控制模糊程度20倍物镜建议1.0到1.5100倍油镜建议0.6到0.9。poisson_peak低于20时噪声会严重破坏鳞片脊线适合测试去噪算法的极限高于50则接近干净图像适合超分任务单独评估。read_std一般设1.0到3.0对应8bit图像的1到3个灰度级。注意下采样用切片而不是cv2.resize是为了避免插值引入额外高频保持退化链路的物理可解释性。2.3 数据集的组织方式与配对策略显微蝴蝶数据集通常按标本编号分文件夹每个标本下包含hr/和lr/两个子目录或者用.h5打包。配对策略有两种一种是同视野多帧平均得到HR再对单帧做退化得到LR另一种是用高倍镜图像作为HR低倍镜同区域作为LR但后者需要做严格的配准否则超分模型会学到错位映射。我倾向于第一种因为配准误差可控而且可以生成任意scale的LR。文件命名建议带退化参数比如specimen03_psf1.2_peak30_s4.png这样训练时可以直接按退化强度分组采样避免模型只适应单一噪声水平。如果数据集只给图像不给退化参数那就得自己反推看LR的噪声方差和边缘宽度估算psf_sigma和poisson_peak再生成匹配的验证集。3. 用显微蝴蝶数据集跑通超分与去噪的最小闭环3.1 环境准备与数据加载的必调参数先确认你的数据格式。如果是.tif堆栈用tifffile读如果是.h5用h5py。下面是一个最小加载器假设数据按specimen_id/hr.png和specimen_id/lr.png组织import os import cv2 import numpy as np import torch from torch.utils.data import Dataset, DataLoader class ButterflyMicroscopy(Dataset): def __init__(self, root, scale4, patch_size128, augmentTrue): self.root root self.scale scale self.patch_size patch_size self.augment augment self.specimens sorted(os.listdir(root)) def __len__(self): return len(self.specimens) * 20 # 每个标本随机采20个patch def __getitem__(self, idx): spec self.specimens[idx // 20] hr cv2.imread(os.path.join(self.root, spec, hr.png), -1) lr cv2.imread(os.path.join(self.root, spec, lr.png), -1) # 归一化到[0,1], 保留位深信息 hr hr.astype(np.float32) / 65535.0 if hr.dtype np.uint16 else hr.astype(np.float32) / 255.0 lr lr.astype(np.float32) / 65535.0 if lr.dtype np.uint16 else lr.astype(np.float32) / 255.0 # 随机裁剪 h, w lr.shape[:2] if h self.patch_size or w self.patch_size: lr cv2.resize(lr, (self.patch_size, self.patch_size)) hr cv2.resize(hr, (self.patch_size * self.scale, self.patch_size * self.scale)) else: top np.random.randint(0, h - self.patch_size) left np.random.randint(0, w - self.patch_size) lr lr[top:topself.patch_size, left:leftself.patch_size] hr hr[top*self.scale:(topself.patch_size)*self.scale, left*self.scale:(leftself.patch_size)*self.scale] # 数据增强: 翻转和旋转 if self.augment: if np.random.rand() 0.5: lr, hr np.fliplr(lr), np.fliplr(hr) if np.random.rand() 0.5: lr, hr np.flipud(lr), np.flipud(hr) # 转为tensor, 注意灰度图要加通道维 lr torch.from_numpy(lr.copy()).unsqueeze(0) hr torch.from_numpy(hr.copy()).unsqueeze(0) return lr, hrpatch_size建议设128对应HR的512×512这样显存占用可控batch16时约6GB。augment里的翻转和旋转对显微图像是安全的因为鳞片结构没有方向偏好但不要用颜色抖动显微图像的颜色是染色或反射光谱决定的改了颜色会破坏物理意义。__len__里乘20是每个标本采20个patch实际训练时每个epoch都能看到不同位置避免过拟合到固定区域。3.2 超分模型选型EDSR还是SwinIR显微图像超分和自然图像超分最大的区别是高频细节集中在特定频率带鳞片周期而不是全频段。EDSR的残差块堆叠能恢复平滑区域但对周期纹理容易产生振铃SwinIR的窗口注意力能捕捉局部周期但需要足够大的训练集否则窗口边界会出现拼接伪影。我的经验是数据量小于500对时用EDSR大于2000对时用SwinIR。如果只有几十对建议用预训练的Real-ESRGAN做微调但要把判别器的感受野调小否则会生成不存在的鳞片分支。一个EDSR的简化训练循环import torch.nn as nn import torch.optim as optim class EDSR(nn.Module): def __init__(self, scale4, n_resblocks16, n_feats64): super().__init__() self.head nn.Conv2d(1, n_feats, 3, padding1) self.body nn.Sequential(*[ResBlock(n_feats) for _ in range(n_resblocks)]) self.tail nn.Sequential( nn.Conv2d(n_feats, n_feats * scale * scale, 3, padding1), nn.PixelShuffle(scale), nn.Conv2d(n_feats, 1, 3, padding1) ) def forward(self, x): x self.head(x) x self.body(x) x return self.tail(x) class ResBlock(nn.Module): def __init__(self, n_feats): super().__init__() self.conv1 nn.Conv2d(n_feats, n_feats, 3, padding1) self.conv2 nn.Conv2d(n_feats, n_feats, 3, padding1) self.relu nn.ReLU(inplaceTrue) def forward(self, x): return x self.conv2(self.relu(self.conv1(x))) # 训练配置 model EDSR(scale4).cuda() optimizer optim.Adam(model.parameters(), lr1e-4) criterion nn.L1Loss() # L1比L2更少产生过平滑 scheduler optim.lr_scheduler.StepLR(optimizer, step_size50, gamma0.5) for epoch in range(200): for lr, hr in dataloader: lr, hr lr.cuda(), hr.cuda() sr model(lr) loss criterion(sr, hr) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step()n_resblocks16是EDSR的默认值显微图像建议加到32因为鳞片细节需要更深的感受野。n_feats64可以降到48来省显存但不要低于32否则高频恢复能力断崖式下降。损失函数用L1而不是MSE是因为L1对显微图像的暗部噪声更鲁棒MSE会过度惩罚暗区的小误差导致模型把暗部抹平。学习率1e-4配StepLR是稳妥选择如果loss震荡就降到5e-5。3.3 去噪任务的训练策略与噪声水平匹配去噪任务的关键是噪声水平估计。如果你不知道LR的噪声参数可以用小波变换的鲁棒中值估计import pywt def estimate_noise_sigma(img): 用Daubechies小波估计高斯噪声标准差 coeffs pywt.dwt2(img, db8) cH, cV, cD coeffs[1] sigma np.median(np.abs(cD)) / 0.6745 return sigma这个估计假设噪声主要是高斯型对泊松噪声会偏高。更准的做法是用Anscombe变换先把泊松-高斯混合噪声变成近似高斯再估计。但工程上我一般直接跑两轮第一轮用估计的sigma训练第二轮用第一轮模型在验证集上的残差方差修正sigma。去噪模型选DnCNN或FFDNetFFDNet可以接受噪声水平图作为输入更适合显微图像这种噪声空间变化的情况。训练时把噪声水平作为条件输入class FFDNet(nn.Module): def __init__(self, in_ch2, out_ch1, n_feats64, n_layers15): super().__init__() self.head nn.Conv2d(in_ch, n_feats, 3, padding1) self.body nn.Sequential(*[ nn.Sequential( nn.Conv2d(n_feats, n_feats, 3, padding1), nn.ReLU(inplaceTrue) ) for _ in range(n_layers) ]) self.tail nn.Conv2d(n_feats, out_ch, 3, padding1) def forward(self, x, sigma_map): # x: 噪声图像, sigma_map: 与x同尺寸的噪声水平图 inp torch.cat([x, sigma_map], dim1) return self.tail(self.body(self.head(inp)))sigma_map的生成方式如果整图噪声均匀直接填常数如果暗区噪声更强用局部方差图。显微图像建议用局部方差因为暗区的读出噪声占比更高。训练时sigma_map的值要覆盖实际噪声范围比如0.01到0.1用对数均匀采样这样模型对噪声水平变化更鲁棒。4. 显微蝴蝶数据集实战避坑从配准到评估的五个翻车点4.1 现象超分结果出现周期性网格伪影 → 原因下采样前未做抗混叠 → 解决先PSF模糊再下采样很多人直接对HR做cv2.resize下采样结果LR里保留了高频混叠超分模型学到的是混叠模式而不是真实退化。正确做法是先做PSF模糊高斯sigma≈0.5×scale再下采样。如果数据集已经给定了LR那就检查LR的频谱看有没有超过Nyquist频率的能量有的话说明采集时没加抗混叠滤波训练时要加一个可学习的模糊核来补偿。4.2 现象去噪后鳞片脊线消失 → 原因噪声水平估计偏低 → 解决用残差方差迭代修正用中值估计的sigma往往比真实值低20%到30%导致去噪强度不够模型学到的映射接近恒等。修正方法先用估计sigma训练一个初始模型在验证集上计算输出与HR的残差残差方差就是更准的噪声方差。迭代两到三次sigma会收敛。注意不要直接用HR和LR的差来估噪声因为LR还包含模糊差里混了信号。4.3 现象模型在训练集上PSNR很高但视觉上糊 → 原因L2损失过度平滑 → 解决换L1感知损失但感知损失权重要小显微图像的评估不能只看PSNR。L2损失会让模型输出条件均值高频细节被平均掉。换L1能缓解但还不够。加一个基于预训练VGG的感知损失权重设0.01到0.05能恢复一些纹理。但权重不能大否则会生成不存在的鳞片结构在生物形态学分析里是致命的。我一般用L10.02×感知损失再配一个总变差损失权重1e-5抑制噪声放大。4.4 现象不同标本之间的颜色/亮度差异导致模型泛化差 → 原因未做颜色归一化 → 解决按标本做直方图匹配或白平衡显微图像的照明和染色批次差异很大同一个模型在标本A上训好到标本B上PSNR掉3dB。解决训练前对每个标本做直方图匹配把HR和LR都对齐到同一个参考直方图。或者更简单对每个标本单独做白平衡除以背景区域的均值。如果数据量够用CycleGAN做域适应也行但工程上直方图匹配更快更稳。4.5 现象验证集PSNR正常但推理时出现棋盘格 → 原因PixelShuffle前的卷积初始化不当 → 解决用双三次上采样初始化最后一层PixelShuffle对卷积核的初始化敏感如果最后一层卷积权重方差太大上采样后会出现棋盘格。解决把PixelShuffle前的卷积核初始化为双三次插值的等效权重或者直接用nn.Upsample(modebicubic)加一个卷积层替代PixelShuffle。后者更稳但计算量稍大。我一般在训练初期用双三次初始化训练中期再放开权重。5. 显微蝴蝶数据集上的进阶技巧用退化感知训练和频域评估把超分去噪做到可复现5.1 退化感知训练把PSF和噪声参数作为条件输入普通超分模型对所有退化一视同仁但显微图像的退化参数PSF sigma、噪声水平在采集时是已知的。把这些参数作为条件输入模型可以自适应调整恢复强度。实现方式在EDSR的head前加一个参数编码器把psf_sigma和poisson_peak映射成64维向量然后通过FiLM层调制每个残差块的特征。训练时随机采样退化参数让模型见过足够多的组合。推理时输入实际采集参数PSNR能比无条件模型高0.5到1dB。class FiLMResBlock(nn.Module): def __init__(self, n_feats, cond_dim64): super().__init__() self.conv1 nn.Conv2d(n_feats, n_feats, 3, padding1) self.conv2 nn.Conv2d(n_feats, n_feats, 3, padding1) self.film nn.Linear(cond_dim, n_feats * 2) # 输出gamma和beta self.relu nn.ReLU(inplaceTrue) def forward(self, x, cond): gamma, beta self.film(cond).chunk(2, dim-1) gamma gamma.view(-1, x.size(1), 1, 1) beta beta.view(-1, x.size(1), 1, 1) h self.relu(self.conv1(x)) h h * (1 gamma) beta return x self.conv2(h)cond_dim64是退化参数的编码维度psf_sigma和poisson_peak先归一化到[0,1]再拼接通过两层MLP映射到64维。FiLM层的初始化要把gamma和beta的权重设小比如0.01否则训练初期会破坏预训练特征。5.2 频域评估用径向功率谱看超分是否恢复了鳞片周期PSNR和SSIM对显微图像的周期结构不敏感。更直接的评估是计算超分结果和HR的径向功率谱RPS看鳞片周期对应的频率峰是否恢复。具体做法对图像做2D FFT取模平方然后按半径平均得到1D功率谱。鳞片周期d对应频率f1/d在RPS上是一个峰。如果超分结果的RPS在f处有峰且幅值接近HR说明周期结构恢复了如果峰消失或偏移说明模型在合成纹理而不是恢复。def radial_power_spectrum(img): f np.fft.fft2(img) fshift np.fft.fftshift(f) mag np.abs(fshift) ** 2 h, w img.shape cy, cx h // 2, w // 2 y, x np.ogrid[:h, :w] r np.sqrt((x - cx)**2 (y - cy)**2).astype(int) r_max min(cy, cx) rps np.zeros(r_max) for i in range(r_max): mask (r i) if mask.sum() 0: rps[i] mag[mask].mean() return rps对比时把HR、LR、SR的RPS画在同一张图上看SR的曲线在哪个频率段贴近HR。如果SR在高频段高于HR说明模型在放大噪声如果低于HR说明细节丢失。这个评估比PSNR更能反映显微图像的质量。5.3 一个具体技巧用多帧低曝光图像做自监督去噪如果没有配对的高质图像可以用多帧低曝光图像做自监督去噪。核心思想同一视野拍N帧低曝光图每帧噪声独立但信号相同。用其中一帧做输入另外N-1帧的平均做目标训练去噪模型。这样不需要干净参考而且噪声模型是真实的。实现时注意N至少8帧否则平均后的目标仍有残余噪声帧间要做亚像素配准否则平均会模糊。配准用cv2.findTransformECC精度到0.1像素。我自己的习惯是拿到任何显微蝴蝶数据集先跑一遍RPS评估确认鳞片周期频率在哪个位置再决定超分模型的感受野和损失权重。如果周期频率在0.3到0.5 Nyquist之间感受野至少要覆盖两个周期也就是卷积核等效尺寸大于4倍周期像素数。这个习惯帮我省了很多盲调参的时间。希望帮到你。本文还有配套的精品资源点击获取