【图像分类】基于Pascal VOC2012增强数据的多标签图像分类实战
接著上一次的多標簽分類綜述,本文主要以Pascal VOC2012增強數據集進行多標簽圖像分類訓練,詳細介紹增強數據集制作、訓練以及指標計算過程,并通過代碼進行詳細闡述,希望能為大家提供一定的幫助!
作者&編輯 | 郭冰洋
上一期多標簽圖像分類文章,也是本文的基礎,點擊可以閱讀:【技術綜述】多標簽圖像分類綜述
1 簡介
基于image-level的弱監督圖像語義分割大多數以傳統分類網絡作為基礎,從分類網絡中提取物體的位置信息,作為初始標注。
Pascal VOC2012的原始分割數據集僅包含1464個train圖片和1449張val圖片(共2913張),對于分類網絡來說其數據量過小。而benchmark_RELEASE分割數據集包括8498張train圖片和2857張val圖片(共11355張)。因此,許多論文中均選擇使用二者融合后的增強數據集。
近期在復現論文過程中發現,使用增強數據集進行多標簽分類時,某些圖片缺少對應的標記,需要對照原始Pascal VOC2012數據集的標注方法,重新獲取各類物體的標注信息,并完成多標簽分類任務以及相應的指標評價。現將相關細節和部分代碼進行解讀,以幫助大家理解多標簽分類的流程和相關注意事項。
2 Pascal VOC2012數據集介紹
Pascal VOC2012數據集包括五個文件夾:
1、Annotation:存放xml格式的標注信息
2、JPEGImages:存放所有圖片,包括訓練圖片和測試圖片
3、SegmentationClass:語義分割任務中用到的label圖片
4、SegmentationObject:實例分割任務用到的label圖片
5、ImageSets:存放每一種任務對應的數據,其又劃分為四個文件夾
(1) Action:存放人體動作的txt文件
(2) Layout:存放人體部位的txt文件
(3) Main:存放類別信息的txt文件
(4) Segmentation:存放分割訓練的txt文件
本次實戰是關于圖片多標簽分類任務的介紹,因此主要關注的為Annotation文件夾和ImageSets下的Main文件夾。
Main文件夾中包含了20類物體的訓練、驗證標簽文件,其命名格式為class_train.txt、class_trainval.txt或class_val.txt。其中,每個txt文件中均包含對應的標記信息,若圖中存在對應標簽,則為1,反之則為-1
3 benchmark_RELEAS數據集介紹
benchmark_RELEASE數據集包括兩個文件夾:
1、benchmark_code_RELEASE:相關評價指標的matlab文件
2、dataset:包括cls、img、inst三個文件夾和train.txt、val.txt兩個文件
(1) cls:語義分割的mat標注文件
(2) img:分割圖像
(3) inst:實例分割的mat標注文件
mat格式為matlab文件的一種,其中文件中主要包含了物體的類別、邊界、分割標注三類信息。
4 增強數據集介紹
所謂增強數據集,共包含兩個步驟:
1、將上述兩個數據集中的語義分割訓練數據進行融合并剔除重復部分。即將"/benchmark_RELEASE/dataset/"路徑下的train和val文件與"/ImageSets/Segmentation/"路徑下的train和val文件進行融合,獲取最終的train.txt和val.txt文件,共12031個數據(8829+3202)。代碼及注釋如下(為了清晰展示步驟,將函數拆分,直接進行了書寫):
import os
from os.path import join as pjoin
import collectionsimport numpy as np
# PascalVOC2012路徑
voc_path = '/VOC/VOCdevkit/VOC2012/'
# benchmark_RELEASE路徑
sbd_path = '/VOC/benchmark_RELEASE/'
# 構建內置字典,用于存放train、val、trainval數據
files = collections.defaultdict(list)
# 填充files
for split in ["train", "val", "trainval"]: ? ?
# 獲取原始txt文件 ? ?
????? path = pjoin(voc_path,?
? ? ? "ImageSets/Segmentation", split + ".txt")?
? ? # 以元組形式打開文件? ? ?
? ? ? file_list = tuple(open(path, "r")) ? ?
? ? # rstrip清除換行符號/n,并構成列表 ? ?
????? file_list = [id_.rstrip() for id_ in file_list] ? ?
? ? # 不同階段對應不同列表 ? ?
? ? ? files[split] = file_list
# benchmark_RELEASE的train文件獲取
path = pjoin(sbd_path, "dataset/train.txt")
sbd_train_list = tuple(open(path, "r"))
sbd_train_list = [id_.rstrip() for id_ in sbd_train_list]
# benchmark_RELEASE與Pascal VOC2012訓練數據融合
train_aug = files["train"] + sbd_train_list
# 清除重復數據
train_aug = [train_aug[i]?
for i in sorted(np.unique(train_aug,
return_index=True)[1])]
# 獲取最終train數據files["train_aug"] = train_aug
# benchmark_RELEASE的val文件獲取
path = pjoin(sbd_path, "dataset/val.txt")
sbd_val_list = tuple(open(path, "r"))
sbd_val_list = [id_.rstrip() for id_ in sbd_val_list]
# benchmark_RELEASE與Pascal VOC2012訓練數據融合
val_aug = files["val"] + sbd_val_list
# 清除重復數據
val_aug = [val_aug[i]?
for i in sorted(np.unique(val_aug, return_index=True)[1])]
# 清除val中與train數據重復的內容
set_diff = set(val_aug) - set(train_aug)
files["train_aug_val"] = list(set_diff)
2、將"/benchmark_RELEASE/dataset/cls"下mat格式的語義標簽解析成圖片,并與SegmentationClass文件夾下的圖片進行融合。此部分代碼可參考下述網址中的setup_annotation模塊。
https://github.com/meetshah1995/pytorchsemseg/blob/master/ptsemseg/loader/pascal_voc_loader.py
至此,增強數據集的train.txt、val.txt以及分割標注圖片均已獲得,可以愉快地用更大容量的數據集進行訓練啦!
5 標簽文件制作
前一小節主要介紹了Pascal VOC2012數據集的文件夾構成,在ImageSets/Main文件夾下包含了20類物體的標注文檔,包括train、val和trainval三種劃分。我們打開aeroplane_train.txt文檔可以看到,共有5717個訓練數據,每個圖像名稱后面均對應了1或者-1,其中1表示圖片中存在該類別的物體,-1則表示圖片中不存在該類別的物體。增強數據集的train.txt和val.txt文件并沒有各類別的標注信息,因此,我們需要仿照原有的格式,構建每個類別的標注文檔。
Annotation文件夾下包含了所有圖片標注信息的xml格式文件,其中<name>子項目下代表途中的類別信息。打開其中的一個xml文件我們可以看到,一個圖中包含了多個類別信息,其中還有重復項,即圖中存在相同類別的物體。我的思路是遍歷train.txt和val.txt文檔中每個圖片對應的xml文件,獲取其中的類別信息,然后判定類別信息是否包含當前類別,若包含則賦值1,反之賦值-1。對20個類別進行循環后,即可獲得相應的標注文檔。
接下來我將以訓練標注文檔的制作為展示,拆分步驟并結合代碼進行詳細的描述。
步驟1:讀取train.txt文件獲取訓練圖片
# 獲取訓練txt文件
def _GetImageSet(): ? ?
????????# txt路徑 ??
????????image_set_path = '/train.txt' ? ?
????????with open(image_set_path, 'r') as f: ? ? ? ?
? ? ? ? ? ? return [line.split()[0]?for line in f.readlines()]
# 訓練圖片合集
img_set = _GetImageSet()
步驟2:讀取對應的xml文件
# xml標注文件路徑?
annotation='/VOC/VOCdevkit/VOC2012/Annotations'?
# 構建xml列表?
xml = []?
for img in img_set: ? ?
????????xml_path = os.path.join(annotation,img + '.xml') ??
????????xml.append(xml_path)
步驟3:根據xml中的<name>項,判定圖片中是否存在該類別。讀取<name>項之后,一定通過set()函數,清除其中的重復類別名稱,否則會出現標簽重復的情況
# 類別?
VOC_CLASSES = ['aeroplane', 'bicycle', 'bird', 'boat','bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse','motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor']?
for x in xml: ? ?
? ? # 獲取每個name的地址? ? ?
? ? elem_list = minidom.parse(x).
??????????????????????getElementsByTagName('name')
? ? name = []? ??
? ? # 讀取每個地址的內容 ? ?
? ? ?for elem in elem_list: ? ? ? ??
????????????????cat_name = elem.firstChild.data ? ? ? ??
????????????????# 獲取name ? ? ? ??
????????????????name.append(cat_name) ? ?
????????# 刪除重復標記 ? ?
????????name = list(set(name)) ? ?
????????# 根據類別寫入標簽文件 ? ?
????????for cls in VOC_CLASSES: ? ? ? ?
????????????????txt = '/gt/%s_train.txt' % cls ? ? ? ?
????????????????if cls in name: ? ? ? ? ? ?
????????????????????????file_write_obj = open(txt, 'a') ? ? ? ? ? ?
????????????????????????gt = x[-15:-4] + ' ' +' '+ '1' ? ? ? ? ? ?
????????????????????????file_write_obj.writelines(gt) ? ? ? ? ? ?
????????????????????????file_write_obj.write('\n') ? ? ? ?
????????????????else: ? ? ? ? ? ?
????????????????????????file_write_obj = open(txt, 'a') ? ? ? ? ? ?
????????????????????????gt = x[-15:-4] + ' ' ?+ '-1' ? ? ? ? ? ?
????????????????????????file_write_obj.writelines(gt) ? ? ? ? ??
????????????????????????file_write_obj.write('\n')
通過以上三個步驟,就可以生成train.txt在20個類別下的標注文檔。標簽文件的制作是為了后續計算相應的評價指標,以更好的評價分類網絡的性能。
6 增強數據集多標簽文件制作
根據標簽文件的制作,我們已經獲取圖片在每個類別下對應標簽,如何將其轉化成對應的矩陣形式,是我們的下一步工作。
在多標簽分類任務中,我們可以構建一個1x20的矩陣作為圖片的標簽,其中對應的類別若存在,則置1,反之則置0。例如,如果圖片中含有aeroplane和bicycle兩個類別,其對應的標簽矩陣應該為[1,1,......,0,0]。同樣的,我們仍然可以根據xml文件信息,進行矩陣的搭建。
在本節中,我仍將通過步驟拆分,結合代碼展示這一過程。
準備工作:設置文件夾名稱,類別信息名稱及其對應的數字
# 圖片文件夾?
IMG_FOLDER_NAME = "JPEGImages"?
# 標簽文件夾?
ANNOT_FOLDER_NAME = "Annotations"?
# 標簽名稱(不含背景)?
CAT_LIST = ['aeroplane', 'bicycle', 'bird', 'boat','bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', 'horse','motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor']?
# 標簽轉換為數字?
CAT_NAME_TO_NUM = /
????dict(zip(CAT_LIST,range(len(CAT_LIST))))
步驟1:構建單張圖片對應的標簽矩陣
# 從xml文件中讀取圖片標簽?
def load_image_label_from_xml(img, voc12_root): ? ?
????????# 獲取xml中的name項? ? ??
????????el_list=minidom.parse(os.path.join(voc12_root,
? ? ? ? ANNOT_FOLDER_NAME,img + '.xml'))
????? ? .getElementsByTagName('name')? ??
????????# 構建標簽空矩陣 ? ?
????????multi_cls_lab = np.zeros((20), np.float32) ??
????????# 對xml中的name項進行操作 ? ?
????????for el in el_list: ? ? ? ?
????????????????# 讀取name ? ? ? ?
????????????????cat_name = el.firstChild.data ? ? ? ?
????????????????if cat_name in CAT_LIST: ? ? ? ? ??
????????????????# 轉換為數字標簽 ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? cat_num =/
????????????????????????CAT_NAME_TO_NUM[cat_name] ? ? ? ? ? ?
????????????????????????# 將標簽矩陣中對應的位置賦1 ? ? ? ? ? ?
? ? ? ? ? ? ? ? ? ? multi_cls_lab[cat_num] = 1.0 ? ?
????????# 返回標簽矩陣 ? ?
????????return multi_cls_lab
步驟2:遍歷所有的圖片,生成對應的標簽矩陣
# 從.txt文件中載入所有xml文件對應的標簽?
def load_image_label_list_from_xml(img_name_list, voc12_root): ? ?
? ? ? ? # 返回所有標簽矩陣 ? ?
? ? ? ?return [load_image_label_from_xml(img_name, voc12_root) for img_name in img_name_list]
步驟3:生成含有所有標簽矩陣的npy文件
# 加載圖片list?
def load_img_name_list(dataset_path): ? ?
????????# 獲取.txt文件中的圖片(含png和jpg,以及路徑文件)??
????????img_gt_name_list = /
????????????open(dataset_path).read().splitlines() ? ?
????????# 讀取圖片名字 ? ?
????????img_name_list = /
??????? [img_gt_name.split(' ')[0][-15:-4]?
????????for img_gt_name in img_gt_name_list]? ? ?
????????# 返回值 ??
????????return img_name_list?
# 獲取訓練圖片列表?
img_name_list = load_img_name_list(args.train_list)?
# 獲取標簽列表?
label_list=load_image_label_list_from_xml(img_name_list,args.voc12_root)?
# 通過字典保存圖片及其對應的標簽?
d = dict()?
for img, lbl in zip(img_name_list, label_list):? ??
????d[img_name] = label?
# 保存文件?
np.save(args.out, d)
至此,所有的標簽矩陣便構建完成了。
7 評價指標計算
多標簽圖像分類網絡的性能需要根據平均準確率精度(mAP)來進行分析,而平均精度準確率均值需要先對每個類別的平均準確率進行計算。
results =?
{‘aeroplane’:{‘2007_000032’:[0.7,0.8,......0.9],
????????????????????????......
????????????????????????'2011_003276':[1.2,0.8,......0.3]}
????......
? 'tvmonitor':{‘2007_000032’:[0.1,-0.8,......0.2],
????????????????????????......
????????????????????????'2011_003276':[1.1,0.4,......0.8]}}
隨后我們載入每個圖像對應的類別標簽,具體形式如下:
ground_truth =?
{‘aeroplane’:{‘2007_000032’:[0,1,......0],
????????????????????????......
????????????????????????'2011_003276':[1,0,......1]}
????......
? 'tvmonitor':{‘2007_000032’:[1,0,......0],
????????????????????????......
????????????????????????'2011_003276':[1,0,......1]}}
通過上述兩個集合,我們可以分別計算每個類別的平均準確率,計算平均準確率的方法Pascal VOC官方已經給出,可以參照具體標準進行計算。具體代碼如下:
# 每個類別的計算
def EvaluateClass(self, cls, cls_results):
計算出每個類別的平均準確率后,則對所有類別的平均準確率求均值即可求得mAP值,在python代碼中可以直接使用mean函數實現。
8 訓練細節
在進行訓練前需要注意一點,數據讀取時我們需要同時獲取圖片名字、圖片、標簽三個信息,也是為了后續的評價指標計算做基礎,這一點與傳統單標簽分類只讀取圖片和標簽的方法不同,需要格外注意。讀取數據集的代碼在此不再拆分詳細解釋。
本文以Pytorch框架進行編寫,進行了兩種策略的訓練方式。
1、選擇ModelA1作為訓練網絡(即resnet38),并使用對應的預訓練數據,同時將全連接層轉換為卷積層,學習率設置為0.01,batch_size為4,損失函數選用hanming loss,采用SGD優化,在AMD 2600X + GTX 1070Ti搭建的平臺,訓練了約30個小時。
2、選擇Resnet50作為訓練網絡,同時將全連接層轉換為卷積層,學習率設置為0.01,batch_size為16,損失函數選用hanming loss,采用SGD優化,在AMD 2600X + GTX 1070Ti搭建的平臺,訓練了約2個小時。
9 結果分析
通過訓練我們發現,ModelA1取得的最優準確率為91.8%,Resnet50取得的最優準確率為90.3%,故此次結果分析暫時以ModelA1為準。
1、平均準確率均值(mAP)
2、各類別平均準確率曲線
3、每個類別的最高準確率
總結
以上就是整個多標簽圖像分類實戰的過程,由于時間限制,本次實戰并沒有進行詳細的調參工作,因此準確率還有一定的提升空間。
有三AI夏季劃
有三AI夏季劃進行中,歡迎了解并加入,系統性成長為中級CV算法工程師。
轉載文章請后臺聯系
侵權必究
往期精選
【技術綜述】多標簽圖像分類綜述
【技術綜述】你真的了解圖像分類嗎?
【AI-1000問】為什么深度學習圖像分類的輸入多是224*224
總結
以上是生活随笔為你收集整理的【图像分类】基于Pascal VOC2012增强数据的多标签图像分类实战的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【知识星球】Attention网络结构上
- 下一篇: 【知识星球】数据集板块重磅发布,海量数据