支招 | 如何用 TensorLayer 做目標檢測的數據增強

  • 2019 年 10 月 5 日
  • 筆記

本文作者:@董豪,來自帝國理工學院,現已入駐AI研習社社區。歡迎掃描文末社區名片關注他的個人主頁,查看更多動態。

  • Python Can Be Fasttensorlayer.readthedocs.io(https://tensorlayer.readthedocs.io/en/latest/modules/prepro.html#python-can-be-fast)
  • 最新快速數據增強方法tensorlayer.readthedocs.io(https://tensorlayer.readthedocs.io/en/latest/modules/prepro.html#python-can-be-fast)

願回答的方法已經過時了,請看上面鏈接的方法。

更多新技術請掃描底部名片關注我的社區主頁 (https://www.yanxishe.com/center/myPage/5160264 )或知乎專欄:TensorLayer技術分享。

======== 以下是原回答 ========

數據增強在機器學習中的作用不言而喻。和圖片分類的數據增強不同,訓練目標檢測模型的數據增強在對影像做處理時,還需要對圖片中每個目標的坐標做相應的處理。此外,位移、裁剪等操作還有可能使得一些目標在處理後只有一小部分區域保留在原圖中,這需要額外的機制來判斷是否需要去掉該目標來訓練模型。為此TensorLayer 1.7.0發布中,提供了大量關於目標檢測任務的數據集下載、目標坐標處理、數據增強的API。最近的幾次發布主要面向新的卷積方式(Deformable Convolution, Depthwise ...),優化Subpixel Convolution以及提供新的遞歸方式(ConvLSTM)等。

首先,我們下載VOC2012數據集並對類別和坐標做預處理。tl.files.load_voc_dataset函數自動下載數據集,其返回的坐標格式和Darknet一樣,則[x_c, y_c, w,h],其中x_c和y_c代表一個目標的中心在圖片上的位置,w和h代表該目標的寬度和高度,這4個值是其和原圖高度和寬度的比例,所以這4個值的範圍在0~1之間。

tensorlayer tl      imgs_file_list, _, _, _, classes, _, _,      _, objs_info_list, _ = tl.files.load_voc_dataset(dataset="2012")      ann_list = []  info objs_info_list:      ann = tl.prepr      o.parse_darknet_ann_str_to_list(info)      c, b = tl.prepro.parse_darknet_ann_list_to_cls_box(ann)      ann_list.append([c, b])

單張圖片處理

我們先對一張圖片做處理,以觀察tl.prepro工具箱中各個API的效果。這裡我們保存2號圖片的原圖,以供後面做比較。

idx = 2  image = tl.vis.read_image(imgs_file_list[idx])  tl.vis.draw_boxes_and_labels_to_image(image, ann_list[idx][0],       ann_list[idx][1], [], classes, True, save_name='_im_original.png')

原圖 Original Image

im_flip, coords = tl.prepro.obj_box_left_right_flip(image,       ann_list[idx][1], is_rescale=, is_center=, is_random=)  tl.vis.draw_boxes_and_labels_to_image(im_flip, ann_list[idx][0],       coords, [], classes, , save_name='_im_flip.png')

左右翻轉 Flip

# 位移  im_shfit, clas, coords = tl.prepro.obj_box_shift(image, ann_list[idx][0],           ann_list[idx][1], wrg=0.1, hrg=0.1,           is_rescale=True, is_center=True, is_random=False)tl.vis.draw_boxes_and_labels_to_image(im_shfit, clas, coords, [],           classes, True, save_name='_im_shift.png')

位移 Shift

im_zoom, clas, coords = tl.prepro.obj_box_zoom(image, ann_list[idx][0],           ann_list[idx][1], zoom_range=(1.3, 0.7),           is_rescale=, is_center=, is_random=)  tl.vis.draw_boxes_and_labels_to_image(im_zoom, clas, coords, [],           classes, , save_name='_im_zoom.png')

高寬縮放 Zoom

從縮放的圖片中,我們可以看到一架飛機由於大部分區域被移到影像之外了,只剩下機頭的一小部分,所以這個目標被去除了。tl.prepro工具箱中關於目標檢測的API往往有thresh_wh和thresh_wh2兩個閥值,thresh_wh表示在處理影像之後,若一個目標的寬或高和圖片本身寬高的比例小於這個值,則去除該目標;thresh_wh2表示在處理影像之後,若一個目標的寬高或高寬比例小於這個值,則去除該目標。大家可以根據特定開發任務來設置這兩個值,作者建議在常規情況下使用默認值。

im_resize, coords = tl.prepro.obj_box_imresize(image,          coords=ann_list[idx][1], size=[300, 200], is_rescale=)  tl.vis.draw_boxes_and_labels_to_image(im_resize, ann_list[idx][0],          coords, [], classes, , save_name='_im_resize.png')

調整大小

# 裁剪  im_crop, clas, coords = tl.prepro.obj_box_crop(image, ann_list[idx][0],           ann_list[idx][1], wrg=200, hrg=200,           is_rescale=, is_center=, is_random=)  tl.vis.draw_boxes_and_labels_to_image(im_crop, clas, coords, [],           classes, , save_name='_im_crop.png')

裁剪 Crop

多執行緒處理

實際訓練模型時,我們可能會使用多執行緒方法來對一個batch的圖片做隨機的數據增強。這時,tl.prepro工具箱的API中is_random全部設為True。

import tensorlayer as tl  import random    batch_size = 64  im_size = [416, 416]   # 輸出圖的大小  n_data = len(imgs_file_list)  jitter = 0.2  def _data_pre_aug_fn(data):      im, ann = data      clas, coords = ann      ## 隨機改變圖片亮度、對比度和飽和度      im = tl.prepro.illumination(im, gamma=(0.5, 1.5),               contrast=(0.5, 1.5), saturation=(0.5, 1.5), is_random=True)      ## 隨機左右翻轉      im, coords = tl.prepro.obj_box_left_right_flip(im, coords,               is_rescale=True, is_center=True, is_random=True)      ## 隨機調整大小並裁剪出指定大小的圖片,這同時達到了隨機縮放的效果      tmp0 = random.randint(1, int(im_size[0]*jitter))      tmp1 = random.randint(1, int(im_size[1]*jitter))      im, coords = tl.prepro.obj_box_imresize(im, coords,              [im_size[0]+tmp0, im_size[1]+tmp1], is_rescale=True,               interp='bicubic')      im, clas, coords = tl.prepro.obj_box_crop(im, clas, coords,               wrg=im_size[1], hrg=im_size[0], is_rescale=True,               is_center=True, is_random=True)      ## 把數值範圍從 [0, 255] 轉到 [-1, 1] (可選)      im = im / 127.5 - 1      return im, [clas, coords]    # 隨機讀取一個batch的圖片及其標記  idexs = tl.utils.get_random_int(min=0, max=n_data-1, number=batch_size)  b_im_path = [imgs_file_list[i] for i in idexs]  b_images = tl.prepro.threading_data(b_im_path, fn=tl.vis.read_image)  b_ann = [ann_list[i] for i in idexs]    # 多執行緒處理  data = tl.prepro.threading_data([_ for _ in zip(b_images, b_ann)],                _data_pre_aug_fn)  b_images2 = [d[0] for d in data]  b_ann = [d[1] for d in data]    # 保存每一組圖片以供體會  for i in range(len(b_images)):      tl.vis.draw_boxes_and_labels_to_image(b_images[i],               ann_list[idexs[i]][0], ann_list[idexs[i]][1], [],               classes, True, save_name='_bbox_vis_%d_original.png' % i)      tl.vis.draw_boxes_and_labels_to_image((b_images2[i]+1)*127.5,               b_ann[i][0], b_ann[i][1], [], classes, True,               save_name='_bbox_vis_%d.png' % i)

最後,我們得到64組處理前和處理後的圖片,下面列出2組圖片以供參考。

原圖

隨機處理後

原圖

隨機處理後

處理邏輯

這就完了嗎?大家認真思考一下上面的 _data_pre_aug_fn 函數做數據增強有什麼潛在缺點?假設我們的訓練影像高寬非常不確定的話,比如有的圖是300x1000而有的圖是1000x300,上面的函數一上來就把圖片resize到一個正方形,會導致很多形狀高寬資訊丟失!

當我們的數據集存在高寬比例多樣性很大時,我們需要另外的機制來解決這個問題。下面的函數中,我們的resize會根據原圖高寬來決定,我們把原圖最小的那個邊resize成最終尺寸對應需要的大小,同時另外一個邊以同比例resize(比如,如果原圖高比寬小,則把高resize成最終需要的高,同時寬以相同比例resize)。做完這一步之後,我們再對其進行隨機左右翻轉,縮放等操作,最終裁剪出我們需要的尺寸的圖。

def _data_aug_fn(self, data, jitter):      im, ann = data      clas, coords = ann      ## resize到高寬合適的大小      scale = np.max((self.im_size[1] / float(im.shape[1]),              self.im_size[0] / float(im.shape[0])))      im, coords = tl.prepro.obj_box_imresize(im, coords,              [int(im.shape[0]*scale)+2, int(im.shape[1]*scale)+2],              is_rescale=True, interp='bicubic')      ## 幾何增強 geometric transformation      im, coords = tl.prepro.obj_box_left_right_flip(im,              coords, is_rescale=True, is_center=True, is_random=True)      im, clas, coords = tl.prepro.obj_box_shift(im, clas,              coords, wrg=0.1, hrg=0.1, is_rescale=True,              is_center=True, is_random=True)      im, clas, coords = tl.prepro.obj_box_zoom(im, clas,              coords, zoom_range=(1-jitter, 1+jitter),              is_rescale=True, is_center=True, is_random=True)      im, clas, coords = tl.prepro.obj_box_crop(im, clas, coords,              wrg=self.im_size[1], hrg=self.im_size[0],              is_rescale=True, is_center=True, is_random=True)      ## 光度增強 photometric transformation      im = tl.prepro.illumination(im, gamma=(0.5, 1.5),              contrast=(0.5, 1.5), saturation=(0.5, 1.5), is_random=True)      im = tl.prepro.adjust_hue(im, hout=0.1, is_offset=True,              is_clip=True, is_random=True)      im = tl.prepro.pixel_value_scale(im, 0.1, [0, 255], is_random=True)      ## 把數值範圍從 [0, 255] 轉到 [-1, 1] (可選)      im = im / 127.5 - 1.      im = np.clip(im, -1., 1.)      return im, [clas, coords]

原圖

隨機處理後

原圖

隨機處理後

原圖

隨機處理後

處理前

隨機處理後

更新

新版本的TensorFlow發布了dataset API,自帶threading功能,大家可以到下面鏈接獲取程式碼。

TensorLayer結合Dataset API生成VOC:

https://github.com/tensorlayer/tensorlayer/blob/master/example/tutorial_tf_dataset_voc.py

結束語

對於產業界的朋友來說,數據增強的邏輯和業務本身是非常相關的,我們需要對不同的數據集寫不同的增強程式碼,合理的增強邏輯往往會在相同的演算法上大大提高準確性。各位還可以仔細思考一下crop和shift, zoom之間的先後問題會對圖片有什麼影響。TensorLayer把每一種增強行為都獨立開來,以便大家完全可控地實現自己的增強演算法邏輯。

Tags: