基于SRCNN的哨兵2号遥感影像超分重建:从10米到2.5米实战
2026/9/16 21:18:56 网站建设 项目流程

做遥感的人多少都经历过这种尴尬:哨兵2号影像免费、覆盖广、更新勤,但10米分辨率放在县级尺度还好用,一放大到村镇级就糊成一片,道路、田块、房子边界全靠猜。想用高分商业影像,钱又不答应。于是不少人把目光投向了超分重建——用深度学习模型把10米影像“脑补”成2.5米。我最近用SRCNN把这个流程完整跑了一遍,从下载哨兵2数据、做训练集、训练模型,到输出带坐标的2.5米GeoTIFF,全程没花一分钱买数据。这篇文章就记录一下整个项目的思路、代码和踩过的坑,给同样想做遥感影像增强的读者一个可复制的起点。

先说结论:SRCNN这种2014年的老模型,放到今天依然很适合遥感超分场景,尤其是哨兵2这种“量大管饱但分辨率不够细”的数据源。它结构极简,训练成本低,CPU都能推理,而且效果比直接bicubic放大扎实得多。当然,超分不是无中生有,它更像是在影像先验约束下的纹理增强和边缘重建——10米像素里被模糊掉的信息,模型会按“看起来合理”的方式补回来,所以适合目视解译和特征提取,不适合拿去做像元级定量反演。这个边界心里要有数。

1. 项目整体设计与思路拆解

1.1 为什么选SRCNN而不是更花哨的模型

SRCNN是2014年提出的超分重建开山之作,核心思路非常简单:先用bicubic把低分辨率图放大到目标尺寸,再让三个卷积层去学习从“插值放大图”到“高清图”的映射关系。放在今天,这个结构比SRGAN、ESRGAN、SwinIR落后不少,但用在哨兵2号这种场景里,它有几个实实在在的好处。

第一,模型轻。SRCNN参数不到十万,训练起来特别快,一张普通显卡几分钟就能跑一个epoch,甚至纯CPU也能在几小时内完成训练。哨兵2影像动辄上万乘上万像素,推理阶段要分块处理,轻量模型在这里是巨大优势——不用堆显卡,普通办公电脑就能批处理整景影像。

第二,稳定性好。遥感影像不像自然图像那样“错一个像素无所谓”,超分结果会被拿去叠图、分类、量算。SRCNN的训练目标是MSE损失,输出天然偏“保守”,不会像GAN类模型那样凭空生成让人惊艳但不可信的细节。对我这种要拿结果做分析的人来说,保守往往是优点。

第三,对初学者友好。如果你是第一次接触深度学习遥感应用,SRCNN几乎是完美的练手项目。我之前在《动手学深度学习》里刷过CNN相关章节,真正自己复现一个模型时,还是从这种经典结构入手最有踏实感。等流程跑通了,后续想换ESRGAN、SwinIR,也只是替换网络主体的事,数据流和工程框架都不用大改。

1.2 10米到2.5米的数据闭环怎么设计

这里有个绕不开的问题:超分训练需要成对的低分辨率/高分辨率样本,哨兵2号本身没有2.5米真值,那训练标签从哪来?我在项目里试了两条路,也推荐大家按场景选择。

方案A:降尺度模拟训练(本文实操方案)。做法是先把10米影像当作“高清真值”,用bicubic下采样4倍得到40米影像,再把40米影像上采样回10米作为低分辨率输入,训练SRCNN把输入重建回原始的10米影像。推理阶段,对真实的10米哨兵2影像做同样的bicubic上采样到2.5米网格,送进模型,得到超分结果。

这个方案的好处是零成本、可快速验证,但要说清楚一点:训练时的数据对是“40米→10米”,推理时是“10米→2.5米”,相对尺度都是4倍,绝对尺度不同,属于尺度外推。实际效果通常不错,因为bicubic退化模型的一致性很好,模型学到的主要是高频先验和边缘锐化,换一个绝对尺度依然适用。但严格讲,这不等于真实4倍信息恢复。

方案B:高分辨率参考数据监督。如果项目对精度要求高,可以用同一区域的Planet 3米、SPOT 6/7或航空正射影像作为HR参考,哨兵2的10米波段作为LR输入,配对训练。这个方案更严谨,但数据不一定免费,而且跨传感器影像之间存在几何配准误差、大气差异和时相差异,预处理工作量会明显增大。

对比维度方案A:降尺度模拟方案B:外部高分参考
数据成本零成本可能需要购买或申请
是否需配准不需要需要严格几何配准
训练可控性高,退化模型已知低,受传感器差异影响
结果意义视觉增强、边缘锐化更接近真实信息恢复
适合场景快速出图、算法原型定量反演、高精度制图

我这次以方案A为主线,因为它最容易在没预算的前提下跑通全流程。如果你手头正好有高分参考影像,把训练数据替换一下就行,模型代码完全不用动。

2. 核心细节解析与实操要点

2.1 SRCNN模型结构到底在学什么

SRCNN的原始结构只有三层卷积,我用PyTorch重写了一份。需要注意,训练和推理时输入图像都要先经过bicubic插值放大到目标尺寸,网络做的是“插值结果到高清结果”的残差式修正。

import torch import torch.nn as nn class SRCNN(nn.Module): def __init__(self, num_channels=3): super(SRCNN, self).__init__() # 特征提取 self.conv1 = nn.Conv2d(num_channels, 64, kernel_size=9, padding=4) # 非线性映射 self.conv2 = nn.Conv2d(64, 32, kernel_size=1, padding=0) # 重建 self.conv3 = nn.Conv2d(32, num_channels, kernel_size=5, padding=2) self.relu = nn.ReLU(inplace=True) def forward(self, x): x = self.relu(self.conv1(x)) x = self.relu(self.conv2(x)) x = self.conv3(x) return x

三个卷积层分工明确:第一层9×9卷积感受野大,负责从低分辨率输入上提取局部边缘、纹理等特征;第二层1×1卷积做跨通道特征融合,相当于把一个高维特征向量映射到更高层的语义空间;第三层5×5卷积把特征图重新组合成RGB图像。整个过程可以理解成一个可学习的“高清化滤镜”。

原始论文里用的是valid卷积,输出尺寸会比输入小一圈,所以训练时要对标签做中心裁剪。我在代码里用same padding保持尺寸不变,效果没有实质差别,还省去裁剪的麻烦。损失函数就是最标准的MSE,衡量的是预测图和真值之间的逐像素误差。

2.2 数据准备:从下载哨兵2影像到制作训练集

数据源方面,我推荐直接从Copernicus Data Space下载L2A级产品,也就是已经做过大气校正的地表反射率数据。它的10米波段包括B2蓝、B3绿、B4红和B8近红外,做真彩色合成用B2/B3/B4就够了。下载后先检查云量,挑云量小于10%的景。

拿到影像后的第一步是用rasterio读取,把需要的波段打包成一个三通道影像。这里有个细节:L2A地表反射率的有效值范围是0到10000,无效区域(如云掩膜、边缘)通常是0或NaN,训练前必须处理。我一般把小于0或大于10000的像素统一置为0,并在裁剪patch时跳过有效像元占比低于95%的块,避免模型学到一堆无意义的空洞纹理。

制作训练集的核心代码大概长这样:

import numpy as np import rasterio from torch.utils.data import Dataset def load_rgb_bands(path): with rasterio.open(path) as src: b2 = src.read(1).astype(np.float32) b3 = src.read(2).astype(np.float32) b4 = src.read(3).astype(np.float32) # 反射率量纲转成0~1,方便网络收敛 rgb = np.stack([b2, b3, b4], axis=0) / 10000.0 return np.clip(rgb, 0.0, 1.0) def make_hr_lr_pair(hr_patch, scale=4): # hr_patch: (C, H, W),直接用双三次下采样再放大,构造低分辨率输入 import torch.nn.functional as F hr = torch.from_numpy(hr_patch).unsqueeze(0) lr = F.interpolate(hr, scale_factor=1.0/scale, mode="bicubic", align_corners=False) lr_up = F.interpolate(lr, scale_factor=scale, mode="bicubic", align_corners=False) return lr_up, hr

裁剪patch时,我习惯用64×64或128×128尺寸。patch太小学不到大范围纹理关系,patch太大训练慢且显存压力大。为了增强泛化性,每块patch都做随机翻转和90度旋转,相当于把数据量翻了8倍。如果手里有五六景不同地貌的哨兵2影像,全部裁完能得到几万对训练块,对SRCNN这种小模型来说完全够用。

3. 实操过程与核心环节实现

3.1 训练SRCNN的完整代码与参数选择

训练配置我直接给出可复用的参数,都是从实际效果里调出来的经验值,新手照抄问题不大:

  • 输入patch:64×64×3
  • batch size:32
  • 优化器:Adam,初始学习率1e-4
  • 损失函数:MSE
  • epoch:60到80,最后一个阶段学习率降到1e-5
  • 验证集:单独留一景影像的patch,不参与训练

训练主循环的核心代码:

import torch.optim as optim from torch.utils.data import DataLoader model = SRCNN(num_channels=3).cuda() optimizer = optim.Adam(model.parameters(), lr=1e-4) criterion = nn.MSELoss() for epoch in range(epochs): total_loss = 0.0 for lr_up, hr in train_loader: lr_up, hr = lr_up.cuda(), hr.cuda() optimizer.zero_grad() sr = model(lr_up) loss = criterion(sr, hr) loss.backward() optimizer.step() total_loss += loss.item() if (epoch + 1) % 20 == 0: # 验证集上看一下PSNR,防止过拟合到训练区 val_psnr = evaluate(model, val_loader) print(f"Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.4f}, Val PSNR: {val_psnr:.2f} dB")

训练过程中,你会发现MSE loss下降得很快,前10个epoch就能看到明显效果,后面进入缓慢优化阶段。这种“大头在前”的特征正是小模型的好处——不需要像大模型那样烧几十万步。

一个比较容易踩的坑是训练集与验证集的划分必须按“景”来分。如果你从同一景影像的不同位置裁patch,一部分训练一部分验证,验证指标会虚高很多,因为模型已经见过同一个漏检区域或同一种地物纹理了。我后来改成用两景完全不同的影像做验证,PSNR和SSIM才变得有参考意义。

3.2 推理:单景哨兵2影像超分与GeoTIFF输出

训练好的模型要应用到整景哨兵2影像上,最忌讳的做法是直接把整张影像一次性送进网络。一万像素乘一万像素的输入,显存瞬间爆炸。正确做法是分块推理,每块256×256,带一定重叠区域,最后做平均融合,避免拼接缝。

另外,GeoTIFF的地理坐标信息必须在整个流程中保持住。我的做法是:先用rasterio读取原影像的transform,然后把影像分块处理,每块的超分结果写回对应的高分辨率网格位置。

import rasterio import numpy as np import torch from rasterio.transform import Affine from rasterio.enums import Resampling def inference_geotiff(model, input_path, output_path, block_size=256, overlap=16): with rasterio.open(input_path) as src: height, width = src.height * 4, src.width * 4 transform = src.transform * src.transform.scale( src.width / width, src.height / height ) profile = src.profile.copy() profile.update(height=height, width=width, transform=transform) model.eval() result = np.zeros((src.count, height, width), dtype=np.float32) with rasterio.open(output_path, "w", **profile) as dst: for y in range(0, src.height * 4, block_size - overlap): for x in range(0, src.width * 4, block_size - overlap): # 从原影像按窗口读取,再放大到目标网格 win = rasterio.windows.Window( x // 4, y // 4, min(block_size // 4 + overlap // 4, src.width - x // 4), min(block_size // 4 + overlap // 4, src.height - y // 4) ) block = src.read(window=win, out_shape=( src.count, win.height * 4, win.width * 4 ), resampling=Resampling.bilinear) block_in = torch.from_numpy(block / 10000.0).unsqueeze(0).float() with torch.no_grad(): out = model(block_in.cuda()).cpu().numpy()[0] * 10000.0 # 写回,重叠区域直接取均值 ...

实际写代码时,重叠区域的融合建议用距离权重,也就是越靠近块中心权重越高,块边缘权重越低。这样可以显著减少拼接缝。如果嫌麻烦,一个简单的平均也能用,只要overlap取16个像素以上。

输出GeoTIFF后,可以在QGIS里直接叠加到原始10米影像上对比查看。最直观的效果是:道路边界更锐利,农田地块之间的田埂分界线更清楚,城镇区域屋顶轮廓有明显改善。

3.3 效果怎么评估:有参考指标和无参考指标

评估这一步,我分两种情况说。

如果你的验证集是模拟数据(比如把10米降采样到40米再超分回10米),可以直接用PSNR和SSIM做有参考评估。代码很简单:

from skimage.metrics import peak_signal_noise_ratio as psnr from skimage.metrics import structural_similarity as ssim score_psnr = psnr(hr_image, sr_image, data_range=1.0) score_ssim = ssim(hr_image, sr_image, channel_axis=0, data_range=1.0)

我实测下来,在模拟数据上SRCNN通常比bicubic高0.8到1.5dB PSNR,SSIM提升在0.01到0.03左右。这个差距在数值上看起来不大,但目视效果差异却非常明显,尤其是高频细节区域。

如果是真实影像的10米到2.5米超分,没有真值,就需要无参考清晰度指标。最常用的是拉普拉斯方差(Variance of Laplacian),它衡量图像梯度能量的强弱,值越大说明边缘越锐利。另一个是Tenengrad梯度,基于Sobel算子计算。这类指标不能说明“恢复的信息是对的”,但可以说明“图像变清晰了”。

import cv2 def variance_of_laplacian(img): return cv2.Laplacian(img, cv2.CV_64F).var()

更重要的是目视抽查。我通常挑三类区域做对比:道路交叉口、农田边界、城镇建筑轮廓。如果这些地方的边缘比bicubic放大结果更干净利落,而且没有出现明显的伪纹理或者扭曲,这个模型就算合格。

4. 常见问题与排查技巧实录

4.1 新手最容易翻车的5个问题

跑这个项目时我踩了不少坑,有些问题折腾了大半天才找到原因。这里整理成一张速查表,希望后面的人能少走弯路。

问题现象根本原因解决办法
训练loss不下降归一化不一致或学习率过大输入和标签统一除以10000,把学习率降到1e-4
输出图像整体偏灰反射率值域被模型压到0~1之间过窄训练前做min-max或固定区间归一化,推理后做反向映射
色彩严重偏色每个通道独立归一化,破坏了光谱比例三通道用同一组最大最小值统一缩放
大图拼接处有接缝分块推理没有重叠或没做融合重叠16~32像素,边缘做线性衰减融合
云和阴影区域出现伪纹理训练集混入了太多无效值根据云掩膜剔除无效patch,推理时可对云区跳过超分

这里面最隐蔽的是第一个坑。SRCNN对输入值域非常敏感,输入是0到1的浮点,标签却是0到10000的整数,loss会瞬间爆炸或者根本不收敛。我后来统一把反射率除以10000,再clip到0到1,模型才正常起来。

4.2 一些比较深的实操心得

项目跑通之后,回头总结,有几个经验想重点分享。

第一个经验是:别一上来就做4倍超分。我最初直接做10米到2.5米,总感觉边缘不够干净。后来改成先做2倍(10米到5米),目视效果进步非常明显,再叠加一次2倍超分到2.5米,比单次4倍更稳定。当然,分两次推理会多花一点时间,但换来的是更好的边缘质量,特别适合哨兵2这种本身纹理信息就不算丰富的数据源。

第二个经验是:训练数据的地物类型一定要和目标区域匹配。我用城区影像训练后去推农田区,田块边界普遍发虚;反过来用农田区训练去推城区,屋顶纹理又会出现奇怪的振铃。解决方案很简单,多收集几景不同地貌的影像混着训练。SRCNN吃数据,给它五六种典型地貌,泛化性会明显提升。

第三个经验是:超分结果适合做“视觉增强底图”,不适合直接当测绘成果。10米波段本身的光谱信息有限,超分重建只能优化空间纹理,没法凭空创造真实的高频细节。我一般把超分影像作为辅助图层,叠加在原始10米影像上做半透明显示,既保留了原始光谱可靠性,又能利用锐利边缘辅助判读。

5. 总结与一些个人体会

这个项目我从数据准备到最终出图大约花了三个晚上,其中大部分时间都耗在了数据清洗和调试归一化上,真正写模型反而很快。SRCNN之所以到现在还有生命力,是因为它在“工程稳定性”和“效果提升”之间找到了一个很实用的平衡点。做遥感应用的朋友,如果只是想让现有免费影像在目视层面更好用,完全不需要一上来就上生成对抗网络,SRCNN这套轻量方案在多数场景里已经够用。

我个人在实际操作中最满意的一点,是这个流程完全可重复、不依赖昂贵硬件。训练一晚上,推理一景影像半小时,全程免费数据、开源工具。如果后续想做更深度的增强,可以考虑把网络换成EDSR或SwinIR,再配合感知损失,细节真实度会进一步提升。但作为第一步,先用SRCNN把数据管线、训练流程、评估方案跑通,价值已经很大。

最后再分享一个汇报小技巧:把超分结果做成滑动对比图或者透明叠加图,左边是bicubic放大,右边是SRCNN结果,领导或者甲方一眼就能看出差别。技术指标说得再多,都不如一张直观的对比图有说服力。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询