
简介一套基于Unet网络实现天文图像降噪的Python项目源码面向计算机相关专业的学生完成课程设计、毕业设计或图像降噪实践。项目采用先为原始图像添加噪声、再将噪声图与原始图分别作为训练集和标签的方式借助Google Colab平台完成Unet模型训练覆盖数据生成、模型构建、训练与结果展示全流程。资源包共209个文件压缩后约35.94MB主要包含png格式图像样例、npy格式原始及不同等级噪声数据、py格式数据生成脚本、ipynb格式教程与结果演示Notebook以及模型相关文件与项目说明文档。文件组织清晰便于直接对照学习或二次开发。目前已有383人学习浏览代码均经过运行验证适合入门者理解Unet在图像去噪任务中的应用。下载后可从README.md入手通过自带数据快速复现训练流程也可修改generate_data.py生成自定义噪声数据或调整网络结构迁移到其他降噪场景具有较高参考价值。1. 一张暗场照片逼我重写降噪逻辑Unet 不是分割模型是噪声分布拟合器拿到「基于 Unet 网络实现对天文图像的降噪处理」这个项目标题时我的第一反应是疑惑Unet 不是做图像分割的吗用它跑过细胞膜分割、遥感地物分类怎么跑到降噪领域来了直到我把一张带着明显读出噪声的暗场照片丢进训练好的 Unet 权重里看到输出端吐出一张干净背景图才意识到天文图像降噪一直没绕开的那个坑——噪声根本不是简单的高斯白噪声而是读出噪声、暗电流、光子泊松噪声叠出来的混合信号。Unet 的编码器-解码器结构天然适合把高维噪声分布映射成低维结构再配合残差学习网络学的其实是「什么该被去掉」。这篇笔记写给想复现或重构这个方向的天文数据处理从业者讲清楚原理、实现、参数和踩坑。2. 为什么选 Unet 做天文图像降噪从混合噪声模型到残差学习2.1 天文图像噪声不是一种噪声读出噪声、暗电流与泊松光子的叠加场天文图像和普通照片最大的区别在噪声来源。普通摄影的噪声通常可以近似为信号无关的高斯白噪声但天文图像是长时间曝光的产物噪声由多个物理过程叠加而成。读出噪声是 CCD/CMOS 读出电路产生的电子噪声近似高斯分布信号无关暗电流是热激发产生的电子积累随温度指数增长强相关于曝光时间在暗场中表现为像素间的非均匀性光子到达探测器符合泊松分布也就是散粒噪声信号越强噪声越大。三者叠加之后噪声方差在暗区和亮区不一样直接套高斯去噪模型会在亮区域留下明显残留。传统管线处理这类噪声的做法是分步走先减偏置再减暗场然后对小波变换系数或 BM3D 块匹配做空间域滤波。这套流程的问题是每个环节都是手工选参数一旦曝光时间变化、温度波动、像素坏点叠加进来噪声非平稳性就会突破单一滤波器的假设。Unet 在这里的优势不是更强的「滤波」而是能隐式建模噪声的空间分布和强度分布——在目标检测和分割里学特征的思路放到降噪里就是学「噪声在这个像素位置长什么样」。理解这一点特别关键Unet 输出的不是一张模糊的干净图而是一张噪声强度图。天文图像里的天光背景往往是空间缓变的曝光时间越长背景梯度越明显传统滤波会把背景梯度当成信号保留而 Unet 能利用更大的感受野把「渐变的背景」和「局部随机噪声」分开。这也是为什么单纯把高斯滤波的半径调大永远得不到同等质量的降噪结果。2.2 Unet 编解码结构与跳跃连接下采样扩感受野跳跃连接保星点细节Unet 的结构分为编码器和解码器两条路径。编码器通过逐步下采样把输入从高分辨率压缩到低分辨率特征图每一步下采样都能扩大感受野让网络看到更大的上下文。对天文图像来说这极其重要因为天光背景是缓变的一个 256×256 的 patch 里边缘和中心的背景亮度差可能达到几十个 ADU如果感受野不够大网络会错误地把背景梯度假想为信号导致去噪不彻底或者产生虚假的平坦区域。解码器的作用是把低分辨率特征逐步上采样回原尺寸恢复像素级输出。跳跃连接把编码器对应层的特征拼接到解码器上这种做法保留了高频细节。对天文图像而言高频细节里既有噪声也有星点网络必须学会区分星点形状是平滑的高斯 PSF而噪声是像素级随机分布。跳跃连接让解码器不需要从低分辨率特征里凭空硬编出这些细节而是直接结合原始特征做判断。正是因为这些结构特性Unet 在降噪任务里表现为「按像素做回归的分割器」它给每个像素输出一个连续数值表示该位置噪声强度的估计值而不是像语义分割那样输出类别标签。实现上的差别很小把最后激活从 softmax 换成恒等映射或线性层损失从交叉熵换成回归损失网络就从图像分割模型变成了降噪模型。很多人第一次接触这个项目时在这里绕了弯以为要改网络结构其实核心只是任务重定义。2.3 把降噪改写成残差学习输入带噪图输出噪声图干净图靠相减得到直接让网络输出干净图像预测目标灰度范围大、细节复杂优化难度很高。项目里采用的是残差学习输入带噪图 x网络拟合的是噪声 n y - xy 为真值图训练目标是让网络输出的 n_hat 逼近真实噪声图最终干净图通过 x - n_hat 恢复。用残差学习有几个直接好处。第一干净图像的大尺度结构天体形状、背景梯度不需要网络从头重建网络只需要集中精力预测高频噪声第二噪声图的大部分区域是零均值的随机分布输出目标稀疏且简单收敛速度明显更快第三实际推理时如果遇到训练分布之外的微弱目标残差输出天然带着保守性不容易把真实信号彻底抹掉。损失函数选 L1 还是 L2 是争议点。L2 对异常大噪声惩罚更强但会产生模糊感星点边缘发毛L1 对像素级离群值更鲁棒星点形态保持得更好。我自己实际测试的策略是先用 L1 训到一个稳定阶段再切到 L1 与 L2 加权混合微调几十轮实测综合 PSNR 比单损失高出 0.3~0.8 dB。这个结论在不同数据集上差异不小建议把损失函数作为可配置项写进训练脚本里方便批量对比实验。3. 训练数据怎么来模拟星场与真实暗场对齐是复现项目的前提Unet 降噪项目最大的门槛不是网络结构而是训练数据。天文图像的真值在真实观测中几乎不存在因为没有办法拍摄一张「完全没有噪声」的星空。这一点决定了数据准备策略和通用 CV 项目完全不同也是项目复现时最容易卡住的地方。3.1 三种主流训练数据来源模拟星场、多帧叠加与公开数据集第一种最常用也是项目说明里最常见的方案用天体物理模型合成带噪图像同时保留无噪声真值。具体做法是先生成星点位置和亮度用高斯 PSF 渲染出干净星场再叠加天空背景梯度最后按噪声模型加泊松散粒噪声和读出噪声。合成数据的真值是精确已知的训练时 loss 不会受噪声干扰因此它成为首选方案。第二种是真实观测多帧叠加对同一目标拍摄上百帧短曝图像对齐后取中位数或均值作为真值。这个方案的数据分布最真实但问题也很明显——对齐误差会让星点出现双轮廓真值本身仍包含残余噪声而且在没有专业望远镜数据的团队里难以落地。即使有数据上百帧的对齐运算量也很大预处理时间可能是训练时间的几倍。第三种是拼接公开天文图像数据。像 Hubble Legacy Archive 这类公开数据中有很多图像是已经处理过的科学产品直接用来训练会引入处理痕迹比如平滑伪影、去背景残留、HST 点扩散函数特征对降噪网络反而有害。公开数据集更适合用来做验证集而不是训练主力。综合来看做训练集时优先模拟数据因为它可控、可复现、可定制噪声参数。3.2 用 Python 合成带噪星场高斯 PSF、泊松光子与读出噪声的叠加下面这个脚本生成一张 256×256 的带噪星场星点用二维高斯模拟 PSF背景加缓变梯度噪声按泊松加高斯模型叠加。这是模拟数据里最基础但也最实用的实现后续改星点密度、噪声水平、背景梯度都从这里入手import numpy as np def simulate_noisy_starfield(size256, n_stars40, psf_sigma1.2, background300.0, bias100.0, read_noise8.0, gain1.0, seed42): rng np.random.default_rng(seed) yy, xx np.mgrid[0:size, 0:size] # 信号部分天光背景 缓变梯度 星点PSF signal background * (1.0 0.1 * np.sin(xx / size * np.pi)) for _ in range(n_stars): cx, cy rng.uniform(8, size - 8, size2) flux rng.uniform(2000.0, 20000.0) # 星点总光子数 r2 (xx - cx) ** 2 (yy - cy) ** 2 signal flux / (2 * np.pi * psf_sigma ** 2) * \ np.exp(-r2 / (2 * psf_sigma ** 2)) # 泊松散粒噪声光子到达的随机性 photons rng.poisson(np.maximum(signal, 0) * gain) # 偏置 高斯读出噪声 noisy photons bias rng.normal(0, read_noise, size(size, size)) # 真值图 信号 偏置 clean signal bias return clean.astype(np.float32), noisy.astype(np.float32)逻辑说明clean 是训练用的低噪真值由天光背景、缓变梯度和二维高斯 PSF 星点组成。signal 先做泊松抽样模拟光子到达探测器时的随机性然后叠加固定偏置和零均值高斯读出噪声。这样生成的数据中像素越亮的区域噪声越大符合真实 CCD 的噪声特征。参数说明psf_sigma 控制星点半高全宽设得太大星点变肥测试时细窄星点会被网络当成噪声抹掉read_noise 设到 15 以上时信噪比急剧下降网络更难收敛seed 必须固定否则每次生成的数据不同无法复现实验。生成的数据最好以 float32 存成 .npy 或 FITS 格式不要用 JPEG 或 PNG 保存有损压缩会引入网络学不到的额外噪声。3.3 数据增强与归一化16bit 天文图像进网络前的必修课天文图像通常是 16bit 甚至 32bit 存储像素范围到 65535直接喂给网络会让权重和梯度爆炸。归一化是必须步骤推荐的做法是统计训练集的均值和标准差用 (x - mean) / std 做标准化而不是线性缩放到 [0,1]。原因是天光背景在图像中占比极大线性缩放会把暗弱目标压到小数点后好几位信息严重丢失网络很难学到暗目标的细节。数据增强方面星空图像具有近似旋转不变性水平翻转、垂直翻转在物理上是合理的等效于镜像观测。唯一要注意的是不能做任意角度旋转CCD 像素网格是方形的任意角度旋转会引入插值伪影破坏星点 PSF 的高斯形态这会让网络学到的噪声分布偏离真实观测。patch 大小也影响训练成败。256×256 是 Unet 深度为 4 到 5 时比较稳妥的输入尺寸更小的话感受野抓不到背景梯度更大则显存开销翻倍。训练时用随机裁剪验证时用中心裁剪并固定位置避免同一张图的不同裁剪版本同时出现在训练集和验证集里造成数据泄漏。4. 用 Python 从零搭一个 Unet 降噪网络骨架代码、训练配置与收敛判断这一章直接给出一套可运行的 PyTorch 实现包括网络骨架、训练循环和收敛判断方法。里面的参数是跑通的最小配置后续可以根据显存和效果调整。4.1 编码器与解码器的最小实现卷积双模块、下采样与跳跃连接下面代码实现了一个针对 256×256 输入、深度为 4 的 Unet。每个编码层用两次卷积加 BatchNorm 和 ReLU下采样用双线性插值而不是池化或跨步卷积这是降噪任务里减少空间伪影的关键细节import torch import torch.nn as nn class ConvBlock(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.block nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1, biasFalse), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1, biasFalse), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), ) def forward(self, x): return self.block(x) class UNetDenoise(nn.Module): def __init__(self, in_ch1, base_ch32, depth4): super().__init__() self.enc nn.ModuleList() self.dec nn.ModuleList() ch in_ch for i in range(depth): self.enc.append(ConvBlock(ch, base_ch * (2 ** i))) ch base_ch * (2 ** i) self.bottleneck ConvBlock(ch, ch * 2) for i in range(depth - 1, -1, -1): out_ch base_ch * (2 ** i) self.dec.append(nn.ConvTranspose2d(ch * 2, out_ch, 2, stride2)) self.dec.append(ConvBlock(out_ch out_ch, out_ch)) ch out_ch self.head nn.Conv2d(base_ch, in_ch, 1) def forward(self, x): skips [] for enc in self.enc: x enc(x) skips.append(x) x nn.functional.interpolate( x, scale_factor0.5, modebilinear, align_cornersFalse) x self.bottleneck(x) for i in range(len(self.dec) // 2): x self.dec[i * 2](x) # 转置卷积上采样 x torch.cat([x, skips[len(skips) - 1 - i]], dim1) x self.dec[i * 2 1](x) # 卷积块融合 return self.head(x)逻辑说明编码器每一层保存一个跳跃连接特征下采样用双线性插值避免跨步卷积在降噪任务中引入空间伪影。解码器用转置卷积上采样后与对应编码层特征拼接。头部 1x1 卷积把通道数降回 1输出的是噪声图而不是干净图这是残差学习的关键设计。参数说明base_ch 取 32 时模型参数量约 40M显存占用约 4GB适合大多数单卡环境base_ch 提到 64 后 PSNR 增益有限显存却会涨到 8GB 以上。depth 为 4 时感受野约 128×128对 256×256 输入已能覆盖背景梯度depth 为 5 时小目标损失增加星点细节保持变差需要针对数据实测。再说一个经验ConvTranspose2d 上采样容易产生棋盘伪影如果训练后发现输出存在周期性条纹把上采样换成 nearest 插值加 3×3 卷积通常能直接消除。这个改动在项目里属于性价比最高的一行代码。4.2 训练循环与关键配置损失函数、优化器、批大小与早停训练配置直接决定最终能收敛到的 PSNR 上限。这里给出一套实测有效的配置优化器用 AdamW初始学习率 1e-4weight_decay 设 1e-5。SGD 在天文图像这种高动态范围数据上收敛太慢不推荐首试。损失函数用 L1 为主联合 L2 微调L1 权重 0.7、L2 权重 0.3对星点边缘保持更好。批大小在 256×256、base_ch32 配置下推荐 8低于 4 时 BatchNorm 统计量不稳定需要改用 InstanceNorm 或梯度累积。学习率调度用 CosineAnnealingLRT_max 设 50eta_min 设 1e-6前 10 轮做 warmup 从 1e-5 线性升到 1e-4。早停条件设为验证集 PSNR 连续 15 轮不提升保存验证 PSNR 最高的权重而不是最后一轮这是训练过程的后悔药永远保留最佳版本。def train_one_epoch(model, loader, optimizer, device): model.train() total_loss 0.0 for batch in loader: noisy batch[noisy].to(device) clean batch[clean].to(device) pred_noise model(noisy) loss 0.7 * nn.functional.l1_loss(pred_noise, clean - noisy) \ 0.3 * nn.functional.mse_loss(pred_noise, clean - noisy) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() return total_loss / len(loader)逻辑说明模型输入是带噪图预测目标是干净图减带噪图得到的噪声图这样网络学的是残差而不是完整图像训练更容易稳定。损失函数按 0.7 和 0.3 加权混合兼顾星点边缘和整体亮度。训练循环每一步都清空梯度、反传、更新这个顺序不能乱否则梯度会累积导致发散。参数说明batch_size 降到 2 时建议开启梯度累积每 4 步累积一次等效批大小 8开启 torch.cuda.amp 混合精度可以再省一半显存但要注意 L2 损失在 fp16 下容易溢出把计算损失的张量显式转回 fp32 再算。4.3 训练到什么程度算好三个信号一起看别只盯 loss单看训练 loss 下降很容易被误导。我一般同时看三个信号验证集 PSNR、验证集残差标准差、以及一张固定验证图像的可视化输出。验证集 PSNR 持续上升说明模型泛化在变好如果验证 PSNR 上升后开始下降训练 loss 还在降就是过拟合信号应提前停或加强数据增强。如果验证 PSNR 一直徘徊在 28~30 dB 不涨优先怀疑模拟数据与验证数据分布不一致。验证集必须从数据生成阶段就独立抽出至少 10~20 张任何训练步骤都不能碰到。验证时保持固定裁剪位置确保每个 epoch 的结果可比。训练结束后选一张包含明亮星点和暗弱目标的验证图像把干净图、带噪图、预测噪声图、去噪结果四张并排打印出来肉眼看星点是否平滑、背景有没有残留条纹、暗目标是保留还是被抹除这一步永远比指标直观。保存权重时建议连同一个 tiny JSON 一起存记录 in_ch、base_ch、depth、mean、std 五项信息否则推理时根本不知道该用什么参数做归一化。项目说明文档里如果写了模型配置却漏了归一化参数复现的人大概率会在这里翻车。5. Unet 降噪避坑指南五个让我返工的真实问题与排查路径5.1 现象训练集 PSNR 很高测试集却出现周期性网格伪影原因解码器用 ConvTranspose2d 上采样卷积核无法整除步长时会产生棋盘效应通道数越多周期性条纹越明显在纯噪声区域观察特别清楚。解决上采样改成 nearest 插值加 3×3 卷积或者用 PixelShuffle。实测对比中 nearestconv 输出最平滑PixelShuffle 会引入微小的亚像素错位对测光有轻微影响因此优先推荐 nearestconv。改完后网络参数基本不变伪影通常立刻消失。5.2 现象星点被当成噪声抹掉去噪后星点变暗或干脆消失原因训练数据里星点占比太低默认 n_stars40 时星点只占整幅图面积不到 5%网络学到「删掉高频」是最小化损失的最短路把星点信号一并判成噪声。这是模拟数据最典型的坑真实数据里也一样亮星多时没问题暗星一多就露馅。解决提高训练数据中星点密度或者用星点掩膜对损失函数加权把星点区域的 L1 权重提到 2 到 3 倍。具体做法是生成数据时记录每个星点的位置和亮度创建一张二值掩膜星点半径内权重加倍让网络明白这些高频结构必须保留。5.3 现象同一帧图像每次推理结果不同输出抖动原因推理时没有把模型切到 eval 模式BatchNorm 还在用当前 batch 的统计量更新或者模型里残留了 Dropout 分支推理时仍随机丢弃单元。这种抖动通常肉眼不易察觉但多帧测光时会导致同一颗星在不同帧里的通量不一致。解决推理前强制调用 model.eval()并用 torch.no_grad() 包裹。固定随机种子设置 torch.backends.cudnn.deterministic True。如果还抖检查 forward 里有没有不小心保留训练专用的 Dropout。5.4 现象显存不足batch size 调到 2 仍然 OOM原因256×256 输入、depth5、base_ch64 的组合激活值缓存轻松超过 12GB加上中间变量没释放batch size 一调小就崩。这组配置在追求效果时常见但代价远超收益。解决优先把 base_ch 从 64 降到 32而不是急着改 depth。depth 从 5 降到 4 感受野从 128 降到 90 左右星点保持更好base_ch 减半才是显存杀手锏。开启混合精度训练显存再砍一半配合梯度累积把物理 batch size 降到 2等效 batch size 仍是 8。5.5 现象迁移语义分割的 Unet 预训练权重收敛非常慢原因语义分割输出概率图最后的分类层和降噪完全不兼容而且预训练输入归一化方式与天文图像统计量差异巨大初始权重落在了一个损失面很陡峭的位置前几十轮都在重新调整底层特征。解决不迁移从零开始训练。天文图像噪声分布与 ImageNet 差距太大迁移收益远低于通用视觉任务。如果一定想迁移只迁移编码器部分权重冻结 BatchNorm 的 running_mean 和 running_var学习率降到 1e-5 以下微调否则预训练权重反而拖慢收敛。6. 验证与部署PSNR 之外用残差图和 PSF 形态判断网络是否真的可用PSNR 和 SSIM 是常用指标但单看一个数字没法判断降噪结果是否破坏了天文科学信息。我习惯同时盯三样东西去噪结果图、残差图带噪图减输出去噪图、以及星点放大图。残差图如果出现明显的暗斑或亮斑说明网络把一部分真实信号当成噪声移除了如果残差图全是随机颗粒说明噪声被剥离得比较干净。星点放大图更能说明问题用各向同性的高斯 PSF 拟合去噪前后的星点若半高全宽变化超过 1 个像素说明网络把星点抹模糊了后续测光会系统偏暗。def evaluate(clean, noisy, denoised): mse np.mean((clean - denoised) ** 2) psnr 10 * np.log10(clean.max() ** 2 / max(mse, 1e-12)) resid noisy - denoised resid_std np.std(resid) return psnr, resid_std, resid逻辑说明残差标准差可以直接反映剩余噪声水平如果它远低于训练时模拟数据的 read_noise 设定值就要怀疑网络过度平滑了。PSF 形态检查用星点拟合的半高全宽做定量评价这个方法比肉眼可靠也是天文数据管线里通用的质量门槛。对多帧观测数据可以把 Unet 去噪结果与传统的中值叠加、小波滤波结果放在一起比确认在相同残差水平下Unet 是否保留了更多星点通量。这个对比区间的选择会直接影响结论建议固定使用同一套验证集避免不同批次数据带来的干扰。近两年我养成一个习惯每轮训练后固定跑一套验证脚本把模型版本、PSNR、残差标准差、PSF 半高全宽、训练数据种子全部记到一个 CSV 里。改网络深度、损失函数、数据增强策略时翻记录就能定位是哪一版改动带来的副作用不用重新从头排查。这个习惯帮我省下的时间比训练本身还多。如果后续想在 Unet 基础上继续改进可以从给编码器加 SE 注意力模块或者改残差 U 形结构入手但改之前一定先跑通上面这套验证流程否则改出来的网络效果涨没涨、涨在哪完全是一笔糊涂账。希望这些从实际项目里攒下的经验能帮你少走弯路——跑通 Unet 降噪不难难的是让它在真实天文数据上同样可靠希望我的这些踩坑记录能帮到你。本文还有配套的精品资源点击获取