
簡介一份基于PyTorch框架與卷積神經網絡實現遙感圖像滑坡識別的完整工程面向深度學習初學者、遙感方向學生及需要完成課程設計或期末大作業的開發者。項目覆蓋數據劃分、模型訓練與驗證全流程內含AlexNet、ResNet等經典網絡結構及數據劃分腳本并附有已訓練好的模型權重用戶既可自行訓練也可直接加載權重進行推理驗證。壓縮包共15個文件包括7個Python源碼、1個pth模型文件、配置文件與說明文檔等整體約52.73MB目錄結構清晰方便按需查閱。目前已有70人學習使用適用于遙感災害監測、地質預警等教學與科研場景。資源源自網絡分享僅供學習交流請勿用于商業用途。對于希望將CNN應用于遙感圖像分析的讀者這套代碼從數據準備到模型應用提供了完整參照能加快項目落地與學習進度。1. 遙感圖像滑坡識別一個典型的像素級分割任務滑坡識別在遙感影像處理里本質上不是目標檢測那樣的“框出滑坡”而是逐像素判斷這個像素屬于滑坡體還是背景。災后應急、地質災害調查、國土空間監測都要先把“滑坡到底圈在哪”這件事做準。傳統做法用光譜指數、紋理閾值在裸土和植被混合的區域很容易誤判泛化能力也差。深度學習CNN把特征提取和分類做成端到端卷積層自動學習滑坡的紋理、邊界和上下文精度和魯棒性都上了一個臺階。PyTorch這邊生態成熟從數據集封裝、預訓練主干到分布式訓練都有現成組件很適合在這個任務上搭起完整流程。下面是完整落地路徑主線就是把這套源碼、數據集和模型拆開講透。2. CNN從特征提取到U型分割滑坡識別模型的網絡設計2.1 滑坡體在遙感影像上的特征與CNN結構圖的對應關系滑坡體的影像特征和常規分割對象不太一樣。首先滑坡體形狀極不規則沒有建筑物那種銳利直線邊界其次滑坡區域的紋理是破碎的裸土、碎石、植被殘骸混在一起顏色上又和周圍裸地接近只用RGB閾值幾乎不可能分離。好在CNN結構圖天然匹配這個需求淺層卷積核捕捉邊緣和紋理中層組合出斑塊狀Pattern深層通過擴大感受野感知滑坡體與山體、溝谷的位置關系最終在空間維度上輸出逐像素分類結果。輸入(3, H, W) - Conv3x3ReLU - MaxPool - Conv3x3ReLU - MaxPool - Conv3x3ReLU - 上采樣 - 逐像素分類這個基礎結構說明一個關鍵點如果網絡只做全局分類最后特征圖被壓成一維向量位置信息就丟了。滑坡識別要求輸出和輸入同分辨率的掩膜所以網絡必須保留空間維度這就引出了分割網絡最常用的U型結構。很多開源的滑坡識別源碼都采用U型設計原因就在這里——它不只是一個CNN分類器而是編碼器加解碼器的組合。2.2 為什么U-Net比普通CNN分類網絡更適合滑坡提取滑坡邊界的精細程度直接影響災害評估中面積統計的可靠性。純下采樣卷積網絡恢復不到原始分辨率直接造成小滑坡漏檢和邊界膨脹。U型結構通過跳躍連接把編碼器各層的高分辨率特征拼到解碼器對應層讓邊界信息不經過層層下采樣就被保留下來。對滑坡這種“邊緣模糊”的目標這個機制非常有效。下表是滑坡識別中備選網絡的對比選型時候可以直接參考。網絡結構跳躍連接參數量滑坡邊界恢復能力適用場景FCN-8s無僅上采樣中一般邊界偏粗快速初篩U-Net同層拼接中好邊界細節恢復默認首選DeepLabV3ASPP模塊替代中大較好感受野大大尺度滑坡體我一般第一版直接用U-Net訓練穩定、顯存壓力小、改造成本低。如果影像分辨率特別高比如0.5米無人飛機影像再換DeepLabV3這類帶空洞卷積的結構。2.3 編碼器加預訓練主干的有效性滑坡數據集往往只有幾百到幾千張裁剪影像從頭訓練一個深層CNN很容易過擬合。常見做法是編碼器部分換成ImageNet預訓練的ResNet34或VGG16解碼器保持U-Net結構。預訓練權重已經學到大量紋理和邊緣特征滑坡體雖然和常規物體不同但底層濾波器是通用的。訓練時默認全量微調不凍結主干因為遙感影像的分布和ImageNet差異還是存在只訓練解碼器效果通常不夠好。segmentation_models_pytorch庫里封裝了這些組合一行代碼就能加載預訓練U-Net。如果不想引入第三方庫手寫U-Net也很簡單下面是一個最小實現片段import torch.nn as nn class DoubleConv(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue), nn.Conv2d(out_ch, out_ch, 3, padding1), nn.BatchNorm2d(out_ch), nn.ReLU(inplaceTrue) ) def forward(self, x): return self.conv(x) class UNet(nn.Module): def __init__(self, in_channels3, num_classes1): super().__init__() self.enc1 DoubleConv(in_channels, 64) self.pool nn.MaxPool2d(2) self.enc2 DoubleConv(64, 128) self.enc3 DoubleConv(128, 256) self.enc4 DoubleConv(256, 512) self.enc5 DoubleConv(512, 1024) self.up5 nn.ConvTranspose2d(1024, 512, 2, stride2) self.dec5 DoubleConv(1024, 512) self.up4 nn.ConvTranspose2d(512, 256, 2, stride2) self.dec4 DoubleConv(512, 256) self.up3 nn.ConvTranspose2d(256, 128, 2, stride2) self.dec3 DoubleConv(256, 128) self.up2 nn.ConvTranspose2d(128, 64, 2, stride2) self.dec2 DoubleConv(128, 64) self.out nn.Conv2d(64, num_classes, 1) def forward(self, x): e1 self.enc1(x) e2 self.enc2(self.pool(e1)) e3 self.enc3(self.pool(e2)) e4 self.enc4(self.pool(e3)) e5 self.enc5(self.pool(e4)) d5 self.dec5(torch.cat([self.up5(e5), e4], dim1)) d4 self.dec4(torch.cat([self.up4(d5), e3], dim1)) d3 self.dec3(torch.cat([self.up3(d4), e2], dim1)) d2 self.dec2(torch.cat([self.up2(d3), e1], dim1)) return self.out(d2)DoubleConv是U-Net的基本模塊做兩次卷積加歸一化編碼器逐步壓縮空間尺寸同時增加通道數解碼器通過ConvTranspose2d上采樣并拼接編碼器對應層。最后的1x1卷積把通道壓縮成1輸出每個像素屬于滑坡的概率。注意這里沒有加Sigmoid訓練時交給損失函數處理。3. 滑坡遙感數據集裁剪、標注與PyTorch數據管線3.1 遙感影像數據的來源與標注格式滑坡識別源碼能順利跑起來數據集組織是關鍵。常見數據源包括高分一號/二號國產衛星影像、哨兵2號多光譜影像、無人機航拍DOM。前兩者是GeoTIFF格式單景影像動輒上億像素沒法直接丟進GPU。標注方面野外調查采集的多邊形shp文件需要柵格化成掩膜或者直接用GIS軟件導出PNG格式的標簽圖。數據目錄建議按下面的結構組織源碼里也好寫路徑目錄內容images/裁剪后的原始影像PNG或JPGmasks/與影像同名的標簽掩膜0為背景255為滑坡train.txt訓練集文件名列表每行一個文件名val.txt驗證集文件名列表掩膜中滑坡區域通常用255標注白色背景為0黑色。轉成Tensor時除以255讓標簽落在0和1。3.2 大影像滑窗裁剪與重疊采樣原始遙感影像太大必須做滑窗裁剪。窗口大小需要同時兼顧顯存和上下文信息512×512比較平衡太小了滑坡體的上下文不足模型容易把孤立的裸土誤判為滑坡太大了顯存占用高batch size上不去。裁剪時加一些重疊避免滑坡體恰好被窗口邊緣切斷。下面是裁剪腳本的核心邏輯import numpy as np from PIL import Image import os def crop_image_with_mask(image_path, mask_path, save_dir, size512, overlap128): img np.array(Image.open(image_path)) mask np.array(Image.open(mask_path)) h, w img.shape[:2] stride size - overlap for y in range(0, h, stride): for x in range(0, w, stride): y_end min(y size, h) x_end min(x size, w) patch_img img[max(0, y_end-size):y_end, max(0, x_end-size):x_end] patch_mask mask[max(0, y_end-size):y_end, max(0, x_end-size):x_end] if patch_img.shape[0] ! size or patch_img.shape[1] ! size: pad_img np.zeros((size, size, 3), dtypenp.uint8) pad_mask np.zeros((size, size), dtypenp.uint8) pad_img[:patch_img.shape[0], :patch_img.shape[1]] patch_img pad_mask[:patch_mask.shape[0], :patch_mask.shape[1]] patch_mask patch_img, patch_mask pad_img, pad_mask Image.fromarray(patch_img).save( os.path.join(save_dir, images, f{os.path.basename(image_path)[:-4]}_{y}_{x}.png)) Image.fromarray(patch_mask).save( os.path.join(save_dir, masks, f{os.path.basename(image_path)[:-4]}_{y}_{x}.png))代碼里overlap128意味著步長stride384相鄰窗口有128像素重疊。邊緣不足size的部分用零填充不影響訓練分布。需要保證影像和掩膜的地理坐標系一致最好用GDAL讀取而不是PIL避免坐標錯位。3.3 歸一化、數據增強與Dataset類實現遙感影像的像素值范圍和自然圖像不同哨兵2號多光譜數據可能是16位整型直接除以255會得到錯誤結果。源碼里必須根據數據特性確定歸一化方式8位影像除以255是常規操作16位影像先按百分位截斷再歸一化到0-1例如取2%到98%分位數做線性拉伸。滑坡識別的數據增強要克制過強的色彩擾動會讓模型學到錯誤的顏色關聯。import torch from torch.utils.data import Dataset from PIL import Image import numpy as np import torchvision.transforms as T class LandslideDataset(Dataset): def __init__(self, image_dir, mask_dir, file_list, use_augFalse): self.image_paths [] self.mask_paths [] with open(file_list, r) as f: for line in f: name line.strip() self.image_paths.append(f{image_dir}/{name}.png) self.mask_paths.append(f{mask_dir}/{name}.png) self.use_aug use_aug self.base_transform T.Compose([ T.ToTensor(), T.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) def __len__(self): return len(self.image_paths) def __getitem__(self, idx): image Image.open(self.image_paths[idx]).convert(RGB) mask Image.open(self.mask_paths[idx]) if self.use_aug: p np.random.rand() if p 0.5: image T.functional.hflip(image) mask T.functional.hflip(mask) p np.random.rand() if p 0.5: image T.functional.vflip(image) mask T.functional.vflip(mask) image self.base_transform(image) mask np.array(mask, dtypenp.float32) / 255.0 mask torch.from_numpy(mask).unsqueeze(0) return image, mask這里用了ImageNet的均值和標準差做標準化。但需要注意數據增強用的是鏡像翻轉而不是RandomResizedCrop因為滑坡體形狀和上下文經隨機裁剪改變過大反而不利于學習。開源代碼里有人用隨機亮度飽和度擾動這個按需使用我建議滑坡識別只做幾何增強顏色擾動幅度要小。4. PyTorch訓練滑坡識別模型損失函數、epoch與評估4.1 環境準備與PyTorch基礎框架搭建訓練之前先把PyTorch環境裝好。常見做法是用Anaconda建獨立環境命令如下conda create -n landslide python3.9 conda activate landslide pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118--index-url部分指定CUDA 11.8版本具體根據機器驅動版本調整。沒有NVIDIA GPU的機器直接pip install torch torchvision裝CPU版。PyTorch基礎框架的核心就是Dataset、DataLoader、nn.Module、optimizer和loss這五個組件串起來下面訓練腳本也是按這個順序組織。4.2 損失函數選擇BCE與Dice的組合滑坡體在影像中占比通常只有幾個百分點正負樣本極度不平衡。如果直接用二元交叉熵模型會學會把所有像素預測為背景因為這樣損失已經足夠低。業界通用的解法是BCE加Dice LossDice系數衡量預測掩膜和真實掩膜的重疊程度對類別不平衡不敏感。import torch.nn.functional as F def bce_dice_loss(pred, target): bce F.binary_cross_entropy_with_logits(pred, target) pred_prob torch.sigmoid(pred) smooth 1.0 intersection (pred_prob * target).sum() dice 1.0 - (2.0 * intersection smooth) / (pred_prob.sum() target.sum() smooth) return bce dice代碼中pred是網絡最后一層的原始輸出logitstarget是0和1的標簽張量。Dice部分不需要對target做sigmoid因為它已經是0-1。smooth防止分子分母都是零時出現除零錯誤。兩個損失相加BCE提供像素級梯度Dice提供區域級梯度兩者互補在滑坡分割上是穩定有效的組合。4.3 優化器、學習率與深度學習epoch的配合滑坡識別數據集規模通常不大訓練過程對超參更敏感。優化器用AdamW初始學習率1e-4權重衰減1e-4。batch size根據顯存調整512×512的輸入在12GB顯存上batch size設為4比較穩妥。深度學習中epoch不是拍腦袋定的滑坡識別一般100到200個epoch就夠關鍵在于配合驗證集上的Early Stopping驗證IoU連續15個epoch不提升就停止。epoch lr train_loss val_iou 1 1e-4 0.482 0.312 50 5e-5 0.213 0.651 100 1e-5 0.148 0.703 120 1e-5 0.139 0.711學習率用余弦退火或者階梯下降都行。我一般前50個epoch用固定1e-4之后每個epoch乘以0.98逐步衰減。滑坡邊界這種細粒度特征在訓練后期才逐漸收斂學習率降太快會導致邊界學不到位。4.4 訓練循環骨架與模型保存訓練腳本里最關鍵的是驗證邏輯每個epoch結束在驗證集上算IoU只保存IoU最高一次的權重。這樣即使后續過擬合模型文件也是歷史最佳狀態。def train_one_epoch(model, loader, optimizer, device): model.train() total_loss 0.0 for images, masks in loader: images images.to(device) masks masks.to(device) preds model(images) loss bce_dice_loss(preds, masks) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() * images.size(0) return total_loss / len(loader.dataset) torch.no_grad() def validate(model, loader, device): model.eval() total_iou 0.0 for images, masks in loader: images images.to(device) masks masks.to(device) preds torch.sigmoid(model(images)) 0.5 intersection (preds masks.bool()).sum(dim(1, 2, 3)) union (preds | masks.bool()).sum(dim(1, 2, 3)) iou (intersection.float() / (union.float() 1e-6)).mean() total_iou iou.item() * images.size(0) return total_iou / len(loader.dataset)torch.sigmoid(model(images)) 0.5是推理階段的二值化操作驗證的時候也要評估原始閾值下的性能確保訓練和評估是一致的。模型保存時推薦同時保存model.state_dict()和對應的epoch、val_iou方便回溯對比。5. 模型推理與成果導出重疊滑窗加權融合5.1 大影像推理的邊緣偽影問題訓練時輸入是512×512裁剪塊推理階段面對整景影像不能直接resize。直接resize會破壞滑坡體的尺度信息尤其是小滑坡縮小后可能只有十幾個像素。標準做法是推理時也做滑窗但窗口之間通常有重疊重疊區域的預測結果如果直接取平均值邊緣處會出現明顯的接縫偽影。5.2 Hann窗加權融合消除拼接痕跡重疊區域需要做加權平均權重窗口的中心高、邊緣低這樣拼接起來過渡平滑。Hann窗是常用選擇import numpy as np import torch def hann_2d(size): hann_1d np.hanning(size) hann_2d np.outer(hann_1d, hann_1d) return torch.from_numpy(hann_2d).float() def sliding_predict(model, image, window_size512, overlap128, devicecuda): model.eval() c, h, w image.shape stride window_size - overlap prob_map torch.zeros((1, h, w), devicedevice) weight_map torch.zeros((1, h, w), devicedevice) win hann_2d(window_size).unsqueeze(0).unsqueeze(0).to(device) for y in range(0, h, stride): for x in range(0, w, stride): sub image[:, y:ywindow_size, x:xwindow_size] if sub.shape[1] window_size or sub.shape[2] window_size: pad torch.zeros((c, window_size, window_size), devicedevice) pad[:, :sub.shape[1], :sub.shape[2]] sub sub pad sub sub.unsqueeze(0) with torch.no_grad(): prob torch.sigmoid(model(sub)) prob_map[:, y:ywindow_size, x:xwindow_size] prob * win weight_map[:, y:ywindow_size, x:xwindow_size] win final_prob prob_map / (weight_map 1e-8) return final_prob代碼里win就是Hann窗權重乘到每個窗口的預測結果上再累加最后除以權重和。1e-8防止圖像邊緣沒有窗口覆蓋的位置除零。stridewindow_size-overlap控制重疊度重疊越大拼接越平滑但推理時間線性增長。一般overlap取128到256之間具體看滑坡體的尺度滑坡體大的影像建議overlap取256。5.3 閾值選擇與后處理融合后的概率圖不是直接就是最終結果默認0.5閾值只適合概率分布居中時的場景。滑坡區域紋理破碎模型輸出往往在邊界處概率偏低統一用0.5會在溝壑、陰影處產生漏檢。可以觀察驗證集上不同閾值對應的IoU曲線最佳閾值一般落在0.35到0.5之間。選好閾值之后做一次形態學開運算去除孤立的誤檢小斑塊import cv2 final_mask np.ones((h, w), dtypenp.uint8) final_mask[prob_map.cpu().squeeze() 0.45] 1 final_mask[prob_map.cpu().squeeze() 0.45] 0 kernel np.ones((3, 3), np.uint8) final_mask cv2.morphologyEx(final_mask.astype(np.uint8), cv2.MORPH_OPEN, kernel)后處理開運算是用3×3結構元素對二值掩膜做腐蝕再膨脹孤立的小噪點面積小于結構元素會被直接消除。注意開運算也會抹掉很小的真實滑坡體運行前先觀察數據的滑坡體最小尺度如果小滑坡和孤立的誤檢大小接近就不要做開運算。5.4 可視化驗證技巧跑完推理別急著導出GeoTIFF先隨機抽幾塊區域把原圖、標注、預測結果并排拼接在一起看細節。重點看三類位置滑坡邊界是否緊貼真實邊緣、陰影區域是否誤報、裸土區域是否和滑坡體混淆。如果邊界系統性膨脹說明損失函數里BCE權重占比太高如果裸土大面積誤報說明數據里負樣本多樣性不夠需要補充非滑坡區域的裸地影像。可視化確認沒問題之后再通過GDAL把掩膜寫入原始GeoTIFF的地理坐標導出可供制圖和統計的成果文件這樣滑坡面積、周長這些指標也就能自動計算出來了。本文還有配套的精品資源點擊獲取