論文筆記:圖像數據增強之彈性形變(Elastic Distortions)

前言

  • 我們都知道,深度學習的成功的原因主要有兩點:
  1. 當前計算機的計算能力有很大提升;
  2. 隨著大數據時代的到來,當前的訓練樣本數目有很大的提升。
  • 然而深度學習的一大問題是,有的問題並沒有大量的訓練數據,而由於深度神經網絡具有非常強的學習能力,如果沒有大量的訓練數據,會造成過擬合,訓練出的模型難以應用。因此對於一些沒有足夠樣本數量的問題,可以通過已有的樣本,對其進行變化,人工增加訓練樣本。
  • 對於圖像而言,常用的增加訓練樣本的方法主要有對圖像進行旋轉、移位等仿射變換,也可以使用鏡像變換等等,這裡介紹一種常用於字符樣本上的變換方法,彈性變換算法(Elastic Distortion)
  • 該算法最先是由Patrice等人在2003年的ICDAR上發表的《Best Practices for Convolutional Neural Networks Applied to Visual Document Analysis》。本文主要是對論文中提出的彈性形變數據增強方法進行解讀。

插播一下雙線性插值的定義

  • 雙線性插值,顧名思義就是兩個方向的線性插值加起來(這解釋過於簡單粗暴,哈哈)。所以只要瞭解什麼是線性插值,分別在x軸和y軸都做一遍,就是雙線性插值了。
  • 線性插值:兩個點A,B,要在AB中間插入一個點C(點C座標在AB連線上),就直接讓C的值落在AB的值的連線上就可以了。如A點座標(0,0),值為3,B點座標(0,2),值為5,那要對座標為(0,1)的點C進行插值,就讓C落在AB線上,值為4就可以了。
  • 但是如果C不在AB的線上腫麼辦捏,所以就有了雙線性插值。如下圖,已知Q12,Q22,Q11,Q21,但是要插值的點為P點,這就要用雙線性插值了,首先在x軸方向上,對R1和R2兩個點進行插值,這個很簡單,然後根據R1和R2對P點進行插值,這就是所謂的雙線性插值。
論文筆記:圖像數據增強之彈性形變(Elastic Distortions)


1、仿射變換

  • 仿射變換是最常用的空間座標變換的方法之一,具體定義可參考岡薩雷斯的《數字圖像處理第三版》50頁。論文中是如下解釋仿射變換的:
  • 將仿射變換應用於圖像,新像素的位置是由原始位置確定的,Δx(x,y)=1,Δy(x,y)=0代表向右移一個單位,Δx(x,y)= αx, Δy(x,y)= αy代表像素點由原點位置進行縮放。
  • 上面說明了如何計算變換之後每個像素點的座標,下圖說明了如何應用位移字段來計算每個像素的新值(其實就是雙線性插值的方法):
論文筆記:圖像數據增強之彈性形變(Elastic Distortions)


  • 假設A是原點(0,0),而數字3,7,5,9是圖像要轉換的灰度等級,座標分別為(1,0),(2,0),(1,-1),(1,-2),A的位移由Δx(0,0) = 1.75 and Δy(0,0) = -0.5給出,如箭頭所示。通過評估原始圖像的位置(1.75,-0.5)處的灰度級來計算新(扭曲)圖像中的A的新灰度值。用於評估灰度級的簡單算法是原始圖像的像素值進行“線性插值”。儘管可以使用其他插值方案(例如,雙三次和B樣條插值),但雙線性插值是最簡單的插值方法之一,並且適用於以所選分辨率(29×29)生成附加的扭曲字符圖像。
  • 先水平插值,然後垂直插值,完成評估。箭頭結束的位置在3,5,7,9的方格內,這樣我們先計算箭頭相對於它結束的方格的座標。在這種情況下,它相對於正方形方格中的座標是(0.75,0.5),假設該正方形的原點是左下角(也就是灰度值為5的點)。在此示例中,水平插值為:3 +0.75×(7-3)= 6;垂直插值為:8 +0.5×(6-8)= 7,因此A的新像素值為7.
  • 對所有像素都進行了類似的計算。在給定圖像之外的所有像素位置都假定有一個灰度值。

2、彈性形變

  • 仿射變換改善了在MNIST數據集上的實驗結果,但是實驗在彈性形變後的數據集上取得了最好的結果。
  • 那麼什麼是彈性形變呢?
  • 首先創建隨機位移場來使圖像變形,即Δx(x,y) = rand(-1,+1)、Δy(x,y)=rand(-1,+1),其中rand(-1,+1)是生成一個在(-1,1)之間均勻分佈的隨機數,然後用標準差為σ的高斯函數對Δx和Δy進行卷積,如果σ值很大,則結果值很小,因為隨機值平均為0.如果我們將位移場標準化(達到1的範數),則該字段接近常數,具有隨機方向。
  • 如果σ很小,則歸一化後該字段看起來像一個完全隨機的字段(如圖2右上角所示)。
  • 對於中間σ值,位移場看起來像彈性變形,其中σ是彈性係數。然後將位移場乘以控制變形強度的比例因子α。 在我們的MNIST實驗(29x29輸入圖像)中,產生最佳結果的值是σ = 4和α= 34。
  • 將經過高斯卷積的位移場乘以控制變形強度的比例因子α,得到一個彈性形變的位移場,最後將這個位移場作用在仿射變換之後的圖像上,得到最終彈性形變增強的數據。作用的過程相當於在仿射圖像上插值的過程,最後返回插值之後的結果。
  • 關於高斯卷積的原理可以參考這篇文章:高斯卷積濾波
  • 如果文章看完文章,還是不太懂彈性形變數據增強的原理的話,可以結合代碼一起看,下面是參考代碼,我都有註釋。

3、參考代碼

# -*- coding:utf-8 -*-
"""
@author:TanQingBo
@file:elastic_transform.py
@time:2018/10/1221:56
"""
# Import stuff
import os
import numpy as np
import pandas as pd
import cv2
from scipy.ndimage.interpolation import map_coordinates
from scipy.ndimage.filters import gaussian_filter
import matplotlib.pyplot as plt
# Function to distort image alpha = im_merge.shape[1]*2、sigma=im_merge.shape[1]*0.08、alpha_affine=sigma
def elastic_transform(image, alpha, sigma, alpha_affine, random_state=None):
"""Elastic deformation of images as described in [Simard2003]_ (with modifications).
.. [Simard2003] Simard, Steinkraus and Platt, "Best Practices for
Convolutional Neural Networks applied to Visual Document Analysis", in
Proc. of the International Conference on Document Analysis and
Recognition, 2003.
Based on https://gist.github.com/erniejunior/601cdf56d2b424757de5
"""
if random_state is None:
random_state = np.random.RandomState(None)
shape = image.shape
shape_size = shape[:2] #(512,512)表示圖像的尺寸
# Random affine
center_square = np.float32(shape_size) // 2
square_size = min(shape_size) // 3
# pts1為變換前的座標,pts2為變換後的座標,範圍為什麼是center_square+-square_size?

# 其中center_square是圖像的中心,square_size=512//3=170
pts1 = np.float32([center_square + square_size, [center_square[0] + square_size, center_square[1] - square_size],
center_square - square_size])
pts2 = pts1 + random_state.uniform(-alpha_affine, alpha_affine, size=pts1.shape).astype(np.float32)
# Mat getAffineTransform(InputArray src, InputArray dst) src表示輸入的三個點,dst表示輸出的三個點,獲取變換矩陣M
M = cv2.getAffineTransform(pts1, pts2) #獲取變換矩陣
#默認使用 雙線性插值,
image = cv2.warpAffine(image, M, shape_size[::-1], borderMode=cv2.BORDER_REFLECT_101)
# # random_state.rand(*shape) 會產生一個和 shape 一樣打的服從[0,1]均勻分佈的矩陣
# * 2 - 1 是為了將分佈平移到 [-1, 1] 的區間
# 對random_state.rand(*shape)做高斯卷積,沒有對圖像做高斯卷積,為什麼?因為論文上這樣操作的
# 高斯卷積原理可參考:https://blog.csdn.net/sunmc1204953974/article/details/50634652
# 實際上 dx 和 dy 就是在計算論文中彈性變換的那三步:產生一個隨機的位移,將卷積核作用在上面,用 alpha 決定尺度的大小
dx = gaussian_filter((random_state.rand(*shape) * 2 - 1), sigma) * alpha
dy = gaussian_filter((random_state.rand(*shape) * 2 - 1), sigma) * alpha
dz = np.zeros_like(dx) #構造一個尺寸與dx相同的O矩陣
# np.meshgrid 生成網格點座標矩陣,並在生成的網格點座標矩陣上加上剛剛的到的dx dy
x, y, z = np.meshgrid(np.arange(shape[1]), np.arange(shape[0]), np.arange(shape[2])) #網格採樣點函數
indices = np.reshape(y + dy, (-1, 1)), np.reshape(x + dx, (-1, 1)), np.reshape(z, (-1, 1))
# indices = np.reshape(y+dy, (-1, 1)), np.reshape(x+dx, (-1, 1)), np.reshape(z, (-1, 1))
return map_coordinates(image, indices, order=1, mode='reflect').reshape(shape)
# Define function to draw a grid
def draw_grid(im, grid_size):
# Draw grid lines
for i in range(0, im.shape[1], grid_size):
cv2.line(im, (i, 0), (i, im.shape[0]), color=(255,))
for j in range(0, im.shape[0], grid_size):
cv2.line(im, (0, j), (im.shape[1], j), color=(255,))

if __name__ == '__main__':
img_path = 'E:/liverdata/nii/png/img'
mask_path = 'E:/liverdata/nii/png/label'
# img_path = '/home/changzhang/ liubo_workspace/tmp_for_test/img'
# mask_path = '/home/changzhang/liubo_workspace/tmp_for_test/mask'
img_list = sorted(os.listdir(img_path))
mask_list = sorted(os.listdir(mask_path))
print(img_list)
img_num = len(img_list)
mask_num = len(mask_list)
assert img_num == mask_num, 'img nuimber is not equal to mask num.'
count_total = 0
for i in range(img_num):
print(os.path.join(img_path, img_list[i])) #將路徑和文件名合成一個整體
im = cv2.imread(os.path.join(img_path, img_list[i]), -1)
im_mask = cv2.imread(os.path.join(mask_path, mask_list[i]), -1)
# # Draw grid lines
# draw_grid(im, 50)
# draw_grid(im_mask, 50)
# Merge images into separete channels (shape will be (cols, rols, 2))
im_merge = np.concatenate((im[..., None], im_mask[..., None]), axis=2)
# get img and mask shortname
(img_shotname, img_extension) = os.path.splitext(img_list[i]) #將文件名和擴展名分開
(mask_shotname, mask_extension) = os.path.splitext(mask_list[i])
# Elastic deformation 10 times
count = 0
while count < 10:
# Apply transformation on image im_merge.shape[1]表示圖像中像素點的個數
im_merge_t = elastic_transform(im_merge, im_merge.shape[1] * 2, im_merge.shape[1] * 0.08,
im_merge.shape[1] * 0.08)
# Split image and mask
im_t = im_merge_t[..., 0]
im_mask_t = im_merge_t[..., 1]
# save the new imgs and masks
cv2.imwrite(os.path.join(img_path, img_shotname + '-' + str(count) + img_extension), im_t)
cv2.imwrite(os.path.join(mask_path, mask_shotname + '-' + str(count) + mask_extension), im_mask_t)
count += 1
count_total += 1
if count_total % 100 == 0:
print('Elastic deformation generated {} imgs', format(count_total))
# # Display result
# print 'Display result'
# plt.figure(figsize = (16,14))
# plt.imshow(np.c_[np.r_[im, im_mask], np.r_[im_t, im_mask_t]], cmap='gray')
# plt.show()
  • 關於map_coordinates函數原理的參考文章:Python/Scipy插值(map_coordinates)

參考

  • 論文原文:Best Practices for Convolutional Neural Networks Applied to Visual Document Analysis
  • MingChaoSun-CSDN:高斯卷積濾波
  • Python/Scipy插值(map_coordinates)

-----------------

1.回覆【圖書】:獲取15本新手自學編程,零基礎入門經典學習教材;

2.回覆【我要造輪子】:獲取100多本計算機類經典書籍;

3.回覆【開發工具】:獲取幾大主流編程語言的開發工具~

4.回覆【內推】:可幫你內推到大廠工作。

論文筆記:圖像數據增強之彈性形變(Elastic Distortions)


分享到:


相關文章: