ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

高分二号遥感图像语义分割全流程实战指南

高分二号遥感图像语义分割全流程实战指南 简介本资源是一份面向遥感图像处理研究者、深度学习初学者及地理信息工程实践者的PyTorch语义分割实战教程聚焦高分二号GF-2等高分辨率遥感影像的地物精细分割任务解决环境监测、城市规划中像素级地物识别难、数据标注缺、模型复现弱等实际问题。压缩包共1029个文件含819张遥感影像与真值标签PNG图、35个核心Python训练/推理脚本含数据加载、U-Net结构实现、IoU评估等、1个CSV元数据文件、1份README.md说明文档及可视化结果样例图整体577.48MB结构清晰开箱即用。已有94人学习下载覆盖从遥感图像预处理、多波段数据读取、标签制作到模型训练与结果融合的完整链路附带真实场景预测效果图predict.png、blend.png、img_gt_pre.png及典型样本classes_sample.jpg显著降低遥感语义分割项目落地门槛。1. 高分遥感图像语义分割实现不是调个UNet就能跑通的“端到端流程”而是从GF2原始影像到像素级地物标签的完整闭环你手头有一张高分二号GF2PMS2传感器拍摄的遥感图波段数4、空间分辨率1米、幅宽约10km——但直接扔进PyTorch训练大概率在DataLoader卡死、loss不降反升、predict.png里全是噪点斑块。这不是模型不行而是遥感图像语义分割根本不是CV通用任务的简单平移它要求你同时处理多光谱对齐误差、大尺寸内存溢出、地物类别长尾分布、标注掩膜与原始影像坐标系错位这四大硬伤。本项目提供的不是“PyTorch语义分割Demo”而是一套经过GF2_PMS2_E116.0_N39.1_20170302_L1A0002214760-MSS2实测验证的落地链路含真实采集的4波段影像含.csv元数据、人工精标8类地物掩膜classes_sample.jpg定义、预处理脚本、轻量UNet主干ASPP增强结构、以及可复现的img_gt_pre.png可视化对比结果。适合遥感方向研究生做毕设基线、地信工程师快速部署小范围解译模块、或算法岗面试前突击遥感实战细节——尤其当你发现Cityscapes预训练权重在农田边缘完全失效时这份资源能让你少踩3周坑。2. 数据结构解析与预处理为什么GF2影像必须重采样归一化切块而不是直接resize2.1 GF2_PMS2原始数据的物理特性与格式陷阱项目中提供的GF2_PMS2_E116.0_N39.1_20170302_L1A0002214760-MSS2_*.png是经L1A级辐射定标后的4波段影像B1-B4对应蓝、绿、红、近红外但注意非标准RGB排列文件名末尾MSS2表示多光谱传感器2号其波段顺序为[B, G, R, NIR]而非OpenCV默认的[B, G, R]DN值范围非0-255原始DN值区间为0~102310bit量化直接转uint8会丢失近红外波段动态范围地理坐标未嵌入.csv元数据包含成像时间、太阳高度角、卫星姿态角但无GeoTIFF地理参考信息因此不能用rasterio直接读取空间坐标需依赖img_gt_pre.png中人工标注的像素级对应关系。提示classes_sample.jpg不是类别列表而是8类地物的彩色编码图如建筑红色、道路黄色、水体蓝色其RGB值即为训练时的label映射表。务必用cv2.IMREAD_UNCHANGED读取避免PNG alpha通道被丢弃导致颜色失真。2.2 四步预处理流水线从原始影像到PyTorch DataLoader可接受张量以下脚本需在data_preprocess.py中实现项目已提供完整代码此处拆解关键逻辑import numpy as np import cv2 from pathlib import Path def preprocess_gf2_image(img_path: str, label_path: str, target_size(512, 512), norm_methodminmax) - tuple[np.ndarray, np.ndarray]: # Step 1: 读取4波段影像并校验通道顺序 img cv2.imread(img_path, cv2.IMREAD_UNCHANGED) # shape: (H, W, 4) if img.shape[-1] ! 4: raise ValueError(fExpected 4 bands, got {img.shape[-1]} in {img_path}) # Step 2: DN值归一化关键避免NIR波段主导梯度 if norm_method minmax: # 对每个波段独立归一化保留光谱特征差异 img_norm np.zeros_like(img, dtypenp.float32) for i in range(4): band img[:, :, i] band_min, band_max band.min(), band.max() img_norm[:, :, i] (band - band_min) / (band_max - band_min 1e-6) else: # z-score适用于多时相联合训练 img_norm (img - img.mean(axis(0,1))) / (img.std(axis(0,1)) 1e-6) # Step 3: 切块非简单resize避免地物形变 h, w img_norm.shape[:2] patches [] for i in range(0, h, target_size[0]): for j in range(0, w, target_size[1]): patch img_norm[i:itarget_size[0], j:jtarget_size[1]] if patch.shape[0] target_size[0] and patch.shape[1] target_size[1]: patches.append(patch) # Step 4: 读取并同步label掩膜需严格像素对齐 label cv2.imread(label_path, cv2.IMREAD_UNCHANGED) # shape: (H, W, 3) label_patches [] for i in range(0, h, target_size[0]): for j in range(0, w, target_size[1]): patch label[i:itarget_size[0], j:jtarget_size[1]] if patch.shape[0] target_size[0] and patch.shape[1] target_size[1]: # 将RGB label转为单通道class_id查classes_sample.jpg映射表 label_id rgb_to_class_id(patch) # 实现见下文 label_patches.append(label_id) return np.stack(patches), np.stack(label_patches) def rgb_to_class_id(rgb_label: np.ndarray) - np.ndarray: 根据classes_sample.jpg定义的RGB→class_id映射生成单通道label # classes_sample.jpg中各色块中心RGB值实测提取 class_map { (255, 0, 0): 0, # 建筑红 (255, 255, 0): 1, # 道路黄 (0, 0, 255): 2, # 水体蓝 (0, 255, 0): 3, # 植被绿 (255, 165, 0): 4, # 裸土橙 (128, 0, 128): 5, # 农田紫 (0, 255, 255): 6, # 湿地青 (128, 128, 128): 7 # 其他灰 } h, w, _ rgb_label.shape label_id np.zeros((h, w), dtypenp.uint8) for r in range(h): for c in range(w): key tuple(rgb_label[r, c]) label_id[r, c] class_map.get(key, 0) # 默认建筑类避免未定义色块报错 return label_id参数说明target_size(512, 512)遥感影像切块尺寸需满足GPU显存限制RTX3090可支持512×512×4波段输入norm_methodminmax推荐初学者使用避免z-score在单景影像上因统计量偏差导致归一化失真rgb_to_class_id函数中class_map键值对必须与classes_sample.jpg实际像素值严格一致——建议用cv2.imshow逐像素校验这是后续训练label错位的首要排查点。2.3 预处理后数据集结构与加载器配置执行预处理后生成的目录结构如下data/ ├── train/ │ ├── images/ # 512×512×4的npy文件float32 │ └── labels/ # 512×512的npy文件uint80-7 ├── val/ │ ├── images/ │ └── labels/ └── classes_sample.jpg # 类别定义源文件torch.utils.data.Dataset需重写__getitem__以适配4波段输入class GF2Dataset(Dataset): def __init__(self, img_dir: Path, label_dir: Path, transformNone): self.img_paths sorted(img_dir.glob(*.npy)) self.label_paths sorted(label_dir.glob(*.npy)) assert len(self.img_paths) len(self.label_paths) self.transform transform def __getitem__(self, idx): img np.load(self.img_paths[idx]) # shape: (512, 512, 4) label np.load(self.label_paths[idx]) # shape: (512, 512) # PyTorch要求CHW格式且4波段需保持通道维度 img torch.from_numpy(img.transpose(2, 0, 1)) # → (4, 512, 512) label torch.from_numpy(label).long() # → (512, 512) if self.transform: img self.transform(img) return img, label关键配置项batch_size84波段×512×512输入在RTX3090上显存占用约12GBbatch_size8为安全上限num_workers4预处理已转为npy无需CPU实时解码num_workers0可加速数据搬运禁止使用transforms.Normalize因4波段归一化已在预处理阶段完成此处再标准化会导致NIR波段数值坍缩。3. 模型架构与训练策略为什么UNet比DeepLabV3更适合GF2小样本场景3.1 针对遥感图像的网络结构选型依据通用语义分割模型如DeepLabV3在遥感任务中常表现不佳核心矛盾在于感受野过大DeepLabV3空洞卷积设计针对城市街景物体尺度集中而GF2影像中农田斑块可达1000×1000像素建筑仅20×20像素单一感受野无法兼顾多尺度特征融合不足遥感地物存在显著尺度差异如水库vs电线杆UNet的嵌套跳跃连接能更精细地传递局部纹理与全局上下文参数量敏感本项目仅提供约200张切片≈1000样本大模型易过拟合UNet约12M参数比HRNet≈28M更适配小数据集。项目采用UNet with ASPP backbone非标准UNet主干替换为ResNet18ASPP结构如下Input (4,512,512) ├─ Encoder: ResNet18 (modified for 4-channel input) │ ├─ Stage0: Conv7x7 BN ReLU → (64,256,256) │ ├─ Stage1: 2×ResBlock → (64,128,128) │ ├─ Stage2: 2×ResBlock → (128,64,64) │ ├─ Stage3: 2×ResBlock → (256,32,32) │ └─ Stage4: ASPP module (rates[1,6,12,18]) → (256,32,32) ├─ Decoder: UNet nested skip connections │ ├─ Level1: 4×concat(Stage4, Stage3_up) → (512,64,64) │ ├─ Level2: 3×concat(Stage2_up, Level1_up) → (256,128,128) │ └─ Level3: 2×concat(Stage1_up, Level2_up) → (128,256,256) └─ Output: Conv1x1 → (8,512,512) Softmax3.2 4波段输入适配与损失函数定制ResNet18默认接收3通道输入需修改第一层卷积import torchvision.models as models def build_unetpp_aspp(num_classes8, pretrainedTrue): # 加载预训练ResNet18仅用ImageNet权重初始化前几层 backbone models.resnet18(pretrainedpretrained) # 替换第一层卷积以支持4通道输入 backbone.conv1 nn.Conv2d(4, 64, kernel_size7, stride2, padding3, biasFalse) # 初始化新卷积层权重保持ImageNet预训练特征迁移能力 if pretrained: # 复制原3通道权重第4通道用均值初始化 with torch.no_grad(): backbone.conv1.weight[:, :3] backbone.conv1.weight[:, :3].clone() backbone.conv1.weight[:, 3] backbone.conv1.weight[:, :1].mean(dim1) # 构建ASPP模块简化版避免引入过多参数 aspp ASPP(in_channels512, out_channels256, rates[1,6,12,18]) # UNet decoder部分项目已提供完整实现此处略 decoder UNetPPDecoder(backbone, aspp, num_classesnum_classes) return nn.Sequential(backbone, aspp, decoder)损失函数选择主损失Dice Loss CrossEntropy Loss加权组合α0.5缓解农田/植被等大类对loss的主导辅助损失Boundary-aware Loss在label边缘1像素内加权提升道路、水体等线状地物边界精度禁用Focal Loss因GF2数据集各类别样本量相对均衡classes_sample.jpg显示8类覆盖均匀Focal Loss反而降低小类召回。3.3 训练超参与收敛监控要点# 推荐训练命令基于项目提供的train.py python train.py \ --data_dir ./data \ --model unetpp_aspp \ --batch_size 8 \ --lr 1e-3 \ --epochs 120 \ --scheduler cosine \ --warmup_epochs 5 \ --weight_decay 1e-4 \ --save_dir ./checkpoints/gf2_unetpp_v1关键参数解释--lr 1e-34波段输入使梯度方差增大初始学习率需比RGB任务低10倍--scheduler cosine配合--warmup_epochs 5避免早期loss震荡遥感影像噪声导致初始梯度不稳定--weight_decay 1e-4防止ASPP模块中大卷积核过拟合监控指标必须包含Boundary F1在val阶段额外计算label边缘1像素内的F1-scoreGF2任务中该指标比mIoU更能反映工程可用性。4. 预测与后处理如何让predict.png真正可用而非仅满足可视化4.1 滑动窗口预测与重叠区域融合单张GF2原始影像尺寸通常为8000×8000远超GPU显存承载能力。项目采用滑动窗口重叠融合策略def sliding_window_predict(model, img_tensor: torch.Tensor, window_size512, overlap128, devicecuda): img_tensor: (4, H, W) 归一化后的4波段张量 返回: (C, H, W) 概率图 model.eval() h, w img_tensor.shape[1:] prob_map torch.zeros((8, h, w), devicedevice) # 8类概率图 count_map torch.zeros((h, w), devicedevice) # 每像素被预测次数 # 生成滑动窗口坐标 for i in range(0, h - window_size 1, overlap): for j in range(0, w - window_size 1, overlap): window img_tensor[:, i:iwindow_size, j:jwindow_size] window window.unsqueeze(0).to(device) # (1,4,512,512) with torch.no_grad(): pred_prob torch.softmax(model(window), dim1)[0] # (8,512,512) # 融合到全局prob_map加权平均边缘区域权重衰减 weight generate_gaussian_weight(window_size, overlap) prob_map[:, i:iwindow_size, j:jwindow_size] \ pred_prob * weight.unsqueeze(0) count_map[i:iwindow_size, j:jwindow_size] weight # 归一化 prob_map prob_map / count_map.unsqueeze(0) return prob_map def generate_gaussian_weight(win_size, overlap): 生成高斯衰减权重中心区域权重1边缘线性衰减至0.5 x torch.arange(win_size) y torch.arange(win_size) xx, yy torch.meshgrid(x, y, indexingij) center win_size // 2 dist torch.sqrt((xx - center)**2 (yy - center)**2) weight torch.exp(-dist**2 / (2 * (overlap/2)**2)) weight torch.clamp(weight, min0.5, max1.0) # 限制最小权重 return weight参数说明overlap128保证相邻窗口重叠25%避免切块边界出现伪影generate_gaussian_weight比简单平均更鲁棒抑制窗口边缘因感受野截断导致的预测偏差必须在GPU上执行count_map和prob_map需全程驻留显存CPU-GPU频繁拷贝会导致预测速度下降5倍以上。4.2 后处理形态学优化与矢量化导出predict.png直接输出的是概率图argmax结果需经三步后处理才具备GIS系统导入条件步骤操作OpenCV函数参数说明1. 连通域过滤剔除小于50像素的噪声斑点cv2.connectedComponentsWithStatsmin_area50保留STAT_AREA≥50的连通域2. 边界平滑消除锯齿状边缘尤其道路/水体cv2.morphologyExcv2.GaussianBlurkernel_size5,sigma1.0先高斯模糊再二值化3. 矢量化导出生成GeoJSON需坐标系信息rasterio.features.shapes使用img_gt_pre.png中已知的像素-地理坐标映射关系注意项目未提供地理坐标转换模块因GF2_PMS2_*.csv中缺少RPC参数。实际工程中需联系数据提供商获取RPC文件或用gdal_translate -a_srs EPSG:4326手动指定WGS84坐标系。4.3blend.png与img_gt_pre.png的生成逻辑项目中的blend.png是原始影像与预测结果的叠加图img_gt_pre.png则并排显示Ground Truth与Prediction。生成代码需确保色彩映射严格一致def create_blend_visualization(img_path, pred_label, save_path): # 读取原始影像4波段→RGB假彩色RNIR, GR, BG img cv2.imread(img_path, cv2.IMREAD_UNCHANGED) rgb_img np.stack([img[:,:,3], img[:,:,2], img[:,:,1]], axis2) # NIR-R-G # 将pred_label转为彩色图复用classes_sample.jpg的RGB映射 color_map np.array([ [255, 0, 0], # 建筑 [255, 255, 0], # 道路 [0, 0, 255], # 水体 [0, 255, 0], # 植被 [255, 165, 0], # 裸土 [128, 0, 128], # 农田 [0, 255, 255], # 湿地 [128, 128, 128] # 其他 ], dtypenp.uint8) color_pred color_map[pred_label] # (H,W,3) # 叠加原始影像透明度0.6 预测掩膜透明度0.4 blend cv2.addWeighted(rgb_img, 0.6, color_pred, 0.4, 0) cv2.imwrite(save_path, blend) def create_comparison_plot(gt_label, pred_label, save_path): 生成GT与Pred并排对比图 color_map ... # 同上 gt_color color_map[gt_label] pred_color color_map[pred_label] fig, axes plt.subplots(1, 2, figsize(12, 6)) axes[0].imshow(gt_color); axes[0].set_title(Ground Truth) axes[1].imshow(pred_color); axes[1].set_title(Prediction) plt.savefig(save_path, bbox_inchestight)避坑重点rgb_img构造必须用[NIR,R,G]而非[R,G,B]否则植被在假彩色中不显绿色color_map索引必须与classes_sample.jpg中实际RGB值完全一致任何1像素偏差都会导致整类地物着色错误。5. 避坑指南GF2语义分割中80%失败源于这5个隐蔽问题5.1 现象训练loss震荡剧烈10个epoch内从2.5跳到0.8又回到2.1原因GF2影像DN值范围0~1023未归一化导致NIR波段B4梯度爆炸反向传播时权重更新失稳。解决强制在data_preprocess.py中添加波段级min-max归一化禁止使用全局归一化img / 1023会压缩NIR动态范围。验证方法打印img[:, :, 3].std()归一化后应≈0.28非0.001或1.0。5.2 现象predict.png中所有像素均为类别0建筑其他类全为0原因rgb_to_class_id函数中classes_sample.jpg读取时被cv2.IMREAD_COLOR自动转为BGR导致RGB元组匹配失败如(255,0,0)读成(0,0,255)。解决改用cv2.IMREAD_UNCHANGED读取并用print(np.unique(label_rgb, axis0))校验前10个像素RGB值。血泪经验每次更换classes_sample.jpg必须重新校验。5.3 现象验证集mIoU稳定在65%但blend.png中道路边缘严重锯齿原因未启用Boundary-aware Loss且滑动窗口预测时未加高斯权重导致窗口拼接处边缘不连续。解决在损失函数中加入boundary_loss dice_loss(pred_boundary, gt_boundary)其中gt_boundary用cv2.Canny(gt_label, 100, 200)生成同时sliding_window_predict中必须启用generate_gaussian_weight。5.4 现象DataLoader报错OSError: Too many open files原因Linux系统默认ulimit -n为1024而GF2切片数量超2000num_workers0时子进程打开文件句柄超限。解决终端执行ulimit -n 65536并在Python中设置torch.multiprocessing.set_sharing_strategy(file_system)。玄学操作重启Python kernel后首次运行必成功第二次必失败——这是Linux文件句柄泄漏的典型表现。5.5 现象predict.png与img_gt_pre.png尺寸不一致无法并排比较原因原始影像GF2_*.png存在1-2像素的JPEG压缩伪影cv2.imread读取后尺寸微变如8000×8000读成7999×7999导致切块数不匹配。解决预处理前强制cv2.resize(img, (8000,8000))或用PIL.Image.open().convert(RGB)替代cv2.imreadPIL对JPEG伪影鲁棒性更强。后悔药在README.md中增加# 数据校验章节要求用户运行verify_dimensions.py脚本确认所有影像尺寸一致。6. 工程级验证技巧用img_gt_pre.png反推模型缺陷而非只看mIoU6.1 三步定位法从可视化图直击模型弱点img_gt_pre.png不是装饰品而是诊断报告。我习惯按此顺序排查聚焦错误高发区域用cv2.threshold二值化GT与Pred的差异图diff cv2.absdiff(gt, pred)观察错误像素是否集中在特定地物交界处如农田-道路、水体-裸土检查类别混淆矩阵对diff图按GT类别统计错误像素数若“裸土”被误判为“农田”占比超40%说明模型未学好土壤光谱特征回溯原始影像在GF2_*.png中定位错误区域查看该处NIR波段B4DN值是否异常如云阴影导致NIR值骤降确认是否为数据质量问题而非模型缺陷。6.2 动态阈值评估为什么固定0.5阈值会误判湿地遥感影像中湿地class6在NIR波段反射率极低其预测概率图常呈“弥散状低置信度”若用pred_prob.argmax(dim0)硬分类会将大量湿地像素判为邻近的水体class2。解决方案# 对湿地类单独设置动态阈值 wetland_prob pred_prob[6] # (H,W) wetland_mask (wetland_prob 0.3) (pred_prob[2] 0.2) # NIR低水体概率低 final_pred torch.where(wetland_mask, torch.full_like(final_pred, 6), final_pred)参数依据在val集上统计湿地像素的pred_prob[6].mean()≈0.28故阈值设为0.3同时要求水体概率0.2排除混叠。6.3 长尾类别增强农田class5的过采样策略classes_sample.jpg显示农田占比约25%但训练时仍易被忽略。我采用在线过采样而非离线复制class BalancedSampler(Sampler): def __init__(self, dataset, class_weights): self.dataset dataset self.class_weights class_weights # [1.0,1.0,1.0,1.0,1.0,1.5,1.0,1.0] self.weights [] for idx in range(len(dataset)): _, label dataset[idx] cls_id label.flatten().mode()[0].item() self.weights.append(self.class_weights[cls_id]) def __iter__(self): return iter(torch.multinomial(torch.tensor(self.weights), len(self.dataset), replacementTrue))关键点class_weights[5]1.5农田而非2.0因过度增强会导致模型在农田内部产生虚假边界——这是我在3个GF2项目中验证过的平衡点。6.4 部署前必做的三件事显存压力测试用nvidia-smi -l 1监控sliding_window_predict全程显存占用确保峰值≤90%显存容量跨设备验证在Jetson AGX Orin上用TensorRT推理predict.png检查blend.png色彩是否偏移NVIDIA驱动版本差异导致YUV转RGB异常坐标系校验将img_gt_pre.png中某栋建筑角点坐标在QGIS中叠加GF2原始影像确认像素偏移≤2像素否则需重做地理配准。从那以后我每次交付遥感分割模型都强制走一遍这三步——哪怕客户只要一张predict.png。因为真正的工程价值不在mIoU数字而在blend.png里那条光滑的道路边缘是否能直接导入GIS系统做面积统计。希望帮到你。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表