練習題︱圖像分割與識別——UNet網路練習案例(兩則)
?> 代碼見Github:mattzheng/U-Net-Demo
CSDN越來越不好使,給差評!!!
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當然現在還有很多流行、好用的分割網路:谷歌的DeepLabv3+(DeepLab: Deep Labelling forSemantic Image Segmentation)、Mask R-CNN、COCO-16 圖像分割冠軍的實例分割FCIS(msracver/FCIS) 等。
跟目標檢測需要準備的數據集不一樣,因為圖像分割是圖像中實體的整個輪廓,所以標註的內容就是物體的掩膜。有兩種標記方式:一種是提供單個物體的掩膜、一種是提供物體輪廓的標點。
一、U-Net網路練習題一: Kaggle - 2018 Data Science Bowl
因為Kaggle有該比賽,而且code寫的很簡單易懂,於是乎拿來玩一下。Keras U-Net starter - LB 0.277
與U-Net相關的開源項目與code很多,各種框架的版本都有:Tensorflow Unet、End-to-end baseline with U-net (keras)等等。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()
推薦閱讀:
※相關濾波之基礎框架——MOSSE
※讀論文系列:Object Detection NIPS2015 Faster RCNN
※採用HALCON機器視覺軟體及C#語言檢測工件位置的方法 (之一)
※計算機視覺方面博客及代碼
※運用機器視覺實現鑄造模具的智能化檢測