1. 程式人生 > >練習題︱影象分割與識別——UNet網路練習案例(兩則)

練習題︱影象分割與識別——UNet網路練習案例(兩則)

U-Net是Kaggle比賽非常青睞的模型,簡單、高效、易懂,容易定製,可以從相對較小的訓練集中學習。來看幾個變形:

  • (1)Supervise.ly 公司。
    在用 Faster-RCNN(基於 NasNet)定位 + UNet-like 架構的分割,來做他們資料眾包影象分割方向的主動學習,當時沒有使用 Mask-RCNN,因為靠近物體邊緣的分割質量很低(終於!Supervise.ly 釋出人像分割資料集啦(免費開源));

  • (2)Kaggle-衛星影象分割與識別。
    需要分割出:房屋和樓房;混雜的人工建築;道路;鐵路;樹木;農作物;河流;積水區;大型車輛;小轎車。在U-Net基礎上微調了一下。 而且針對不同的影象型別,微調的地方不一樣,就會有不同的分割模型,最後融合。(

    Kaggle優勝者詳解:如何用深度學習實現衛星影象分割與識別

  • (3)廣東政務資料創新大賽—智慧演算法賽 。
    國土監察業務中須監管地上建築物的建、拆、改、擴,高解析度影象和智慧演算法以自動化完成工作。並且:八通道U-Net:直接輸出房屋變化,可應對高層建築傾斜問題;資料增強:增加模型泛化性,簡單有效;加權損失函式:增強對新增建築的檢測能力;模型融合:取長補短,結果更全。(參考:LiuDongjing/BuildingChangeDetector
    這裡寫圖片描述

  • (4)Kaggle車輛邊界識別——TernausNet。
    由VGG初始化權重 + U-Net網路,Kaggle Carvana Image Masking Challenge 第一名,使用的預訓練權重改進U-Net,提升影象分割的效果。開源的程式碼在

    ternaus/TernausNet

跟目標檢測需要準備的資料集不一樣,因為影象分割是影象中實體的整個輪廓,所以標註的內容就是物體的掩膜。有兩種標記方式:一種是提供單個物體的掩膜、一種是提供物體輪廓的標點。

一、U-Net網路練習題一: Kaggle - 2018 Data Science Bowl

1.1 訓練集的構造

因為使用的是比賽資料,賽方已經很好地幫我們做好了前期資料整理的工作,所以目前來說可能很方便的製作訓練集、測試集然後跑模型。這裡下載得到的資料為提供影象中單個物體的掩膜。其中,筆者認為最麻煩的就是標註集的構造(掩膜)。

原圖:

這裡寫圖片描述

掩膜圖:

這裡寫圖片描述

從掩膜列表可以到,比賽中是把每個細胞的掩膜都分開來了。來看一下這個掩膜標註內容如何:

mask = np.zeros((IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.bool)
Y_train = np.zeros((len(train_ids), IMG_HEIGHT, IMG_WIDTH, 1), dtype=np.bool)
for mask_file in next(os.walk(path + '/masks/'))[2]:
    mask_ = imread(path + '/masks/' + mask_file)
    mask_ = np.expand_dims(resize(mask_, (IMG_HEIGHT, IMG_WIDTH), mode='constant', 
                                  preserve_range=True), axis=-1)
    mask = np.maximum(mask, mask_)
Y_train[n] = mask
  • 讀入(imread)掩膜圖,影象的格式為:(m,n);
  • resize,掩膜的尺寸縮放在128*128
  • np.expand_dims步驟改變影象維度為(m,n,1);
  • np.maximum,當出現很多掩膜的時候,有些掩膜會重疊,那麼就需要留下共有的部分;
  • Y_train的資料格式已經定義為bool型,那麼最後儲存得到的資料即為(x,m,n,1),且資料格式為True/False:
array([[[[False],
         [False],
         [False],
         ..., 
         [False],
         [False],
         [False]],

        [[False],
         [False],
         [False],
         ..., 
         [False],
...

其他X_train訓練資料集,就會被儲存成:(x,m,n,3),同時需要resize成128*128

1.2 預測

預測就可以用model.predict(X_test, verbose=1),即可以得到結果。那麼得到的結果是(128,128,1)的,那麼就是一個圖層,也就是說U-Net出來的結果是單標籤的,如果是多標籤那麼可以多套模型,可參考:Kaggle-衛星影象分割與識別。
預測出來的結果為單圖層,可以重新回到原尺寸:

resize(np.squeeze(preds_test[i]),
     (sizes_test[i][0], sizes_test[i][1]), mode='constant', preserve_range=True)

1.3 結果提交

影象分割在提交結果的時候,主要就是掩膜了。那麼掩膜的提交需要編碼壓縮:
Run-Length Encoding(RLE)行程長度的原理是將一掃描行中的顏色值相同的相鄰畫素用一個計數值和那些畫素的顏色值來代替。例如:aaabccccccddeee,則可用3a1b6c2d3e來代替。對於擁有大面積,相同顏色區域的影象,用RLE壓縮方法非常有效。由RLE原理派生出許多具體行程壓縮方法。
那麼影象壓縮出來的結果即為:

'137795 3 138292 25 138802 29 139312 32 139823 34 140334 36 140845 38 141356 40 141867 42 142371 51 142881 54 143391 57 143902 59 144414 59 144925 61 145436 62 145948 63 146459 65 146970 66 147482 66 147994 66 148506 66 149017 67 149529 67 150041 67 150553 67 151065 67 151577 66 152089 66 152602 65 153114 64 153626 64 154138 63 154650 63 155162 63 155674 63 156187 62 156699 62 157212 60 157724 60 158236 60 158749 59 159261 59 159773 58 160285 58 160798 56 161310 56 161823 55 162335 54 162848 53 163361 52 163874 50 164387 49 164899 48 165412 47 165925 45 166439 42 166953 40 167466 38 167980 35 168495 31 169009 28 169522 26 170036 23 170549 21 171062 18 171577 12 172093 4'

那麼下圖就是出來的結果了,第一張為原圖,第二張為標註的掩膜圖,第三張為預測圖。

這裡寫圖片描述

二、U-Net網路練習題二:氣球識別

在《如何使用Mask RCNN模型進行影象實體分割?》一文中提到了用Mask-RCNN來做氣球分割,官網之中也有對應的程式碼,本著練習的態度,那麼筆者就拿來這個資料集繼續練手,最麻煩的仍然是如何得到標註資料。MaskRCNN的開源code為Mask R-CNN - Inspect Balloon Training Data

由於很多內容是從Mask R-CNN之中挖過來的,筆者也沒細究,能用就行,所以會顯得很笨拙…

2.1 訓練集的準備

資料下載頁面:balloon_dataset.zip
該案例更為通用,因為比賽的訓練集是比賽方寫好的,一般實際訓練的時候,掩膜都是沒有給出的,而只是給出標記點,如:

這裡寫圖片描述

這裡寫圖片描述

此時的標註資料都放在json之中,譬如:

{'10464445726_6f1e3bbe6a_k.jpg712154': {'base64_img_data': '',
  'file_attributes': {},
  'filename': '10464445726_6f1e3bbe6a_k.jpg',
  'fileref': '',
  'regions': {'0': {'region_attributes': {},
    'shape_attributes': {'all_points_x': [1757,
      1772,
      1787,
      1780,
      1764],
     'all_points_y': [867,
      913,
      986,
      1104,
      1170],
     'name': 'polygon'}},

all_points_x以及all_points_y都是掩膜標記的(x,y)點座標,每一個物體都是由很多個box構造而成:

def get_mask(a,dataset_dir):
    image_path = os.path.join(dataset_dir, a['filename'])
    image = io.imread(image_path)
    height, width = image.shape[:2]
    polygons = [r['shape_attributes'] for r in a['regions'].values()]
    mask = np.zeros([height, width, len(polygons)],dtype=np.uint8) 

    # 掩膜mask
    for i, p in enumerate(polygons):
        # Get indexes of pixels inside the polygon and set them to 1
        rr, cc = skimage.draw.polygon(p['all_points_y'], p['all_points_x'])
        mask[rr, cc, i] = 1
    # 此時mask為(685, 1024, 1)

    # mask二值化
    mask, class_ids = mask.astype(np.bool), np.ones([mask.shape[-1]], dtype=np.int32)

    # 提取每個掩膜的座標
    boxes = extract_bboxes(resize(mask, (128, 128), mode='constant',preserve_range=True))

    unique_class_ids = np.unique(class_ids)
    mask_area = [np.sum(mask[:, :, np.where(class_ids == i)[0]])
                     for i in unique_class_ids]
    top_ids = [v[0] for v in sorted(zip(unique_class_ids, mask_area),
                                    key=lambda r: r[1], reverse=True) if v[1] > 0]

    class_id = top_ids[0]
    # Pull masks of instances belonging to the same class.
    m = mask[:, :, np.where(class_ids == class_id)[0]]
    m = np.sum(m * np.arange(1, m.shape[-1] + 1), -1)

    return m,image,height,width,class_ids,boxes
  • polygon之中記錄的是一個掩膜的(x,y)點座標,然後通過skimage.draw.polygon連成圈;
  • mask[rr, cc, i] = 1這句中,mask變成了一個0/1的(m,n,x)的矩陣,x代表可能有x個物體;
  • mask.astype(np.bool)將上述的0/1矩陣,變為T/F矩陣;
  • extract_bboxes()函式,要著重說,因為他是根據掩膜的位置,找出整體掩膜的座標點,給入5個物體,他就會返回5個物體的座標(xmax,ymax,xmin,ymin)
  • np.sum()是降維的過程,把(m,n,1)到(m,n)

那麼,最終 Y_train的資料格式如案例一,一樣的:

array([[[[False],
         [False],
         [False],
         ..., 
         [False],
         [False],
         [False]],

        [[False],
         [False],
         [False],
         ..., 
         [False],
...

2.2 模型預測

model = load_model(model_name, custom_objects={'mean_iou': mean_iou})
preds_train = model.predict(X_train[:int(X_train.shape[0]*0.9)], verbose=1) 
preds_val = model.predict(X_train[int(X_train.shape[0]*0.9):],verbose=1)   
preds_test = model.predict(X_test,verbose=1)  

這邊的操作是把trainset按照9:1,分為訓練集、驗證集,還有一部分是測試集

輸入維度:

    X_train (670, 128, 128, 3)
    Y_train (670, 128, 128, 1)
    X_test  (65, 128, 128, 3)

輸出維度:
每個畫素點的概率[0,1]

    preds_train  (603, 128, 128, 1)
    preds_val    (67, 128, 128, 1)
    preds_test   (65, 128, 128, 1)

2.3 畫圖函式

該部分是從MaskRCNN中搬過來的,

def display_instances(image, boxes, masks, class_names,
                      scores=None, title="",
                      figsize=(16, 16), ax=None,
                      show_mask=True, show_bbox=True,
                      colors=None, captions=None):

需要影象矩陣image,boxes代表每個例項的boxes,masks是影象的掩膜,class_names,是每張圖標籤的名稱。下圖是128*128畫素的,很模糊,將就著看吧…

這裡寫圖片描述

隨機顏色生成函式random_colors

def random_colors(N, bright=True):
    """
    Generate random colors.
    To get visually distinct colors, generate them in HSV space then
    convert to RGB.
    """
    brightness = 1.0 if bright else 0.7
    hsv = [(i / N, 1, brightness) for i in range(N)]
    colors = list(map(lambda c: colorsys.hsv_to_rgb(*c), hsv))
    random.shuffle(colors)
    return colors

還有就是一般來說,掩膜如果是(m,n),或者讓是(m,n,1)都是可以畫出來的。

imshow(mask)
plt.show()