:從數(shù)據(jù)標(biāo)注到U-Net訓(xùn)練全流程)
簡介基于Python的遙感圖像道路提取算法實現(xiàn)與高分課程設(shè)計源碼是一套面向遙感影像道路識別任務(wù)的完整課程設(shè)計項目。項目采用模塊化架構(gòu)覆蓋Core、Cluster、DetectionStrategy、RoadModel等核心目錄囊括灰度共生矩陣、特征提取、K均值聚類、道路檢測策略等關(guān)鍵算法并配有可視化操作界面適合高校學(xué)生作為畢業(yè)設(shè)計、課程綜合實踐或期末大作業(yè)的參考范本。壓縮包共64個文件以35個py源碼為骨干輔以zbak工程備份、pyc與pyd編譯產(chǎn)物、png測試圖片及txt說明文檔整體僅3.97MB目錄清晰便于按需查閱。目前已有30人學(xué)習(xí)瀏覽代碼經(jīng)過系統(tǒng)化測試與調(diào)試可在典型環(huán)境中穩(wěn)定運行。讀者既能獲得完整算法源碼、工程結(jié)構(gòu)、界面實現(xiàn)與測試樣例也能沿循道路提取全流程進行理解與二次開發(fā)對課程設(shè)計與算法學(xué)習(xí)均有實用價值。1. 遙感圖像道路提取一門課程設(shè)計為什么有人拿高分有人勉強及格第一次做基于Python的遙感圖像道路提取課程設(shè)計的人很容易把精力全花在“找一個能跑的U-Net源碼”上。等真正拿到數(shù)據(jù)才發(fā)現(xiàn)公開數(shù)據(jù)集的圖像和標(biāo)簽尺寸對不上訓(xùn)練時損失卡在0.69不下降預(yù)測出來的道路全是碎塊?;赑ython的遙感圖像道路提取算法實現(xiàn)與高分課程設(shè)計源碼要解決的問題不是單點模型替換而是從遙感圖像標(biāo)注、數(shù)據(jù)切塊、算法選型、訓(xùn)練調(diào)參到結(jié)果評估的一條完整鏈路。它適合地信、遙感或計算機視覺方向要交課程設(shè)計的學(xué)生也適合想用一個中等規(guī)模工程入門語義分割的開發(fā)者。能跑通只是及格把每一步為什么這么做講清楚才是高分。2. 先把數(shù)據(jù)做對遙感圖像標(biāo)注、公開數(shù)據(jù)集與標(biāo)簽對齊的幾個坑道路提取本質(zhì)上是一個二分類語義分割任務(wù)背景是0道路是1。模型只負責(zé)從像素上學(xué)規(guī)律數(shù)據(jù)質(zhì)量直接決定模型上限。很多課程設(shè)計翻車不是模型寫錯而是訓(xùn)練集和標(biāo)簽根本對不齊。所以拿到任何源碼第一件事不是跑訓(xùn)練而是先檢查數(shù)據(jù)。2.1 公開數(shù)據(jù)集怎么選Massachusetts Roads、DeepGlobe與自建標(biāo)注樣本的取舍課程設(shè)計里最常見的數(shù)據(jù)集是Massachusetts Roads美國麻省地區(qū)的道路影像每張圖大概1500x1500像素分辨率在1米左右道路在圖上表現(xiàn)為清晰的淺色條帶。優(yōu)點是下載方便、標(biāo)簽規(guī)范、道路與背景對比度高適合第一版跑通。缺點是道路經(jīng)常被樹冠遮擋陰影干擾嚴重而且美國路網(wǎng)形態(tài)和國內(nèi)差異較大答辯時容易被問“你的模型在國內(nèi)數(shù)據(jù)上會不會失效”。DeepGlobe Road Extraction來自衛(wèi)星影像比賽包含城市、鄉(xiāng)村、沙漠等多種場景樣本量大道路標(biāo)簽比麻省數(shù)據(jù)集更貼近真實航拍。中文課程設(shè)計更推薦在國內(nèi)數(shù)據(jù)上做驗證常見做法是找CHN6-CUG這類中國典型城市道路數(shù)據(jù)集或者自己從公開影像源截取一片校園周邊區(qū)域標(biāo)注三五十張512x512的小樣本。自己標(biāo)注的好處是場景可控答辯時能講清楚數(shù)據(jù)來源和處理細節(jié)導(dǎo)師會明顯更認可。我的建議是時間緊就先用Massachusetts Roads跑通全流程再花兩三天自標(biāo)一小塊作為補充驗證。選數(shù)據(jù)集的評分邏輯是答辯老師更關(guān)心你是否意識到域差異而不是數(shù)據(jù)集本身多大多全。2.2 遙感圖像標(biāo)注與柵格化LabelMe畫完線之后離標(biāo)簽還差一步遙感圖像標(biāo)注并不等于在圖上畫線。用LabelMe或QGIS沿著道路中心線描出來的多邊形導(dǎo)出的GeoJSON文件不能直接喂給網(wǎng)絡(luò)必須先把矢量轉(zhuǎn)成一張與影像同尺寸、同地理參考的單波段掩碼圖道路像素為1背景為0。這一步在很多課程設(shè)計源碼里是缺失的因為網(wǎng)上能下載的數(shù)據(jù)集大多已經(jīng)幫你做好了掩碼但一旦需要自標(biāo)數(shù)據(jù)柵格化就是繞不開的門檻。import rasterio from rasterio.features import rasterize import fiona # 讀取影像拿到高度、寬度和地理變換信息 with rasterio.open(image.tif) as src: transform src.transform out_shape (src.height, src.width) # 從 GeoJSON 讀入道路多邊形統(tǒng)一賦值為1 shapes [] with fiona.open(roads.geojson, r) as src: for feat in src: shapes.append((feat[geometry], 1)) # 柵格化背景0道路1 mask rasterize( shapes, out_shapeout_shape, transformtransform, fill0, all_touchedTrue, dtypeuint8, ) with rasterio.open( label.tif, w, driverGTiff, heightout_shape[0], widthout_shape[1], count1, dtypeuint8, transformtransform, ) as dst: dst.write(mask, 1)核心邏輯是把矢量坐標(biāo)從地理坐標(biāo)系映射到像素坐標(biāo)系。rasterio的rasterize函數(shù)接收影像的transform參數(shù)確保生成的掩碼和原始影像逐像素對應(yīng)。參數(shù)里最容易被忽略的是all_touchedTrue它的意思是只要多邊形碰到某個像素就把該像素置為1否則細道路在柵格化后會直接斷成虛線。fill0指定背景值dtype用uint8而不是float能明顯減少內(nèi)存占用。柵格化完成后強烈建議把影像和掩碼疊加輸出一張預(yù)覽圖人工確認道路沒有偏移、沒有斷裂。這段代碼生成的label.tif就是后面訓(xùn)練要用的真值文件也是最容易出問題的一環(huán)。2.3 標(biāo)簽對齊的三個坑錯位、插值污染與“道路只有一條線”第一個坑是錯位。訓(xùn)練時損失正常下降但預(yù)測結(jié)果可視化之后發(fā)現(xiàn)道路整體偏移了幾個像素形狀對但位置不對。原因大多是圖像和標(biāo)簽的裁剪窗口沒有使用同一套坐標(biāo)或者標(biāo)注數(shù)據(jù)和影像本來就不是同一時期的存在配準(zhǔn)誤差。解決方法是訓(xùn)練前固定一個裁剪腳本對圖像和標(biāo)簽用相同的左上角坐標(biāo)窗口切片每次切完都輸出疊加圖做人工確認。第二個坑是插值污染。用OpenCV或PIL對標(biāo)簽做resize時如果用了默認的雙線性插值掩碼里會出現(xiàn)0.5、0.7這類中間值模型看到的是灰度圖而不是二值圖。原因是標(biāo)簽是離散類別不能用連續(xù)插值。解決方法是標(biāo)簽縮放必須用最近鄰插值即cv2.INTER_NEAREST或PIL的Image.NEAREST并且resize之后再執(zhí)行一次二值化掩碼里只允許出現(xiàn)0和1。第三個坑是道路細到只剩一條線。部分數(shù)據(jù)集里道路寬度只有1到2個像素直接訓(xùn)練時正樣本占比可能低于1%模型很快就學(xué)會全輸出背景。常見做法是在訓(xùn)練前對標(biāo)簽做一次形態(tài)學(xué)膨脹用3x3或5x5的矩形核把道路擴寬讓網(wǎng)絡(luò)先學(xué)到道路的連通結(jié)構(gòu)。但評估時一定要換回原始標(biāo)簽否則IoU虛高答辯時經(jīng)不起追問。數(shù)據(jù)這一關(guān)寧可多花一小時做可視化檢查也不要在訓(xùn)練跑了兩小時之后才返工。把圖像、標(biāo)簽、疊加圖三個結(jié)果輸出到同一張畫布上看三秒勝過調(diào)十次參數(shù)。3. 道路提取算法選型從形態(tài)學(xué)傳統(tǒng)法到U-Net課程設(shè)計分數(shù)上限由對比決定課程設(shè)計的題目叫“道路提取算法實現(xiàn)”關(guān)鍵字在“算法”不在“調(diào)包”。如果只從開源倉庫復(fù)制一個U-Net訓(xùn)練完就交大概率只能拿中等分。高分報告里一定有一條清晰的選型邏輯先嘗試傳統(tǒng)圖像處理方法說明它在哪里失敗再引入深度學(xué)習(xí)方法用實驗數(shù)據(jù)證明改進效果。3.1 傳統(tǒng)道路提取Canny邊緣加形態(tài)學(xué)閉運算為什么復(fù)雜場景翻車傳統(tǒng)道路提取的經(jīng)典流程是灰度化、高斯濾波、Canny邊緣檢測、形態(tài)學(xué)閉運算、連通域過濾。用OpenCV實現(xiàn)也就二十行適合作為第一個baseline版本。import cv2 import numpy as np img cv2.cvtColor(cv2.imread(image.tif), cv2.COLOR_BGR2GRAY) # 高斯濾波去噪核大小5x5 blur cv2.GaussianBlur(img, (5, 5), 0) # Canny邊緣檢測低閾值50高閾值150 edges cv2.Canny(blur, 50, 150) # 閉運算把道路邊緣斷線連起來核大小15x15 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (15, 15)) closed cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel) # 按面積過濾去掉孤立小碎塊 num_labels, labels, stats, _ cv2.connectedComponentsWithStats(closed) for i in range(1, num_labels): if stats[i, cv2.CC_STAT_AREA] 200: closed[labels i] 0 cv2.imwrite(traditional_result.png, closed)這段代碼的邏輯是先用邊緣檢測找到圖像里的線條再用閉運算把斷裂的線段連接成道路面最后用連通域面積過濾掉噪聲。高斯濾波核控制去噪強度太大會把細道路抹平Canny閾值的低閾值和高閾值決定了邊緣敏感度道路和背景對比明顯的區(qū)域用默認值問題不大閉運算的15x15核是典型經(jīng)驗值但遇到鄉(xiāng)村泥路上會有大量誤檢。傳統(tǒng)方法在兩類場景上必然翻車一是城市區(qū)域樓頂邊緣、停車場矩形、規(guī)則地塊都會被當(dāng)成道路二是樹蔭遮擋區(qū)域道路被陰影切斷后閉運算無法恢復(fù)連通性。答辯時把這些失敗案例打印出來直接引出“傳統(tǒng)手工特征無法建模道路的上下文語義”過渡到深度學(xué)習(xí)就非常自然。3.2 深度學(xué)習(xí)選型邏輯為什么是U-Net而不是SegNet或DeepLabV3語義分割模型很多但道路提取課程設(shè)計里最常用的是U-Net。原因是道路是細長條狀目標(biāo)需要低層高分辨率特征保持邊緣細節(jié)又需要高層語義特征判斷道路連通性U-Net的跳連接正好把這兩類特征拼在一起。SegNet用池化索引上采樣對邊界細節(jié)保持不錯但中間層的信息損失沒法通過跳連接補回來DeepLabV3的ASPP模塊擅長捕捉多尺度上下文但對只有幾個像素寬的道路不一定劃算而且實現(xiàn)復(fù)雜課程設(shè)計答辯時結(jié)構(gòu)圖都不好畫。從工程量角度考慮U-Net結(jié)構(gòu)簡潔編碼器下采樣、解碼器上采樣、跳連接三個概念可以在一頁PPT內(nèi)講完。網(wǎng)絡(luò)參數(shù)也少普通學(xué)生電腦就能訓(xùn)練。算法路線空間細節(jié)保持連通性建模工程復(fù)雜度課程設(shè)計友好度Canny形態(tài)學(xué)中差低適合做baselineU-Net高較強中首選SegNet中高中中可替代DeepLabV3高強偏高有余力再試3.3 兩條路線都跑通才算完整的道路提取“算法實現(xiàn)”高分課程設(shè)計不是只要深度模型而是要求你實現(xiàn)并對比多種算法。最穩(wěn)妥的報告結(jié)構(gòu)是第三章寫傳統(tǒng)算法的設(shè)計與失效分析寫清楚為什么需要更好方法第四章寫基于U-Net的語義分割實現(xiàn)給出訓(xùn)練過程和評估結(jié)果第五章用同一套評估代碼計算兩種方法的IoU、F1和推理時間形成定量對比。傳統(tǒng)方法速度快但精度低深度方法精度高但依賴訓(xùn)練數(shù)據(jù)和顯存資源。把這些差異用數(shù)字擺出來答辯老師再問你“你的改進點在哪”時你就不需要現(xiàn)場編答案因為整個報告就是一個推理鏈。4. 用Python實操復(fù)現(xiàn)U-Net道路提取數(shù)據(jù)切塊、Dataset、訓(xùn)練與IoU完整代碼到這里才真正進入代碼環(huán)節(jié)。順序是先把大影像切成訓(xùn)練尺寸再寫Dataset類做數(shù)據(jù)讀取和同步增強然后定義精簡U-Net模型最后寫訓(xùn)練循環(huán)和評估函數(shù)。每一步都有明確的參數(shù)說明和坑。4.1 影像切塊與重疊裁剪大影像切成模型吃得下的尺寸遙感影像動不動就是幾千乘幾千像素直接進網(wǎng)絡(luò)顯存不夠所以必須切塊。為了避免道路在切塊邊界被切斷常見做法是讓相鄰窗口之間有重疊即步長小于窗口大小。下面是我常用的切圖函數(shù)。import rasterio from rasterio import windows from pathlib import Path def crop_tif(src_path, dst_dir, size256, stride128): Path(dst_dir).mkdir(parentsTrue, exist_okTrue) with rasterio.open(src_path) as src: h, w src.height, src.width idx 0 for y in range(0, h - size 1, stride): for x in range(0, w - size 1, stride): win windows.Window(x, y, size, size) data src.read(windowwin) profile src.profile.copy() profile.update( heightsize, widthsize, transformwindows.transform(win, src.transform), ) dst_path dst_dir / fcrop_{idx:05d}.tif with rasterio.open(dst_path, w, **profile) as dst: dst.write(data) idx 1 # 圖像和標(biāo)簽用同一套窗口參數(shù)切保證嚴格對齊 crop_tif(image.tif, train_images) crop_tif(label.tif, train_labels)循環(huán)里用窗口的左上角坐標(biāo)x和y依次滑動stride128而size256表示窗口重疊一半。重疊裁剪的意義是讓道路不會因為恰好落在邊界上而失去上下文。如果影像邊長不能整除stride最后一列和最后一行會被漏掉常見做法是提前用反射padding把影像補到stride的整數(shù)倍或修改循環(huán)邊界條件。實際參數(shù)上size256適合入門顯存夠用就切512道路連通性會明顯更好但訓(xùn)練時間增加不少。4.2 自定義Dataset與同步增強圖像和標(biāo)簽必須吃同一份隨機增強PyTorch訓(xùn)練時需要自己定義Dataset類。這里的核心問題是數(shù)據(jù)增強的同步性圖像做了水平翻轉(zhuǎn)標(biāo)簽必須做完全相同的水平翻轉(zhuǎn)否則模型學(xué)到的是錯位特征。import torch from torch.utils.data import Dataset import cv2 import numpy as np class RoadDataset(Dataset): def __init__(self, img_dir, mask_dir, augmentFalse): self.img_paths sorted(Path(img_dir).glob(*.tif)) self.mask_paths sorted(Path(mask_dir).glob(*.tif)) self.augment augment def __len__(self): return len(self.img_paths) def __getitem__(self, idx): img cv2.imread(str(self.img_paths[idx])) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) mask cv2.imread(str(self.mask_paths[idx]), cv2.IMREAD_GRAYSCALE) mask (mask 0).astype(np.float32) if self.augment: # 圖像和標(biāo)簽必須使用同一個隨機狀態(tài) if np.random.rand() 0.5: img img[:, ::-1, :] mask mask[:, ::-1] img torch.from_numpy( img.transpose(2, 0, 1)).float() / 255.0 mask torch.from_numpy(mask).unsqueeze(0).float() return img, mask注意OpenCV讀取通道順序是BGR而訓(xùn)練時習(xí)慣用RGB所以先轉(zhuǎn)換。mask讀取時用灰度模式直接做二值化。增強部分只寫了一個水平翻轉(zhuǎn)作為示例如果你想加亮度抖動可以在翻轉(zhuǎn)之后對img乘以一個隨機系數(shù)img img * (0.8 0.4 * np.random.rand())然后用clip把數(shù)值限定在0到1之間。垂直翻轉(zhuǎn)和隨機旋轉(zhuǎn)按同樣思路處理關(guān)鍵是先對圖像和標(biāo)簽生成同一個隨機數(shù)再決定要不要翻轉(zhuǎn)不能分別調(diào)用np.random.rand。4.3 精簡U-Net模型定義Encoder-Decoder與跳連接課程設(shè)計不需要把U-Net寫成一個幾百行的完整工廠函數(shù)精簡版本足夠跑出有意義的結(jié)果。下面的網(wǎng)絡(luò)有兩次下采樣、兩次上采樣基礎(chǔ)通道數(shù)32輸入256x256的RGB圖輸出單通道道路概率圖。import torch 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 SimpleUNet(nn.Module): def __init__(self, in_ch3, out_ch1, base32): super().__init__() self.enc1 DoubleConv(in_ch, base) self.pool1 nn.MaxPool2d(2) self.enc2 DoubleConv(base, base * 2) self.pool2 nn.MaxPool2d(2) self.bridge DoubleConv(base * 2, base * 4) self.up2 nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) self.dec2 DoubleConv(base * 4 base * 2, base * 2) self.up1 nn.Upsample(scale_factor2, modebilinear, align_cornersFalse) self.dec1 DoubleConv(base * 2 base, base) self.out nn.Conv2d(base, out_ch, 1) def forward(self, x): e1 self.enc1(x) e2 self.enc2(self.pool1(e1)) b self.bridge(self.pool2(e2)) d2 self.dec2(torch.cat([self.up2(b), e2], dim1)) d1 self.dec1(torch.cat([self.up1(d2), e1], dim1)) return self.out(d1)模型的核心是Upsample之后把上采樣特征與對應(yīng)下采樣層的特征做拼接dim1表示在通道維度上拼。跳連接把編碼器的邊緣紋理信息帶給解碼器這是U-Net在道路提取上優(yōu)于普通卷積自編碼器的原因。base32控制整體通道數(shù)顯存不足時優(yōu)先把base降到16效果損失不大想提升精度可以再加一層下采樣和上采樣但訓(xùn)練時間會成倍增加。4.4 訓(xùn)練循環(huán)與IoU評估交叉熵之外還有兩個關(guān)鍵指標(biāo)訓(xùn)練循環(huán)使用BCEWithLogitsLoss這個損失函數(shù)把sigmoid和交叉熵合并成一步數(shù)值上比手動分開更穩(wěn)定。學(xué)習(xí)率1e-3配上AdamW是入門穩(wěn)妥組合。model SimpleUNet(in_ch3, out_ch1, base32).cuda() optimizer torch.optim.AdamW(model.parameters(), lr1e-3) loss_fn nn.BCEWithLogitsLoss() for epoch in range(50): model.train() total_loss 0.0 for img, mask in train_loader: img, mask img.cuda(), mask.cuda() pred model(img).squeeze(1) # (B, H, W) loss loss_fn(pred, mask) optimizer.zero_grad() loss.backward() optimizer.step() total_loss loss.item() * img.size(0) avg_loss total_loss / len(train_loader.dataset) print(fepoch {epoch 1:02d}, loss {avg_loss:.4f})訓(xùn)練完做評估時不能用準(zhǔn)確率作為主要指標(biāo)因為道路只占圖像很少一部分全預(yù)測背景也能得到極高準(zhǔn)確率。課程設(shè)計里重點報告的是IoU和F1尤其是IoU。def compute_iou(pred_mask, true_mask, eps1e-6): pred_mask pred_mask 0.5 true_mask true_mask 0.5 inter (pred_mask true_mask).sum().float() union (pred_mask | true_mask).sum().float() return (inter eps) / (union eps)pred_mask先通過0.5閾值變成二值圖再和真值做逐像素交并比計算。eps防止某張全是背景的測試圖在分母為0時產(chǎn)生NaN。評估時建議把整個驗證集的IoU和每一張圖的IoU都打出來某張圖IoU特別低往往就是該樣本有樹蔭遮擋或道路過于狹窄這一類分析在報告里特別加分。5. 復(fù)現(xiàn)道路提取的5個高頻踩坑點標(biāo)簽錯位、DiceLoss震蕩與訓(xùn)練不收斂這些坑基本是每個做過道路提取課程設(shè)計的人都會遇到的按現(xiàn)象、原因、解決的順序?qū)懬宄?.1 訓(xùn)練集里幾乎全是黑圖損失函數(shù)卡住不動現(xiàn)象訓(xùn)練開始后損失函數(shù)很快就穩(wěn)定在一個數(shù)值附近再訓(xùn)練幾十個epoch也不下降預(yù)測結(jié)果全是一片黑。原因道路在影像中占比太低有的圖道路像素不到1%網(wǎng)絡(luò)學(xué)到“全部輸出為背景”的取巧解交叉熵依然很低。解決先統(tǒng)計訓(xùn)練集每張圖的道路占比把所有道路占比低于0.5%的樣本剔除或做增強然后對標(biāo)簽做形態(tài)學(xué)膨脹把道路從1像素擴展到3到5像素最后把損失函數(shù)換成帶smooth的DiceLoss讓模型必須關(guān)注少數(shù)類。5.2 道路標(biāo)簽像是“復(fù)印偏了”預(yù)測圖與標(biāo)簽整體錯位現(xiàn)象訓(xùn)練一切正常損失在降但可視化預(yù)測結(jié)果時發(fā)現(xiàn)道路位置整體偏移了幾個像素道路形狀是對的位置是錯的。原因圖像和標(biāo)簽裁剪窗口坐標(biāo)不一致或者讀取TIF時波段順序錯了。解決訓(xùn)練前寫一個對齊檢查腳本從圖像和標(biāo)簽的同一位置裁剪同一個窗口輸出疊加圖人工確認檢查兩個文件的width、height和transform是否一致不一致就用rasterio.warp重采樣。重采樣參數(shù)里插值方法用nearest而不是bilinear否則標(biāo)簽又會被污染。5.3 換DiceLoss后訓(xùn)練震蕩甚至loss變成NaN現(xiàn)象交叉熵正常換成DiceLoss之后損失忽高忽低偶爾直接變成NaN。原因DiceLoss的分母接近0時數(shù)值不穩(wěn)定或者sigmoid輸出經(jīng)過log計算溢出。解決在求Dice之前先對預(yù)測做sigmoid再用clamp限制數(shù)值范圍并加smooth系數(shù)。def dice_loss(pred, target, smooth1.0): pred torch.sigmoid(pred).clamp(1e-7, 1 - 1e-7) inter (pred * target).sum(dim(2, 3)) union pred.sum(dim(2, 3)) target.sum(dim(2, 3)) return 1 - ((2 * inter smooth) / (union smooth)).mean()smooth1.0是常見經(jīng)驗值它同時防止分子分母為0也緩解了稀疏道路標(biāo)簽帶來的梯度抖動。clamp的上下限確保sigmoid輸出不會變成真正的0或1。5.4 切塊預(yù)測拼接后到處是斷頭路現(xiàn)象把測試影像切成256x256逐塊預(yù)測然后拼接回完整圖像道路在切塊邊界處頻繁斷裂看起來像一條條斷頭路。原因切塊時道路被攔腰截斷模型只看到局部看不到連續(xù)走向。解決推理時改用重疊預(yù)測。以窗口512、步長256為例預(yù)測結(jié)果只取每個窗口中心256x256區(qū)域?qū)懭胱罱K輸出邊緣區(qū)域的預(yù)測結(jié)果直接丟棄。更平滑的做法是準(zhǔn)備一個與輸出同尺寸的累加器acc和計數(shù)器count每塊預(yù)測按像素位置累加最終輸出為acc/count重疊區(qū)域自然做平均。5.5 Python環(huán)境里的依賴坑GDAL裝不上換個庫讀圖現(xiàn)象從網(wǎng)上復(fù)制的源碼在依賴文件里寫死了gdalpip安裝gdal經(jīng)常失敗conda裝又和其他包沖突環(huán)境配置就能耗掉一下午。原因GDAL是C庫Python綁定必須與本地GDAL版本嚴格匹配不同Python版本之間的wheel兼容性也差。解決讀TIF影像不要直接碰GDAL改用rasterio或者tifffile這兩個庫的預(yù)編譯包在pip上可以直接裝。環(huán)境搭建建議按照基礎(chǔ)的python安裝教程先把Anaconda裝好創(chuàng)建獨立環(huán)境再在VSCode里把python解釋器指向這個conda環(huán)境用conda install rasterio而不是pip install能少踩很多編譯兼容的坑。6. 從“能跑”到“高分”消融實驗設(shè)計與答辯話術(shù)組織源碼能跑通只證明你完成了復(fù)制粘貼高分課程設(shè)計要求你能說清楚每一步改動帶來了什么。我的習(xí)慣是把所有嘗試做成一張實驗記錄表哪怕失敗的嘗試也記進去因為答辯老師最想聽的其實是“你遇到了什么問題怎么排查的”。消融實驗三組起步。第一組對比傳統(tǒng)Canny形態(tài)學(xué)與U-Net用于證明深度學(xué)習(xí)方法有效第二組對比原始標(biāo)簽訓(xùn)練與膨脹標(biāo)簽訓(xùn)練用于說明數(shù)據(jù)預(yù)處理對稀疏道路的必要性第三組對比交叉熵與交叉熵加DiceLoss用于說明損失函數(shù)對正負樣本不平衡的影響。實驗組數(shù)據(jù)增強損失函數(shù)道路IoU結(jié)論傳統(tǒng)Canny形態(tài)學(xué)無無較低復(fù)雜場景失敗U-Net CE水平翻轉(zhuǎn)BCE較高基線模型U-Net 膨脹標(biāo)簽水平翻轉(zhuǎn)BCE再提升數(shù)據(jù)預(yù)處理有效U-Net 膨脹標(biāo)簽翻轉(zhuǎn)亮度抖動BCEDice最優(yōu)增強和損失都有效這個表格里的數(shù)字留空由你跑完自己填。重點是每一行都要對應(yīng)一個可解釋的結(jié)論不能只貼結(jié)果不給分析。可視化部分把原圖、真值標(biāo)簽、預(yù)測結(jié)果、錯誤區(qū)域四列拼成一張對比大圖錯誤區(qū)域用紅色標(biāo)出報告老師一眼就能看到模型在哪里失敗。答辯時按四段組織先講數(shù)據(jù)來源和標(biāo)注形式強調(diào)你做了形態(tài)學(xué)膨脹和標(biāo)簽對齊檢查再講算法路線從傳統(tǒng)方法到U-Net的選型理由然后用消融實驗表說明每一步改進的定量變化最后挑一個最有價值的失敗嘗試展開比如DiceLoss震蕩的排查過程。這一套下來課程設(shè)計就不再是“跑了個模型”而是一次完整的算法實現(xiàn)與驗證閉環(huán)。我當(dāng)年交第一版時只有U-Net沒有傳統(tǒng)算法對比答辯被問“你的方法比傳統(tǒng)方法好多少”當(dāng)場卡住。從那以后我所有實驗都堅持做基線對比和消融記錄這個習(xí)慣也讓我在后來的論文復(fù)現(xiàn)中少走了很多彎路。希望這篇筆記能幫你把道路提取課程設(shè)計做出真正的高分效果希望幫到你。本文還有配套的精品資源點擊獲取