1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        【深度學(xué)習(xí)】CV語義分割實踐指南!

        共 14644字,需瀏覽 30分鐘

         ·

        2021-04-23 17:27

        作者:徐和鼎,浙江大學(xué),Datawhale優(yōu)秀學(xué)習(xí)者

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

        賽題理解

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

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

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

        如下圖,左邊為原始航拍圖,右邊為對應(yīng)的建筑物標(biāo)注。

        1.2 數(shù)據(jù)標(biāo)簽

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

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

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

        1.3 評價指標(biāo)

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


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

        1.4 解題思路

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

        • 步驟1:使用FCN模型模型跑通具體模型訓(xùn)練過程,并對結(jié)果進行預(yù)測提交;
        • 步驟2:在現(xiàn)有基礎(chǔ)上加入數(shù)據(jù)擴增方法,并劃分驗證集以監(jiān)督模型精度;
        • 步驟3:使用更加強大模型結(jié)構(gòu)(如Unet和PSPNet)或尺寸更大的輸入完成訓(xùn)練;
        • 步驟4:訓(xùn)練多個模型完成模型集成操作;

        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ù),兩者為一一對應(yīng)關(guān)系。

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

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

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

        Ⅲ.定義數(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ù)的預(yù)處理。其中,我將opencv的讀取圖片換成了PIL讀取,因為路徑中包含中文
        augments['mask'][None]中的[None],將(256,256)的mask形狀轉(zhuǎn)為(1,256,256),起到升維作用。

        Ⅳ.可視化一下效果

        這一步主要是為了驗證上述的代碼。用了rle_encode(rle_decode(RLE標(biāo)簽))==RLE標(biāo)簽來驗證之前寫的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
        )


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

        Ⅶ.進行訓(xùn)練

        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!")

        Ⅷ.使用模型對測試集進行預(yù)測

        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')

        Ⅸ.可視化模型預(yù)測結(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張圖片的預(yù)測結(jié)果


        往期精彩回顧





        本站qq群851320808,加入微信群請掃碼:

        瀏覽 59
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

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

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            思思热在线 | 激情久久一区二区 | 国产精品福利导航 | 国产性综合 | 蜜臀久久99精品久久久兰草影视 | 538色视频一区二区三区 | 韩国下部无遮挡图片 | 激情性爱五月天 | 中文在线亚洲 | 日本理伦少妇2做爰 |