【深度学习】CV语义分割实践指南!
作者:徐和鼎,浙江大學,Datawhale優秀學習者
遙感技術已成為獲取地表覆蓋信息最為行之有效的手段,已經成功應用于地表覆蓋檢測、植被面積檢測和建筑物檢測任務。本文以天池學習賽地表建筑物識別為例,對語義分割類項目的實踐全流程進行了解析。具體流程如下:
賽題理解
賽題名稱:零基礎入門語義分割-地表建筑物識別
賽題地址:https://tianchi.aliyun.com/competition/entrance/531872/information
1.1 賽題數據
本賽題使用航拍數據,需要參賽選手完成地表建筑物識別,將地表航拍圖像素劃分為有建筑物和無建筑物兩類。
如下圖,左邊為原始航拍圖,右邊為對應的建筑物標注。
1.2 數據標簽
賽題為語義分割任務,因此具體的標簽為圖像像素類別。在賽題數據中像素屬于2類(無建筑物和有建筑物),因此標簽為有建筑物的像素。賽題原始圖片為jpg格式,標簽為RLE編碼的字符串。
RLE全稱(run-length encoding),翻譯為游程編碼或行程長度編碼,對連續的黑、白像素數以不同的碼字進行編碼。RLE是一種簡單的非破壞性資料壓縮法,經常用在在語義分割比賽中對標簽進行編碼。
RLE與圖片之間的轉換代碼詳見本文第二節Baseline代碼解析。
1.3 評價指標
賽題使用Dice coefficient來衡量選手結果與真實標簽的差異性,Dice coefficient可以按像素差異性來比較結果的差異性。Dice coefficient的具體計算方式如下:
其中是預測結果, 為真實標簽的結果。當與完全相同時Dice coefficient為1,排行榜使用所有測試集圖片的平均Dice coefficient來衡量,分數值越大越好。
1.4 解題思路
由于本次賽題是一個典型的語義分割任務,因此可以直接使用語義分割的模型來完成:
步驟1:使用FCN模型模型跑通具體模型訓練過程,并對結果進行預測提交;
步驟2:在現有基礎上加入數據擴增方法,并劃分驗證集以監督模型精度;
步驟3:使用更加強大模型結構(如Unet和PSPNet)或尺寸更大的輸入完成訓練;
步驟4:訓練多個模型完成模型集成操作;
Baseline代碼分析
Ⅰ.將圖片編碼為rle格式
import?numpy?as?np import?pandas?as?pd import?cv2#?將圖片編碼為rle格式 def?rle_encode(im):'''im:?numpy?array,?1?-?mask,?0?-?backgroundReturns?run?length?as?string?formated'''pixels?=?im.flatten(order?=?'F')pixels?=?np.concatenate([[0],?pixels,?[0]])runs?=?np.where(pixels[1:]?!=?pixels[:-1])[0]?+?1runs[1::2]?-=?runs[::2]return?'?'.join(str(x)?for?x?in?runs)Ⅱ.將rle格式進行解碼為圖片
#?將rle格式進行解碼為圖片 def?rle_decode(mask_rle,?shape=(512,?512)):'''mask_rle:?run-length?as?string?formated?(start?length)shape:?(height,width)?of?array?to?return?Returns?numpy?array,?1?-?mask,?0?-?background'''s?=?mask_rle.split()starts,?lengths?=?[np.asarray(x,?dtype=int)?for?x?in?(s[0:][::2],?s[1:][::2])]starts?-=?1ends?=?starts?+?lengthsimg?=?np.zeros(shape[0]*shape[1],?dtype=np.uint8)for?lo,?hi?in?zip(starts,?ends):img[lo:hi]?=?1return?img.reshape(shape,?order='F')RLE編碼的時候返回的時候每兩個數字有空格為間隔,利用s = mask_rle.split()將空格去掉。
s[0:][::2]表示(從1開始的)索引,s[1:][::2]表示個數。于是starts存的是索引,lengths存的是個數,兩者為一一對應關系。
starts \-= 1轉化為(從0開始的)索引。
后續就是創建一副全0的一維序列,填充1,再按列排序,轉為二維的二值圖,就解碼成圖片了。
如果輸入的mask_rle是空的,那么返回的就是全為0的mask,可以觀察數據發現,部分圖片的地表建筑不存在,他們的rle標簽也就是空的。
Ⅲ.定義數據集
class?TianChiDataset(D.Dataset):def?__init__(self,?paths,?rles,?transform,?test_mode=False):self.paths?=?pathsself.rles?=?rlesself.transform?=?transformself.test_mode?=?test_modeself.len?=?len(paths)self.as_tensor?=?T.Compose([T.ToPILImage(),T.Resize(IMAGE_SIZE),T.ToTensor(),T.Normalize([0.625,?0.448,?0.688],[0.131,?0.177,?0.101]),])#?get?data?operationdef?__getitem__(self,?index):#img?=?cv2.imread(self.paths[index])img?=?np.array(Image.open(self.paths[index]))if?not?self.test_mode:mask?=?rle_decode(self.rles[index])augments?=?self.transform(image=img,?mask=mask)return?self.as_tensor(augments['image']),?augments['mask'][None]#(3,256,256),(1,256,256)else:return?self.as_tensor(img),?''????????def?__len__(self):"""Total?number?of?samples?in?the?dataset"""return?self.len定義數據集,主要作了數據的預處理。其中,我將opencv的讀取圖片換成了PIL讀取,因為路徑中包含中文
augments['mask'][None]中的[None],將(256,256)的mask形狀轉為(1,256,256),起到升維作用。
Ⅳ.可視化一下效果
這一步主要是為了驗證上述的代碼。用了rle_encode(rle_decode(RLE標簽))==RLE標簽來驗證之前寫的RLE編碼和解碼正確性。
train_mask?=?pd.read_csv('數據集/train_mask.csv',?sep='\t',?names=['name',?'mask']) train_mask['name']?=?train_mask['name'].apply(lambda?x:?'數據集/train/'?+?x)img?=?cv2.imread(train_mask['name'].iloc[0]) mask?=?rle_decode(train_mask['mask'].iloc[0])print(rle_encode(mask)?==?train_mask['mask'].iloc[0])train_mask['name'].apply(lambda x: '數據集/train/' + x)這一步就是在圖片前補全下路徑
0????????KWP8J3TRSV.jpg 1????????DKI3X4VFD3.jpg 2????????AYPOE51XNI.jpg 3????????1D9V7N0DGF.jpg 4????????AWXXR4VYRI.jpg 0????????數據集/train/KWP8J3TRSV.jpg 1????????數據集/train/DKI3X4VFD3.jpg 2????????數據集/train/AYPOE51XNI.jpg 3????????數據集/train/1D9V7N0DGF.jpg 4????????數據集/train/AWXXR4VYRI.jpg實例化數據集
dataset?=?TianChiDataset(train_mask['name'].values,train_mask['mask'].fillna('').values,trfm,?False )fillna('')起到補全缺失值為''的作用
可視化
image,?mask?=?dataset[0] plt.figure(figsize=(16,8)) plt.subplot(121) plt.imshow(mask[0],?cmap='gray') plt.subplot(122) plt.imshow(image[0]) plt.show()#?補上
看一下第二張圖片
沒有建筑物,mask全黑。
Ⅴ.加載數據集
#定義數據集 train_mask?=?pd.read_csv('數據集/train_mask.csv',?sep='\t',?names=['name',?'mask']) train_mask['name']?=?train_mask['name'].apply(lambda?x:?'數據集/train/'?+?x)dataset?=?TianChiDataset(train_mask['name'].values,train_mask['mask'].fillna('').values,trfm,?False )#劃分數據集(按index手動去劃分) valid_idx,?train_idx?=?[],?[] for?i?in?range(len(dataset)):if?i?%?7?==?0:valid_idx.append(i)else:#?elif?i?%?7?==?1:train_idx.append(i)train_ds?=?D.Subset(dataset,?train_idx) valid_ds?=?D.Subset(dataset,?valid_idx)#?print(len(dataset))#30000 #?print(len(train_ds))#4286 #?print(len(valid_ds))#4286#?define?training?and?validation?data?loaders loader?=?D.DataLoader(train_ds,?batch_size=BATCH_SIZE,?shuffle=True,?num_workers=0)vloader?=?D.DataLoader(valid_ds,?batch_size=BATCH_SIZE,?shuffle=False,?num_workers=0)D.subset是按照索引序列來劃分數據集的, 于是按照每7個數據里面,1個當作驗證集,6個當作訓練集。最后放入數據加載器中。
Ⅵ.定義模型、優化器、損失函數
#?定義模型 model?=?get_model() model.to(DEVICE) #model.load_state_dict(torch.load("model_best.pth"))#定義優化器 optimizer?=?torch.optim.AdamW(model.parameters(),lr=1e-4,?weight_decay=1e-3) #定義損失函數 bce_fn?=?nn.BCEWithLogitsLoss() dice_fn?=?SoftDiceLoss() def?loss_fn(y_pred,?y_true):bce?=?bce_fn(y_pred,?y_true)dice?=?dice_fn(y_pred.sigmoid(),?y_true)return?0.8?*?bce?+?0.2?*?diceⅦ.進行訓練
header?=?r'''Train?|?Valid Epoch?|??Loss?|??Loss?|?Time,?m ''' #??????????Epoch?????????metrics????????????time raw_line?=?'{:6d}'?+?'\u2502{:7.3f}'?*?2?+?'\u2502{:6.2f}' print(header)EPOCHES?=?10 best_loss?=?10 for?epoch?in?range(1,?EPOCHES?+?1):losses?=?[]start_time?=?time.time()model.train()for?image,?target?in?tqdm(loader):#取消了tqdmimage,?target?=?image.to(DEVICE),?target.float().to(DEVICE)optimizer.zero_grad()output?=?model(image)['out']loss?=?loss_fn(output,?target)loss.backward()optimizer.step()losses.append(loss.item())#?print(loss.item())vloss?=?validation(model,?vloader,?loss_fn)print(raw_line.format(epoch,?np.array(losses).mean(),?vloss,(time.time()?-?start_time)?/?60?**?1))losses?=?[]if?vloss?<?best_loss:best_loss?=?vlosstorch.save(model.state_dict(),?'model_best.pth')print("save?successful!")Ⅷ.使用模型對測試集進行預測
trfm?=?T.Compose([T.ToPILImage(),T.Resize(IMAGE_SIZE),T.ToTensor(),T.Normalize([0.625,?0.448,?0.688],[0.131,?0.177,?0.101]), ])subm?=?[]model.load_state_dict(torch.load("./model_best.pth")) model.eval()test_mask?=?pd.read_csv('數據集/test_a_samplesubmit.csv',?sep='\t',?names=['name',?'mask']) test_mask['name']?=?test_mask['name'].apply(lambda?x:?'數據集/test_a/'?+?x)for?idx,?name?in?enumerate(tqdm(test_mask['name'].iloc[:])):image?=?np.array(Image.open(name))#改成PILimage?=?trfm(image)with?torch.no_grad():image?=?image.to(DEVICE)[None]score?=?model(image)['out'][0][0]score_sigmoid?=?score.sigmoid().cpu().numpy()score_sigmoid?=?(score_sigmoid?>?0.5).astype(np.uint8)score_sigmoid?=?cv2.resize(score_sigmoid,?(512,?512))#?breaksubm.append([name.split('/')[-1],?rle_encode(score_sigmoid)])subm?=?pd.DataFrame(subm) subm.to_csv('./tmp.csv',?index=None,?header=None,?sep='\t')Ⅸ.可視化模型預測結果
from?file1?import?rle_decode from?PIL?import?Image import?pandas?as?pd import?numpy?as?npsubm?=?pd.read_csv("./tmp.csv",sep="\t",names=["name","mask"]) def?show_predict_pic(num=0):plt.figure(figsize=(16,8))plt.subplot(121)plt.imshow(rle_decode(subm.fillna('').iloc[num,1]),?cmap='gray')plt.subplot(122)plt.imshow(np.array(Image.open('數據集/test_a/'?+?subm.iloc[num,0])))plt.show() if?__name__?==?'__main__':show_predict_pic(num=10)查看第10張圖片的預測結果
往期精彩回顧適合初學者入門人工智能的路線及資料下載機器學習及深度學習筆記等資料打印機器學習在線手冊深度學習筆記專輯《統計學習方法》的代碼復現專輯 AI基礎下載機器學習的數學基礎專輯溫州大學《機器學習課程》視頻 本站qq群851320808,加入微信群請掃碼:總結
以上是生活随笔為你收集整理的【深度学习】CV语义分割实践指南!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【机器学习基础】半监督算法概览(Pyth
- 下一篇: mac怎么设置默认浏览器 mac默认浏览