<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          CV語義分割實踐指南!

          共 14600字,需瀏覽 30分鐘

           ·

          2021-04-18 23:11

          ↑↑↑關注后"星標"Datawhale
          每日干貨 & 每月組隊學習,不錯過
           Datawhale干貨 
          作者:徐和鼎,浙江大學,Datawhale優(yōu)秀學習者

          遙感技術已成為獲取地表覆蓋信息最為行之有效的手段,已經(jīng)成功應用于地表覆蓋檢測、植被面積檢測和建筑物檢測任務。本文以天池學習賽地表建筑物識別為例,對語義分割類項目的實踐全流程進行了解析。具體流程如下:

          賽題理解

          • 賽題名稱:零基礎入門語義分割-地表建筑物識別
          • 賽題地址:https://tianchi.aliyun.com/competition/entrance/531872/information

          1.1 賽題數(shù)據(jù)

          本賽題使用航拍數(shù)據(jù),需要參賽選手完成地表建筑物識別,將地表航拍圖像素劃分為有建筑物和無建筑物兩類。

          如下圖,左邊為原始航拍圖,右邊為對應的建筑物標注。

          1.2 數(shù)據(jù)標簽

          賽題為語義分割任務,因此具體的標簽為圖像像素類別。在賽題數(shù)據(jù)中像素屬于2類(無建筑物和有建筑物),因此標簽為有建筑物的像素。賽題原始圖片為jpg格式,標簽為RLE編碼的字符串。

          RLE全稱(run-length encoding),翻譯為游程編碼或行程長度編碼,對連續(xù)的黑、白像素數(shù)以不同的碼字進行編碼。RLE是一種簡單的非破壞性資料壓縮法,經(jīng)常用在在語義分割比賽中對標簽進行編碼。

          RLE與圖片之間的轉(zhuǎn)換代碼詳見本文第二節(jié)Baseline代碼解析。

          1.3 評價指標

          賽題使用Dice coefficient來衡量選手結(jié)果與真實標簽的差異性,Dice coefficient可以按像素差異性來比較結(jié)果的差異性。Dice coefficient的具體計算方式如下:


          其中是預測結(jié)果, 為真實標簽的結(jié)果。當完全相同時Dice coefficient為1,排行榜使用所有測試集圖片的平均Dice coefficient來衡量,分數(shù)值越大越好。

          1.4 解題思路

          由于本次賽題是一個典型的語義分割任務,因此可以直接使用語義分割的模型來完成:

          • 步驟1:使用FCN模型模型跑通具體模型訓練過程,并對結(jié)果進行預測提交;
          • 步驟2:在現(xiàn)有基礎上加入數(shù)據(jù)擴增方法,并劃分驗證集以監(jiān)督模型精度;
          • 步驟3:使用更加強大模型結(jié)構(gòu)(如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 - background
              Returns run length as string formated
              '''

              pixels = im.flatten(order = 'F')
              pixels = np.concatenate([[0], pixels, [0]])
              runs = np.where(pixels[1:] != pixels[:-1])[0] + 1
              runs[1::2] -= runs[::2]
              return ' '.join(str(x) for x in runs)

          Ⅱ.將rle格式進行解碼為圖片

          # 將rle格式進行解碼為圖片
          def rle_decode(mask_rle, shape=(512512)):
              '''
              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 -= 1
              ends = starts + lengths
              img = np.zeros(shape[0]*shape[1], dtype=np.uint8)
              for lo, hi in zip(starts, ends):
                  img[lo:hi] = 1
              return img.reshape(shape, order='F')

          RLE編碼的時候返回的時候每兩個數(shù)字有空格為間隔,利用s = mask_rle.split()將空格去掉。

          s[0:][::2]表示(從1開始的)索引,s[1:][::2]表示個數(shù)。于是starts存的是索引,lengths存的是個數(shù),兩者為一一對應關系。

          starts \-= 1轉(zhuǎn)化為(從0開始的)索引。

          后續(xù)就是創(chuàng)建一副全0的一維序列,填充1,再按列排序,轉(zhuǎn)為二維的二值圖,就解碼成圖片了。

          如果輸入的mask_rle是空的,那么返回的就是全為0的mask,可以觀察數(shù)據(jù)發(fā)現(xiàn),部分圖片的地表建筑不存在,他們的rle標簽也就是空的。

          Ⅲ.定義數(shù)據(jù)集

          class TianChiDataset(D.Dataset):
              def __init__(self, paths, rles, transform, test_mode=False):
                  self.paths = paths
                  self.rles = rles
                  self.transform = transform
                  self.test_mode = test_mode
                  
                  self.len = len(paths)
                  self.as_tensor = T.Compose([
                      T.ToPILImage(),
                      T.Resize(IMAGE_SIZE),
                      T.ToTensor(),
                      T.Normalize([0.6250.4480.688],
                                  [0.1310.1770.101]),
                  ])
                  
              # get data operation
              def __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

          定義數(shù)據(jù)集,主要作了數(shù)據(jù)的預處理。其中,我將opencv的讀取圖片換成了PIL讀取,因為路徑中包含中文
          augments['mask'][None]中的[None],將(256,256)的mask形狀轉(zhuǎn)為(1,256,256),起到升維作用。

          Ⅳ.可視化一下效果

          這一步主要是為了驗證上述的代碼。用了rle_encode(rle_decode(RLE標簽))==RLE標簽來驗證之前寫的RLE編碼和解碼正確性。


          train_mask = pd.read_csv('數(shù)據(jù)集/train_mask.csv', sep='\t', names=['name''mask'])
          train_mask['name'] = train_mask['name'].apply(lambda x: '數(shù)據(jù)集/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: '數(shù)據(jù)集/train/' + x)這一步就是在圖片前補全下路徑

          0        KWP8J3TRSV.jpg
          1        DKI3X4VFD3.jpg
          2        AYPOE51XNI.jpg
          3        1D9V7N0DGF.jpg
          4        AWXXR4VYRI.jpg
          0        數(shù)據(jù)集/train/KWP8J3TRSV.jpg
          1        數(shù)據(jù)集/train/DKI3X4VFD3.jpg
          2        數(shù)據(jù)集/train/AYPOE51XNI.jpg
          3        數(shù)據(jù)集/train/1D9V7N0DGF.jpg
          4        數(shù)據(jù)集/train/AWXXR4VYRI.jpg

          實例化數(shù)據(jù)集

          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()# 補上


          看一下第二張圖片

          image, mask = dataset[1]

          沒有建筑物,mask全黑。

          Ⅴ.加載數(shù)據(jù)集

          #定義數(shù)據(jù)集
          train_mask = pd.read_csv('數(shù)據(jù)集/train_mask.csv', sep='\t', names=['name''mask'])
          train_mask['name'] = train_mask['name'].apply(lambda x: '數(shù)據(jù)集/train/' + x)

          dataset = TianChiDataset(
              train_mask['name'].values,
              train_mask['mask'].fillna('').values,
              trfm, False
          )


          #劃分數(shù)據(jù)集(按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是按照索引序列來劃分數(shù)據(jù)集的, 于是按照每7個數(shù)據(jù)里面,1個當作驗證集,6個當作訓練集。最后放入數(shù)據(jù)加載器中。

          Ⅵ.定義模型、優(yōu)化器、損失函數(shù)

          # 定義模型
          model = get_model()
          model.to(DEVICE)
          #model.load_state_dict(torch.load("model_best.pth"))


          #定義優(yōu)化器
          optimizer = torch.optim.AdamW(model.parameters(),
                            lr=1e-4, weight_decay=1e-3)
          #定義損失函數(shù)
          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):#取消了tqdm
                  image, 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 = vloss
                  torch.save(model.state_dict(), 'model_best.pth')
                  print("save successful!")

          Ⅷ.使用模型對測試集進行預測

          trfm = T.Compose([
              T.ToPILImage(),
              T.Resize(IMAGE_SIZE),
              T.ToTensor(),
              T.Normalize([0.6250.4480.688],
                          [0.1310.1770.101]),
          ])

          subm = []

          model.load_state_dict(torch.load("./model_best.pth"))
          model.eval()

          test_mask = pd.read_csv('數(shù)據(jù)集/test_a_samplesubmit.csv', sep='\t', names=['name''mask'])
          test_mask['name'] = test_mask['name'].apply(lambda x: '數(shù)據(jù)集/test_a/' + x)

          for idx, name in enumerate(tqdm(test_mask['name'].iloc[:])):
              image = np.array(Image.open(name))#改成PIL
              image = 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, (512512))

                  # break
              subm.append([name.split('/')[-1], rle_encode(score_sigmoid)])

          subm = pd.DataFrame(subm)
          subm.to_csv('./tmp.csv', index=None, header=None, sep='\t')

          Ⅸ.可視化模型預測結(jié)果

          from file1 import rle_decode
          from PIL import Image
          import pandas as pd
          import numpy as np

          subm = 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('數(shù)據(jù)集/test_a/' + subm.iloc[num,0])))
              plt.show()
          if __name__ == '__main__':
              show_predict_pic(num=10)

          查看第10張圖片的預測結(jié)果



          整理不易,三連
          瀏覽 54
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  最新亚洲国产黄色视频在线观看 | 丝瓜污视频在线观看 | 欧美操妣免费看 | 综合一区二区三区 | 国产特级毛片AAAAAA |