人機(jī)城市圖像語(yǔ)義分割數(shù)據(jù)集實(shí)戰(zhàn):270張標(biāo)注圖與U-Net訓(xùn)練避坑指南)
簡(jiǎn)介面向計(jì)算機(jī)視覺(jué)與無(wú)人機(jī)遙感應(yīng)用開(kāi)發(fā)者這套高分辨率城市圖像語(yǔ)義分割數(shù)據(jù)集覆蓋建筑、公路、樹(shù)木等8個(gè)常見(jiàn)地物類(lèi)別可緩解城市場(chǎng)景標(biāo)注數(shù)據(jù)不足的問(wèn)題適合作為分割模型的訓(xùn)練與驗(yàn)證基準(zhǔn)。壓縮包共543個(gè)文件以541張PNG原圖與對(duì)應(yīng)掩碼為主另含1個(gè)類(lèi)別說(shuō)明文件和1個(gè)Python可視化腳本整體約263.73MB該腳本可隨機(jī)抽取一張樣本將原始圖像、GT真值圖及GT疊加蒙版效果并排展示并保存到當(dāng)前目錄便于直觀核對(duì)標(biāo)注質(zhì)量。訓(xùn)練集約200張、驗(yàn)證集約70張圖像與掩碼一一對(duì)應(yīng)目錄按訓(xùn)練集、驗(yàn)證集劃分清晰PNG格式便于直接接入常見(jiàn)深度學(xué)習(xí)框架類(lèi)別定義見(jiàn)classes文本可根據(jù)研究需要調(diào)整標(biāo)簽映射。數(shù)據(jù)既可直接用于U-Net、SwinUNet、TransUNet等分割網(wǎng)絡(luò)的訓(xùn)練與評(píng)估也適合開(kāi)展數(shù)據(jù)增強(qiáng)、遷移學(xué)習(xí)、類(lèi)別不平衡等方向的小規(guī)模驗(yàn)證實(shí)驗(yàn)。目前已有386人學(xué)習(xí)下載。1. 高分辨率無(wú)人機(jī)城市圖像語(yǔ)義分割270 張帶標(biāo)簽數(shù)據(jù)比模型更早決定你的上限說(shuō)到高分辨率無(wú)人機(jī)城市圖像語(yǔ)義分割卡住大多數(shù)人的往往不是模型選型而是標(biāo)注對(duì)齊的數(shù)據(jù)集。這份資源就是沖這個(gè)來(lái)的約 270 張無(wú)人機(jī)城市航拍圖訓(xùn)練集約 200 張、驗(yàn)證集約 70 張每張?jiān)瓐D配一張 8 類(lèi)別的 GT mask覆蓋建筑、道路、樹(shù)木等典型地物完整類(lèi)別清單以包內(nèi) classes 文件為準(zhǔn)。解壓后 train/images、train/masks、val/images、val/masks 目錄規(guī)整還帶一個(gè)可視化腳本隨機(jī)抽圖把原圖、GT、半透明蒙版拼成一張圖存盤(pán)。對(duì)正在調(diào) U-Net、SwinUNet 的開(kāi)發(fā)者來(lái)說(shuō)它省掉的是最痛苦的環(huán)節(jié)——一張張?jiān)跇?biāo)注軟件里畫(huà)多邊形再轉(zhuǎn) mask對(duì)剛?cè)腴T(mén)語(yǔ)義分割、想完整走一遍數(shù)據(jù)到訓(xùn)練鏈路的新手來(lái)說(shuō)它又是一份能直接跑通的基準(zhǔn)數(shù)據(jù)。在無(wú)人機(jī)視覺(jué)感知項(xiàng)目里我最大的體會(huì)是指標(biāo)虛高、換場(chǎng)景就崩十有八九不是模型問(wèn)題而是數(shù)據(jù)切分和標(biāo)簽編碼有問(wèn)題。下面拆目錄、拆腳本再把最容易翻車(chē)的幾個(gè)坑逐個(gè)排掉。2. 數(shù)據(jù)集結(jié)構(gòu)與標(biāo)簽編碼270 張圖、8 類(lèi) mask先驗(yàn)數(shù)據(jù)再定方案2.1 目錄劃分與文件命名規(guī)則解壓之后先別急著預(yù)覽圖片第一件事是確認(rèn)目錄層級(jí)。所有訓(xùn)練腳本的路徑、數(shù)據(jù)加載器的遍歷邏輯都要圍繞這個(gè)結(jié)構(gòu)來(lái)寫(xiě)結(jié)構(gòu)認(rèn)錯(cuò)了后面每一步都跟著錯(cuò)。這份數(shù)據(jù)集的目錄很規(guī)整常見(jiàn)做法是整理成下面這樣drone_city_seg/ ├── classes.txt # 8 類(lèi)標(biāo)簽清單每行一個(gè)類(lèi)名 ├── visualize.py # GT 可視化腳本隨機(jī)抽圖出體檢報(bào)告 ├── train/ │ ├── images/ # 訓(xùn)練原圖約 200 張 PNG │ │ ├── seq3_000200.png │ │ ├── seq2_000900.png │ │ └── ... │ └── masks/ # 與原圖同名的 GT一一對(duì)應(yīng) │ ├── seq3_000200.png │ └── ... └── val/ ├── images/ # 驗(yàn)證原圖約 70 張 PNG └── masks/文件名本身就是信息。seq3_000200.png 這種命名里seq 是序列編號(hào)可以理解為一次航拍任務(wù)里的航帶號(hào)后面 6 位數(shù)字是幀號(hào)。不同 seq 大概率來(lái)自不同的拍攝時(shí)段或不同區(qū)域同一個(gè) seq 內(nèi)部相鄰幀的視角、光照、地面景物高度相似。這個(gè)特點(diǎn)直接決定訓(xùn)練集和驗(yàn)證集該怎么切如果隨機(jī)切分而不是按 seq 隔離驗(yàn)證集里會(huì)出現(xiàn)和訓(xùn)練集幾乎同畫(huà)面的相鄰幀測(cè)出來(lái)的 mIOU 會(huì)虛高這個(gè)問(wèn)題第 4 章會(huì)展開(kāi)講。還要提醒一個(gè)細(xì)節(jié)網(wǎng)上的轉(zhuǎn)述說(shuō)“約 270 張”“訓(xùn)練集 200 張左右、驗(yàn)證集 70 張左右”解壓后建議用命令核對(duì)真實(shí)數(shù)量不要照搬轉(zhuǎn)述數(shù)字這類(lèi)數(shù)據(jù)在二次分發(fā)時(shí)數(shù)量對(duì)不上是常事。ls train/images/*.png | wc -l ls val/images/*.png | wc -l從資源里給出的文件名樣例看seq3_000200、seq2_000900、seq14_000200、seq13_000600、seq36_000400 這些序列號(hào)跨度很大說(shuō)明拍攝覆蓋了多條航帶和多個(gè)街塊不是單一路線的重復(fù)幀。這對(duì)訓(xùn)練泛化是有利的但也意味著如果驗(yàn)證集只從某幾個(gè) seq 抽評(píng)估結(jié)果會(huì)偏向那幾條航帶的地物風(fēng)格。2.2 8 類(lèi)標(biāo)簽與 mask 編碼方式classes 文件是這份數(shù)據(jù)的“字典”格式一般是每行一個(gè)類(lèi)名行號(hào)對(duì)應(yīng)類(lèi)別索引。摘要里明確提到的類(lèi)別有建筑、道路、樹(shù)其余幾類(lèi)以包內(nèi) classes 文件為準(zhǔn)。特別提醒轉(zhuǎn)述文檔里出現(xiàn)過(guò)“樹(shù)”被寫(xiě)成“書(shū)”的情況所以判斷類(lèi)別永遠(yuǎn)以 classes.txt 原文為準(zhǔn)不要信二手轉(zhuǎn)述。類(lèi)別清單對(duì)應(yīng)的語(yǔ)義大致如下類(lèi)別索引類(lèi)別名說(shuō)明0背景不歸屬其他類(lèi)的區(qū)域1建筑屋頂、墻體等建筑區(qū)域2道路車(chē)行道、路面區(qū)域3樹(shù)木樹(shù)冠、樹(shù)叢等植被4~7以 classes.txt 為準(zhǔn)其余類(lèi)別打開(kāi)文件即見(jiàn)這里值得多講一句語(yǔ)義分割和實(shí)例分割的區(qū)別。YOLO 系列里帶的是實(shí)例分割那種掩碼每個(gè)目標(biāo)一個(gè) mask 實(shí)例而這份數(shù)據(jù)是像素級(jí)的語(yǔ)義分割標(biāo)簽全圖每個(gè)像素都屬于且只屬于一個(gè)類(lèi)別同一類(lèi)別的多個(gè)對(duì)象共享同一個(gè)索引。數(shù)據(jù)加載、評(píng)估指標(biāo)的計(jì)算方式都要按語(yǔ)義分割來(lái)不能拿實(shí)例分割的思路去套否則會(huì)出很多底層錯(cuò)誤。mask 的編碼方式直接決定 DataLoader 怎么寫(xiě)。常見(jiàn)做法有兩種一種是單通道索引圖像素值就是類(lèi)別索引另一種是 RGB 調(diào)色板圖看起來(lái)是彩色的但本質(zhì)仍然是索引。打開(kāi)任意一張 mask 用這段代碼確認(rèn)import numpy as np from PIL import Image # 檢查單張 GTshape 和類(lèi)別取值決定后續(xù)預(yù)處理方式 mask np.array(Image.open(train/masks/seq3_000200.png)) print(shape:, mask.shape, dtype:, mask.dtype) print(unique values:, np.unique(mask))如果 shape 是 (H, W) 且 unique values 落在 0~7說(shuō)明是單通道索引圖直接交給 CrossEntropyLoss 就行如果 shape 是 (H, W, 3)說(shuō)明 GT 存成了三通道形式需要在預(yù)處理里先降維。這段判斷代碼建議寫(xiě)進(jìn)數(shù)據(jù)加載模塊每次啟動(dòng)訓(xùn)練自動(dòng)檢查一次而不是靠人肉記憶。2.3 訓(xùn)練集驗(yàn)證集劃分與適用場(chǎng)景270 張左右的總量訓(xùn)練集約 200 張、驗(yàn)證集約 70 張比例大約是 74/26。對(duì)中小規(guī)模的無(wú)人機(jī)城市分割任務(wù)來(lái)說(shuō)這個(gè)切分合理訓(xùn)練量足夠微調(diào)一個(gè) U-Net 或 SwinUNet 的編碼器驗(yàn)證集也能看出趨勢(shì)。但要清醒一點(diǎn)這個(gè)量級(jí)不適合從零訓(xùn)練大模型更適合做三類(lèi)事情——遷移學(xué)習(xí)、改進(jìn)算法的橫向?qū)Ρ?、?yàn)證你自己的數(shù)據(jù)管線是否可靠。落到實(shí)操上這份數(shù)據(jù)最常見(jiàn)的用法是加載預(yù)訓(xùn)練權(quán)重把最后的分割頭換成 8 類(lèi)輸出凍結(jié)編碼器只訓(xùn)解碼器或者全量微調(diào)。270 張圖在小模型上單卡 2~4 小時(shí)能跑完一個(gè)完整訓(xùn)練周期非常適合做消融實(shí)驗(yàn)。動(dòng)手之前建議做一次重疊檢查統(tǒng)計(jì) train 和 val 里各自出現(xiàn)了哪些 seq 編號(hào)確認(rèn)沒(méi)有同一個(gè) seq 的幀同時(shí)出現(xiàn)在兩邊。把文件名解析成 (seq, frame) 元組再做集合求交一條腳本就能完成。如果真的有重疊說(shuō)明當(dāng)前切分不干凈需要按第 4 章的方法重新劃一遍。這一步做不做直接影響后面所有實(shí)驗(yàn)結(jié)論的可信度。3. 可視化腳本實(shí)戰(zhàn)隨機(jī)抽圖、GT 蒙版疊加、一次跑出十張?bào)w檢圖3.1 運(yùn)行方式與輸出資源附帶的可視化腳本用途是隨機(jī)抽一張訓(xùn)練圖把原圖、GT、以及 GT 半透明疊加在原圖上的效果拼成一張三格圖保存到當(dāng)前目錄。這個(gè)動(dòng)作看似簡(jiǎn)單卻是數(shù)據(jù)體檢的第一步跑一遍你就能直觀看到標(biāo)簽貼得準(zhǔn)不準(zhǔn)、類(lèi)別邊界干不干凈、有沒(méi)有明顯漏標(biāo)。運(yùn)行方式# 在數(shù)據(jù)集根目錄執(zhí)行腳本默認(rèn)讀取 train/images 和 train/masks python visualize.py腳本沒(méi)有命令行參數(shù)路徑寫(xiě)在源碼里。如果你的目錄結(jié)構(gòu)不是標(biāo)準(zhǔn)的 train/images、train/masks打開(kāi)腳本改頭部幾個(gè)路徑常量就行通常兩三行。輸出文件名一般是 visual_check.png保存在當(dāng)前目錄。多跑幾次每次隨機(jī)抽到的圖都不同因?yàn)槟_本內(nèi)部用的是 random.choice。3.2 核心邏輯拆解與參數(shù)含義腳本完整邏輯可以濃縮成下面這段等價(jià)代碼參數(shù)含義我逐個(gè)標(biāo)注import os import random import numpy as np import matplotlib matplotlib.use(Agg) # 無(wú)界面環(huán)境也能存圖 import matplotlib.pyplot as plt from PIL import Image image_dir train/images mask_dir train/masks files [f for f in os.listdir(image_dir) if f.endswith(.png)] # 隨機(jī)抽一張保證每次體檢看到的樣本不同 pick random.choice(files) image np.array(Image.open(os.path.join(image_dir, pick))) mask np.array(Image.open(os.path.join(mask_dir, pick))) # 半透明疊加GT 區(qū)域混入 50% 紅色 overlay image.copy() if mask.ndim 3: # 彩色 GT 先壓成單通道 mask mask[:, :, 0] # 示例降維嚴(yán)謹(jǐn)做法看 4.1 overlay[mask 0] (overlay[mask 0].astype(np.float32) * 0.5 np.array([255, 0, 0]) * 0.5).astype(np.uint8) fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(image) # 第一格原圖 axes[1].imshow(mask, cmaptab20, vmin0, vmax7) # 第二格GT axes[2].imshow(overlay) # 第三格疊加 plt.tight_layout() plt.savefig(visual_check.png, dpi150)幾個(gè)參數(shù)說(shuō)明overlay 里的 0.5 是原圖和紅色的混合權(quán)重想看得更透就把原圖權(quán)重壓到 0.3cmaptab20 把 8 個(gè)類(lèi)別映射成不同顏色vmin 和 vmax 鎖死 0~7 的范圍避免類(lèi)別沒(méi)打滿(mǎn)時(shí) matplotlib 自動(dòng)拉伸色標(biāo)導(dǎo)致相鄰類(lèi)別顏色接近dpi150 保證保存圖放大后邊緣依然清楚方便檢查標(biāo)簽邊界是否對(duì)齊。這里有個(gè)容易忽略的點(diǎn)如果 mask 是彩色三通道直接 imshow 會(huì)得到一整片彩色而不是分塊分色。所以疊加前必須先判斷 ndim這也是腳本里最值得抄的防御性寫(xiě)法。我見(jiàn)過(guò)不少人的可視化腳本栽在這一步GT 一顯示就是花花綠綠的噪點(diǎn)還以為是數(shù)據(jù)壞了。3.3 我一般怎么拿這個(gè)腳本做標(biāo)簽體檢推薦一個(gè)實(shí)用的習(xí)慣把腳本連續(xù)跑 10 遍每次隨機(jī)抽圖然后把 10 張 visual_check.png 拼到一張大圖上集中過(guò)目。重點(diǎn)看三類(lèi)問(wèn)題第一建筑輪廓的邊緣是否貼齊屋頂真實(shí)邊界。無(wú)人機(jī)視角下屋頂和地面的分界線錯(cuò)位是最常見(jiàn)的標(biāo)注瑕疵尤其在斜頂建筑上蒙版會(huì)明顯偏移一眼就能看出來(lái)。第二道路是否連續(xù)。斷頭路通常意味著漏標(biāo)或者路段被樹(shù)冠遮擋后標(biāo)注員直接放棄。對(duì)后續(xù)做無(wú)人機(jī)路徑規(guī)劃類(lèi)應(yīng)用來(lái)說(shuō)不連續(xù)的道路標(biāo)簽會(huì)讓模型永遠(yuǎn)學(xué)不會(huì)完整路面。第三有沒(méi)有整片區(qū)域被標(biāo)成大類(lèi)別、小目標(biāo)被吞掉的情況。車(chē)輛、路燈、井蓋這類(lèi)像素占比極小的對(duì)象在人眼抽查時(shí)最容易暴露而在 mIOU 數(shù)字里幾乎看不出來(lái)。這輪體檢做完你對(duì)這份數(shù)據(jù)適合什么任務(wù)、邊界在哪心里基本有底了比直接丟進(jìn)模型訓(xùn)練省時(shí)間得多。順便說(shuō)一句想自制語(yǔ)義分割數(shù)據(jù)集的話(huà)這套“隨機(jī)抽圖 半透明蒙版”的檢查流程可以直接用作質(zhì)檢工具標(biāo)注一批查一批效率很高。4. 避坑指南無(wú)人機(jī)城市分割數(shù)據(jù)集最常翻車(chē)的 5 個(gè)坑4.1 mask 讀出來(lái)是彩色圖loss 直接異?,F(xiàn)象訓(xùn)練時(shí) CrossEntropyLoss 報(bào)錯(cuò)或者 loss 從第一個(gè) epoch 起就是 nan打印 GT 數(shù)組shape 是 (H, W, 3) 而不是 (H, W)。原因GT 保存成了 RGB 調(diào)色板 PNG不是單通道索引圖。這類(lèi)數(shù)據(jù)集在搬運(yùn)過(guò)程中讀圖腳本和保存腳本不一致很容易留下這種隱性炸彈。有些模型能跑通是因?yàn)閿?shù)據(jù)加載代碼里無(wú)意間只取了第一通道但不同腳本各寫(xiě)一套遲早出事。解決統(tǒng)一在讀圖環(huán)節(jié)做降維。如果顏色是逐類(lèi)純色用顏色查找表映射如果帶調(diào)色板用 PIL 轉(zhuǎn)成 P 模式再取索引。建議封裝一個(gè) to_index() 函數(shù)在 DataLoader 里強(qiáng)制走一遍并在每次啟動(dòng)訓(xùn)練時(shí)自動(dòng)打印 GT 的 shape 和 unique values 做校驗(yàn)。這套校驗(yàn)邏輯值得寫(xiě)進(jìn)你自己的工具函數(shù)庫(kù)所有分割數(shù)據(jù)集通用。4.2 驗(yàn)證集 mIOU 虛高換場(chǎng)景立刻崩現(xiàn)象在 val 上 mIOU 做到 0.8 以上自己拿無(wú)人機(jī)新拍一組城市畫(huà)面測(cè)試指標(biāo)掉到 0.5 以下模型像失憶了一樣。原因train 和 val 是隨機(jī)切幀的沒(méi)有按 seq 序列隔離。同一航帶相鄰幀的視角、光照、地物幾乎完全相同模型等于背下了畫(huà)面而不是學(xué)到了語(yǔ)義。這是航拍數(shù)據(jù)集最大的數(shù)據(jù)泄漏源比一般數(shù)據(jù)集里“同場(chǎng)景不同幀”的泄漏還要嚴(yán)重。解決按 seq 編號(hào)重新切分保證同一個(gè) seq 的幀只落在 train 或 val 的一側(cè)。做法是先解析所有文件名為 (seq, frame) 元組按 seq 分組后再切分而不是一張一張隨機(jī)抽。切完之后再跑一遍第 2.3 節(jié)的集合求交確認(rèn)兩側(cè)沒(méi)有同 seq 幀。4.3 classes 文件被轉(zhuǎn)述錯(cuò)類(lèi)別名對(duì)不上現(xiàn)象文檔說(shuō) 8 類(lèi)但 classes 文件只有 7 行或者類(lèi)別名里出現(xiàn)“書(shū)”這種明顯不屬于城市地物的詞。原因數(shù)據(jù)集在二次分發(fā)時(shí)說(shuō)明文檔被反復(fù)改寫(xiě)tree 被轉(zhuǎn)述成“書(shū)”也出現(xiàn)過(guò)整行類(lèi)別在復(fù)制粘貼時(shí)丟失的情況。摘要只點(diǎn)到了建筑、公路、樹(shù)三個(gè)類(lèi)別剩下的必須看包內(nèi)文件。解決永遠(yuǎn)以包內(nèi) classes 文件為準(zhǔn)而不是網(wǎng)上的轉(zhuǎn)述文字。解壓后第一件事就是打開(kāi) classes.txt 數(shù)行數(shù)。如果確實(shí)少了一類(lèi)用第 3 章的可視化腳本逐張看 GT 的顏色分布確認(rèn)實(shí)際標(biāo)注了幾類(lèi)再?zèng)Q定是補(bǔ)類(lèi)別還是裁剪類(lèi)別不要硬按 8 類(lèi)訓(xùn)練。4.4 高分辨率原圖直接進(jìn)模型顯存爆掉現(xiàn)象單張?jiān)瓐D分辨率很高直接按原尺寸進(jìn) U-Netbatch 稍微大一顯存就 OOM強(qiáng)行 resize 到 256 又發(fā)現(xiàn)小目標(biāo)細(xì)節(jié)全丟。原因無(wú)人機(jī)影像分辨率普遍很高直接等比縮放損失小目標(biāo)細(xì)節(jié)直接原尺寸輸入顯存又扛不住。這是高分辨率分割的老矛盾不是這份數(shù)據(jù)獨(dú)有但在這類(lèi)航拍數(shù)據(jù)上特別明顯。解決訓(xùn)練時(shí)先等比縮小到長(zhǎng)邊 2048 以?xún)?nèi)再隨機(jī)裁剪到 512 或 640驗(yàn)證階段用滑窗推理再拼接??s放 mask 時(shí)必須用 INTER_NEAREST 最近鄰插值線性插值會(huì)在類(lèi)別邊界生成不存在的中間值等于給標(biāo)簽摻了噪聲。具體參數(shù)和代碼在第 5.1 節(jié)。4.5 小目標(biāo)類(lèi)別學(xué)不到逐類(lèi)指標(biāo)暴露真相現(xiàn)象整體 mIOU 看著不錯(cuò)但逐類(lèi)看時(shí)車(chē)輛、路燈這類(lèi)小目標(biāo)類(lèi)別 IoU 接近 0實(shí)際應(yīng)用里漏檢嚴(yán)重。原因類(lèi)別極不平衡。建筑和道路占據(jù)了大部分像素?fù)p失被高頻類(lèi)別主導(dǎo)小目標(biāo)類(lèi)別的梯度被淹沒(méi)模型直接擺爛不學(xué)。解決統(tǒng)計(jì)每類(lèi)像素占比用中位數(shù)頻率平衡法給每個(gè)類(lèi)別算權(quán)重傳給 CrossEntropyLoss 的 weight 參數(shù)或者對(duì)小目標(biāo)類(lèi)別在采樣階段做針對(duì)性重采樣。權(quán)重計(jì)算代碼在第 5.2 節(jié)。另外評(píng)估時(shí)一定要逐類(lèi)打印 IoU只看平均 mIOU 會(huì)掩蓋這個(gè)小目標(biāo)問(wèn)題。5. 從數(shù)據(jù)集到模型U-Net/SwinUNet 訓(xùn)練前先完成三件事5.1 尺寸策略等比縮放 隨機(jī)裁剪別讓高分辨率拖垮訓(xùn)練270 張高分辨率無(wú)人機(jī)圖直接整圖訓(xùn)練不現(xiàn)實(shí)顯存占用有時(shí)候很玄學(xué)同樣的尺寸換個(gè) batch 就爆。我常用的策略是先等比縮放再隨機(jī)裁剪兼顧全局上下文和顯存上限代碼import cv2 import numpy as np import random def load_and_crop(image_path, mask_path, crop_size(512, 512)): # 讀原圖和 GTGT 用 IMREAD_UNCHANGED 保留原始通道 image cv2.imread(image_path) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) mask cv2.imread(mask_path, cv2.IMREAD_UNCHANGED) # 長(zhǎng)邊縮到 2048 以?xún)?nèi)保持寬高比 h, w image.shape[:2] scale min(1.0, 2048 / max(h, w)) if scale 1.0: image cv2.resize(image, (int(w * scale), int(h * scale))) mask cv2.resize(mask, (int(w * scale), int(h * scale)), interpolationcv2.INTER_NEAREST) # 隨機(jī)裁剪固定尺寸GT 不能做任何線性插值 h, w image.shape[:2] top random.randint(0, h - crop_size[0]) left random.randint(0, w - crop_size[1]) return (image[top:top crop_size[0], left:left crop_size[1]], mask[top:top crop_size[0], left:left crop_size[1]])兩個(gè)參數(shù)最關(guān)鍵scale 的 2048 是長(zhǎng)邊上限顯卡 8G 以下建議降到 1536crop_size 的 512 要和模型下采樣倍數(shù)對(duì)齊U-Net 這類(lèi)編碼器下采樣 4 次512 正好能被整除??s小時(shí) mask 必須用 INTER_NEAREST否則邊界會(huì)出現(xiàn)不存在的插值類(lèi)別。如果你的場(chǎng)景特別在意車(chē)輛這類(lèi)小目標(biāo)可以把縮放上限提高、crop 尺寸調(diào)小用更多裁剪塊來(lái)彌補(bǔ)采樣覆蓋率。5.2 類(lèi)別不均衡先算權(quán)重再選損失8 類(lèi)數(shù)據(jù)里建筑和道路占大頭幾乎是一定的訓(xùn)練前先算一遍類(lèi)別權(quán)重別等訓(xùn)練完再看逐類(lèi)指標(biāo)后悔。用中位數(shù)頻率平衡from collections import Counter import numpy as np import os from PIL import Image def compute_class_weight(mask_dir, num_classes8): counter Counter() for name in os.listdir(mask_dir): mask np.array(Image.open(os.path.join(mask_dir, name))) if mask.ndim 3: mask mask[:, :, 0] # 彩色 GT 按需降維 counter.update(np.unique(mask).tolist()) total sum(counter.values()) freq np.array([counter.get(i, 0) / total for i in range(num_classes)]) median np.median(freq[freq 0]) # 只看有像素的類(lèi)別 weights np.where(freq 0, median / freq, 0.0) return weights.astype(np.float32)用法是把 weights 數(shù)組直接傳給 CrossEntropyLoss 的 weight 參數(shù)。兩個(gè)細(xì)節(jié)統(tǒng)計(jì)時(shí) mask 如果是三通道必須先降維否則 unique 會(huì)帶出 (R,G,B) 組合值Counter 全亂掉freq 為 0 的類(lèi)別權(quán)重置 0避免給不存在的類(lèi)別分配梯度。原資源說(shuō)明里附帶了一份醫(yī)學(xué)圖像分割網(wǎng)絡(luò)介紹專(zhuān)欄和 U-Net/SwinUNet 改進(jìn)專(zhuān)欄的參考地址訓(xùn)練前翻一翻里面的改進(jìn)點(diǎn)挑個(gè)合適的解碼器結(jié)構(gòu)能少走不少?gòu)澛贰?.3 驗(yàn)證指標(biāo)逐類(lèi) mIOU 比 pixel accuracy 誠(chéng)實(shí)得多語(yǔ)義分割里 pixel accuracy 極易被建筑、道路這類(lèi)大面積類(lèi)別刷高正確做法是逐類(lèi)算 IoU 再取平均def per_class_iou(pred, gt, num_classes8): ious [] for c in range(num_classes): p (pred c) g (gt c) inter (p g).sum() union (p | g).sum() ious.append(inter / union if union 0 else 1.0) return np.array(ious)空類(lèi)別記 1.0 是常見(jiàn)約定避免某個(gè)類(lèi)別在驗(yàn)證集里恰好沒(méi)出現(xiàn)時(shí)把平均分拉低。把 8 個(gè)類(lèi)別的 IoU 全部打印出來(lái)哪個(gè)類(lèi)別學(xué)不動(dòng)一目了然。如果某個(gè)類(lèi)別長(zhǎng)期是 0優(yōu)先檢查是不是標(biāo)簽編碼錯(cuò)了而不是急著調(diào)模型——很多“模型問(wèn)題”最后都定位到數(shù)據(jù)加載上尤其是 4.1 那種三通道 GT 的坑。6. 一個(gè)容易被忽略的驗(yàn)證動(dòng)作訓(xùn)練完反查可視化比 mIOU 更早發(fā)現(xiàn)壞樣本模型訓(xùn)練完大家習(xí)慣只看 mIOU。我的習(xí)慣是額外做一次預(yù)測(cè)可視化反查從驗(yàn)證集抽幾張圖把預(yù)測(cè) mask 和 GT mask 各自疊加到原圖上并排保存人眼過(guò)一遍。這個(gè)動(dòng)作成本極低驗(yàn)證集 70 張抽 5 張就夠輸出 5 張對(duì)比圖但發(fā)現(xiàn)問(wèn)題的速度比盯指標(biāo)快得多。import numpy as np import matplotlib matplotlib.use(Agg) import matplotlib.pyplot as plt from PIL import Image # pred_mask 是模型 softmax 后 argmax 得到的索引圖 # gt_mask 是與原圖同名的 GT def overlay_compare(image, pred, gt, save_path): fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(image) axes[1].imshow(image, alpha0.6) axes[1].imshow(pred, alpha0.4, cmaptab20) axes[2].imshow(image, alpha0.6) axes[2].imshow(gt, alpha0.4, cmaptab20) axes[0].set_title(original) axes[1].set_title(pred) axes[2].set_title(GT) plt.tight_layout() plt.savefig(save_path, dpi150)反查時(shí)重點(diǎn)看三類(lèi)現(xiàn)象建筑邊緣是否出現(xiàn)鋸齒狀誤判陰影區(qū)域是否被整片吞成地面玻璃幕墻是否被標(biāo)成建筑以外的類(lèi)別。這三類(lèi)問(wèn)題在 mIOU 數(shù)字上可能只差零點(diǎn)幾個(gè)點(diǎn)但人眼掃一遍馬上有結(jié)論。我還會(huì)順手統(tǒng)計(jì)每張圖里 pred 和 GT 的類(lèi)別像素差哪些圖差異最大就優(yōu)先看哪些等于給可視化排序不用盲抽。這個(gè)習(xí)慣幫我省下過(guò)至少兩個(gè)“看起來(lái)成功”的版本——mIOU 都在 0.7 以上反查卻發(fā)現(xiàn)模型把陰影里的車(chē)輛全漏了而這恰恰是實(shí)際項(xiàng)目最在意的對(duì)象。從那以后我每次交付任何分割實(shí)驗(yàn)都強(qiáng)制自己先跑一遍可視化反查再談 mIOU。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取