實踐教程|CV語義分割標簽之間的相互轉換

極市導讀
?本文以語義分割任務為例,介紹了標簽的不同表達形式。并介紹了不同表達形式之間應該如何相互轉換,附相關代碼。?>>加入極市CV技術交流群,走在計算機視覺的最前沿
圖像分割是計算機視覺中除了分類和檢測外的另一項基本任務,它意味著要將圖片根據內容分割成不同的塊。相比圖像分類和檢測,分割是一項更精細的工作,因為需要對每個像素點分類。如下圖的街景分割,由于對每個像素點都分類,物體的輪廓是精準勾勒的,而不是像檢測那樣給出邊界框。

圖像分割可以分為以下三個子領域:語義分割、實例分割、全景分割。

由對比圖可發(fā)現,語義分割是從像素層次來識別圖像,為圖像中的每個像素制定類別標記,目前廣泛應用于醫(yī)學圖像和無人駕駛等;實例分割相對更具有挑戰(zhàn)性,不僅需要正確檢測圖像中的目標,同時還要精確的分割每個實例;全景分割綜合了兩個任務,要求圖像中的每個像素點都必須被分配給一個語義標簽和一個實例id。
01 語義分割中的關鍵步驟
在進行網絡訓練時,時常需要對語義標簽圖或是實例分割圖進行預處理。如對于一張彩色的標簽圖,通過顏色映射表得到每種顏色所代表的類別,再將其轉換成相應的掩膜或Onehot編碼完成訓練。這里將會對于其中的關鍵步驟進行講解。首先,以語義分割任務為例,介紹標簽的不同表達形式。
1.1 語義標簽圖
語義分割數據集中包括原圖和語義標簽圖,兩者的尺寸大小相同,均為RGB圖像。在標簽圖像中,白色和黑色分別代表邊框和背景,而其他不同顏色代表不同的類別:

1.2 單通道掩膜
每個標簽的RGB值與各自的標注類別對應,則可以很容易地查找標簽中每個像素的類別索引,生成單通道掩膜Mask。如下面這種圖,標注類別包括:Person、Purse、Plants、Sidewalk、Building。將語義標簽圖轉換為單通道掩膜后為右圖所示,尺寸大小不變,但通道數由3變?yōu)?。

每個像素點位置一一對應。
1.3 Onehot編碼
Onehot作為一種編碼方式,可以對每一個單通道掩膜進行編碼。比如對于上述掩膜圖Mask,圖像尺寸為,標簽類別共有5類,我們需要將這個Mask變?yōu)橐粋€5個通道的Onehot輸出,尺寸為,也就是將掩膜中值全為1的像素點抽取出生成一個圖,相應位置置為1,其余為0。再將全為2的抽取出再生成一個圖,相應位置置為1,其余為0,以此類推。

02 語義分割實踐
接下來以Pascal VOC 2012語義分割數據集為例,介紹不同表達形式之間應該如何相互轉換。實踐采用的是Pascal VOC 2012語義分割數據集,它是語義分割任務中十分重要的數據集,有 20 類目標,這些目標包括人類、機動車類以及其他類,可用于目標類別或背景的分割。數據集開源地址:https://gas.graviti.cn/dataset/yluy/VOC2012Segmentation
2.1 數據集讀取
在使用之前先進行一些必要的準備工作:
Fork數據集:如果需要使用公開數據集,則需要將其先fork到自己的賬戶。 獲取AccessKey:獲取使用SDK與格物鈦數據平臺交互所需的密鑰,鏈接為https://gas.graviti.cn/tensorbay/developer 理解Segment:數據集的進一步劃分,如VOC數據集分成了“train”和“test”兩個部分。
import?os??
from?tensorbay?import?GAS??
from?tensorbay.dataset?import?Data,?Dataset??
from?tensorbay.label?import?InstanceMask,?SemanticMask??
from?PIL?import?Image??
import?numpy?as?np??
import?torchvision??
import?matplotlib.pyplot?as?plt??
ACCESS_KEY?=?"" ??
gas?=?GAS(ACCESS_KEY)??
def?read_voc_images(is_train=True,?index=0):??
????"""??
????read?voc?image?using?tensorbay??
????"""??
????dataset?=?Dataset("VOC2012Segmentation",?gas)??
????if?is_train:??
????????segment?=?dataset["train"]??
????else:??
????????segment?=?dataset["test"]??
????data?=?segment[index]??
????feature?=?Image.open(data.open()).convert("RGB")??
????label?=?Image.open(data.label.semantic_mask.open()).convert("RGB")??
????visualize(feature,?label)??
??????
????return?feature,?label??#?PIL?Image??
??
def?visualize(feature,?label):??
????"""??
????visualize?feature?and?label??
????"""??
????fig?=?plt.figure()??
????ax?=?fig.add_subplot(121)??#?第一個子圖??
????ax.imshow(feature)??
????ax2?=?fig.add_subplot(122)??#?第二個子圖??
????ax2.imshow(label)??
????plt.show()??
train_feature,?train_label?=?read_voc_images(is_train=True,?index=10)??
train_label?=?np.array(train_label)?#?(375,?500,?3)??

2.2 顏色映射表
在得到彩色語義標簽圖后,則可以構建一個顏色表映射,列出標簽中每個RGB顏色的值及其標注的類別。
def?colormap_voc():??
????"""??
????create?a?colormap??
????"""??
????colormap?=?[[0,?0,?0],?[128,?0,?0],?[0,?128,?0],?[128,?128,?0],??
????????????????????[0,?0,?128],?[128,?0,?128],?[0,?128,?128],?[128,?128,?128],??
????????????????????[64,?0,?0],?[192,?0,?0],?[64,?128,?0],?[192,?128,?0],??
????????????????????[64,?0,?128],?[192,?0,?128],?[64,?128,?128],?[192,?128,?128],??
????????????????????[0,?64,?0],?[128,?64,?0],?[0,?192,?0],?[128,?192,?0],??
????????????????????[0,?64,?128]]??
????classes?=?['background',?'aeroplane',?'bicycle',?'bird',?'boat',??
???????????????????'bottle',?'bus',?'car',?'cat',?'chair',?'cow',??
???????????????????'diningtable',?'dog',?'horse',?'motorbike',?'person',??
???????????????????'potted?plant',?'sheep',?'sofa',?'train',?'tv/monitor']??
??????
????return?colormap,?classes??
2.3 Label與Onehot轉換
根據映射表,實現語義標簽圖與Onehot編碼的相互轉換:
def?label_to_onehot(label,?colormap):??
????"""??
????Converts?a?segmentation?label?(H,?W,?C)?to?(H,?W,?K)?where?the?last?dim?is?a?one??
????hot?encoding?vector,?C?is?usually?1?or?3,?and?K?is?the?number?of?class.??
????"""??
????semantic_map?=?[]??
????for?colour?in?colormap:??
????????equality?=?np.equal(label,?colour)??
????????class_map?=?np.all(equality,?axis=-1)??
????????semantic_map.append(class_map)??
????semantic_map?=?np.stack(semantic_map,?axis=-1).astype(np.float32)??
????return?semantic_map??
def?onehot_to_label(semantic_map,?colormap):??
????"""??
????Converts?a?mask?(H,?W,?K)?to?(H,?W,?C)??
????"""??
????x?=?np.argmax(semantic_map,?axis=-1)??
????colour_codes?=?np.array(colormap)??
????label?=?np.uint8(colour_codes[x.astype(np.uint8)])??
????return?label??
colormap,?classes?=?colormap_voc()??
semantic_map?=?mask_to_onehot(train_label,?colormap)??
print(semantic_map.shape)??#?[H,?W,?K]?=?[375,?500,?21]?包括背景共21個類別??
label?=?onehot_to_label(semantic_map,?colormap)??
print(label.shape)?#?[H,?W,?K]?=?[375,?500,?3]??
2.4 Onehot與Mask轉換
同樣,借助映射表,實現單通道掩膜Mask與Onehot編碼的相互轉換:
def?onehot2mask(semantic_map):??
????"""??
????Converts?a?mask?(K,?H,?W)?to?(H,W)??
????"""??
????_mask?=?np.argmax(semantic_map,?axis=0).astype(np.uint8)??
????return?_mask??
def?mask2onehot(mask,?num_classes):??
????"""??
????Converts?a?segmentation?mask?(H,W)?to?(K,H,W)?where?the?last?dim?is?a?one??
????hot?encoding?vector??
????"""??
????semantic_map?=?[mask?==?i?for?i?in?range(num_classes)]??
????return?np.array(semantic_map).astype(np.uint8)??
mask?=?onehot2mask(semantic_map.transpose(2,0,1))??
print(np.unique(mask))?#?[?0??1?15]?索引相對應的是背景、飛機、人??
print(mask.shape)?#?(375,?500)??
semantic_map?=?mask2onehot(mask,?len(colormap))??
print(semantic_map.shape)?#?(21,?375,?500)
如果覺得有用,就請分享到朋友圈吧!
公眾號后臺回復“transformer”獲取最新Transformer綜述論文下載~

#?CV技術社群邀請函?#

備注:姓名-學校/公司-研究方向-城市(如:小極-北大-目標檢測-深圳)
即可申請加入極市目標檢測/圖像分割/工業(yè)檢測/人臉/醫(yī)學影像/3D/SLAM/自動駕駛/超分辨率/姿態(tài)估計/ReID/GAN/圖像增強/OCR/視頻理解等技術交流群
每月大咖直播分享、真實項目需求對接、求職內推、算法競賽、干貨資訊匯總、與?10000+來自港科大、北大、清華、中科院、CMU、騰訊、百度等名校名企視覺開發(fā)者互動交流~

