0
本文作者: 圖普科技 | 2017-08-24 09:55 |
雷鋒網(wǎng)按:本文由圖普科技編譯自《Image Augmentation for Deep Learning using Keras and Histogram Equalization》,雷鋒網(wǎng)獨(dú)家首發(fā)。
在這篇文章中,我們將要討論的內(nèi)容是:
什么是“圖像增強(qiáng)”?其重要性何在?
如何使用Keras實(shí)現(xiàn)基本的“圖像增強(qiáng)”?
什么是“直方圖均衡”?如何發(fā)揮其作用?
直方圖均衡法——修改keras.preprocessing image.py文件的方式之一
深度神經(jīng)網(wǎng)絡(luò),尤其是卷積神經(jīng)網(wǎng)絡(luò)(CNN),非常擅長(zhǎng)于圖像分類。事實(shí)證明,最先進(jìn)的卷積神經(jīng)網(wǎng)絡(luò)在圖像識(shí)別方面的性能已經(jīng)超過了人類水平。
https://www.eff.org/ai/metrics
然而,正如我們?cè)跅罱ㄏ壬摹癏ot Dog, Not Hot Dog”App(在一個(gè)叫做“Silicon Valley”的熱門電視節(jié)目中的食物識(shí)別App)中了解到的,將圖像收集起來作為訓(xùn)練數(shù)據(jù)使用,是一項(xiàng)非常昂貴且耗時(shí)的工作。
如果你對(duì)“Silicon Valley”這個(gè)電視節(jié)目不太熟悉,請(qǐng)注意以下視頻中的語言是NSFW:
我們通過擴(kuò)充圖像數(shù)據(jù)的方式,從一個(gè)已有的數(shù)據(jù)庫中生成更多新的訓(xùn)練圖像,以降低收集訓(xùn)練圖像的成本?!皥D像擴(kuò)充”其實(shí)就是從已有的訓(xùn)練數(shù)據(jù)集中取出一些圖像,然后根據(jù)這些圖像創(chuàng)建出許多修改版本的圖像。這樣做不僅能夠獲得更多的訓(xùn)練數(shù)據(jù),還能讓我們的分類器應(yīng)對(duì)光照和色彩更加復(fù)雜的環(huán)境,從而使我們的分類器功能越來越強(qiáng)大。以下是來自imgaug的不同的圖像擴(kuò)充例子:
https://github.com/aleju/imgaug
圖像預(yù)處理的方法有很多。在本文中,我們將討論一些常見的、富有創(chuàng)意的方法,這些方法也是Keras深度學(xué)習(xí)庫為擴(kuò)充圖像數(shù)據(jù)所提供的。之后我們將討論如何轉(zhuǎn)換keras預(yù)處理圖像文件,以啟用直方圖均衡法。我們將使用Keras附帶的cifar10數(shù)據(jù)集,但是為了使任務(wù)小到能夠順利在CPU上執(zhí)行,我們將只會(huì)使用其中的貓和狗的圖像。
首先,我們需要加載cifar10數(shù)據(jù)集并格式化其中的圖像,為卷積神經(jīng)網(wǎng)絡(luò)做好準(zhǔn)備。我們還要檢查一下部分圖像,確保數(shù)據(jù)已經(jīng)完成了正確的加載。
from __future__ import print_function
import keras
from keras.datasets import cifar10
from keras import backend as K
import matplotlib
from matplotlib import pyplot as plt
import numpy as np#Input image dimensions
img_rows, img_cols = 32, 32#The data, shuffled and split between train and test sets
(x_train, y_train), (x_test, y_test) = cifar10.load_data()#Only look at cats [=3] and dogs [=5]
train_picks = np.ravel(np.logical_or(y_train==3,y_train==5))
test_picks = np.ravel(np.logical_or(y_test==3,y_test==5))y_train = np.array(y_train[train_picks]==5,dtype=int)
y_test = np.array(y_test[test_picks]==5,dtype=int)x_train = x_train[train_picks]
x_test = x_test[test_picks]if K.image_data_format() == 'channels_first':
x_train = x_train.reshape(x_train.shape[0], 3, img_rows, img_cols)
x_test = x_test.reshape(x_test.shape[0], 3, img_rows, img_cols)
input_shape = (3, img_rows, img_cols)
else:
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 3)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 3)
input_shape = (img_rows, img_cols, 3)x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')#Convert class vectors to binary class matrices
y_train = keras.utils.to_categorical(np.ravel(y_train), num_classes)
y_test = keras.utils.to_categorical(np.ravel(y_test), num_classes)#Look at the first 9 images from the dataset
images = range(0,9)
for i in images:
plt.subplot(330 + 1 + i)
plt.imshow(x_train[i], cmap=pyplot.get_cmap('gray'))
#Show the plot
plt.show()
Cifar10數(shù)據(jù)集中的圖像都是32x 32像素大小的,因此放大來看,它們都呈現(xiàn)出顆粒狀。但是對(duì)卷積神經(jīng)網(wǎng)絡(luò)來說,它看到的不是顆粒,而是數(shù)據(jù)。
用Keras進(jìn)行圖像數(shù)據(jù)的擴(kuò)充是非常簡(jiǎn)單的,在這里,我們應(yīng)該感謝Jason Brownlee,因?yàn)槭撬o我們提供了一個(gè)非常全面、到位的Keras圖像擴(kuò)充教程。圖象擴(kuò)充的過程如下:首先,我們需要使用 ImageDataGenerator()函數(shù)來創(chuàng)建一個(gè)圖像生成器,并且輸入一系列描述圖像更改行為的參數(shù);之后,我們將在這個(gè)圖像生成器中執(zhí)行fit()函數(shù),它將會(huì)一批一批地對(duì)圖像進(jìn)行更改。在默認(rèn)情況下,圖像的更改是任意的,所以并不是所有圖像每次都會(huì)被更改。你還可以用 keras.preprocessing 函數(shù)將擴(kuò)充的圖像導(dǎo)出到一個(gè)文件夾,以便建立一個(gè)更龐大的擴(kuò)充圖像數(shù)據(jù)集。
在本文中,我們將看一些更直觀、有趣的擴(kuò)充圖像。你可以在Keras文件中查看所有的ImageDataGenerator參數(shù),以及keras.preprocessing中的其他方法。
# Rotate images by 90 degrees
datagen = ImageDataGenerator(rotation_range=90)# fit parameters from data
datagen.fit(x_train)# Configure batch size and retrieve one batch of images
for X_batch, y_batch in datagen.flow(x_train, y_train, batch_size=9):
# Show 9 images
for i in range(0, 9):
pyplot.subplot(330 + 1 + i)
pyplot.imshow(X_batch[i].reshape(img_rows, img_cols, 3))
# show the plot
pyplot.show()
break
# Flip images vertically
datagen = ImageDataGenerator(vertical_flip=True)# fit parameters from data
datagen.fit(x_train)# Configure batch size and retrieve one batch of images
for X_batch, y_batch in datagen.flow(x_train, y_train, batch_size=9):
# Show 9 images
for i in range(0, 9):
pyplot.subplot(330 + 1 + i)
pyplot.imshow(X_batch[i].reshape(img_rows, img_cols, 3))
# show the plot
pyplot.show()
break
水平翻轉(zhuǎn)圖片同樣是為分類器生成更多數(shù)據(jù)的一種經(jīng)典方式。這么做非常簡(jiǎn)單,但是我在這里省略了代碼和圖像,是因?yàn)槲覀冊(cè)跊]有看到原始圖像的情況下,無法判斷一張貓狗的圖像是否被水平翻轉(zhuǎn)了。
# Shift images vertically or horizontally
# Fill missing pixels with the color of the nearest pixel
datagen = ImageDataGenerator(width_shift_range=.2,
height_shift_range=.2,
fill_mode='nearest')# fit parameters from data
datagen.fit(x_train)# Configure batch size and retrieve one batch of images
for X_batch, y_batch in datagen.flow(x_train, y_train, batch_size=9):
# Show 9 images
for i in range(0, 9):
pyplot.subplot(330 + 1 + i)
pyplot.imshow(X_batch[i].reshape(img_rows, img_cols, 3))
# show the plot
pyplot.show()
直方圖均衡,即取一張低對(duì)比度圖像,并提高圖像中最亮和最暗部分之間的對(duì)比度,以找出陰影的細(xì)微差別,并創(chuàng)建一個(gè)更高對(duì)比度的圖像。使用這個(gè)方法所產(chǎn)生的結(jié)果相當(dāng)驚人,尤其是針對(duì)那些灰度圖像。以下是一些例子:
https://www.bruzed.com/2009/10/contrast-stretching-and-histogram-equalization/
http://www-classes.usc.edu/engr/ee-s/569/qa2/Histogram%20Equalization.htm
https://studentathome.wordpress.com/2013/03/27/local-histogram-equalization/
在本文中,我們將討論三種用于提高圖像對(duì)比度的圖像擴(kuò)充方法。這些方法有時(shí)也被稱作“直方圖拉伸”,因?yàn)樗鼈儠?huì)使用像素強(qiáng)度的分布,并擴(kuò)展這些分布以適應(yīng)更大范圍的值,從而提高圖像中最亮和最暗部分之間的對(duì)比度。
直方圖均衡
直方圖均衡法通過檢測(cè)圖像的像素強(qiáng)度分布,并繪制出一個(gè)像素強(qiáng)度直方圖,從而提高圖像的對(duì)比度。之后,這個(gè)直方圖的分布會(huì)被進(jìn)行分析,如果分析結(jié)果顯示還有未被利用的像素亮度范圍,那么這個(gè)直方圖就會(huì)被“擴(kuò)展”,以涵蓋這些未被利用的范圍。然后直方圖將被“投射”到圖像上,以提高圖像的整體對(duì)比度。
“對(duì)比度擴(kuò)展”的過程首先是分析圖像中的像素強(qiáng)度分布,然后重新調(diào)節(jié)圖像,使圖像能夠涵蓋在2%至98%之間的所有像素強(qiáng)度。
在直方圖計(jì)算方面,“自適應(yīng)均衡”與常規(guī)的直方圖均衡有很大的區(qū)別。常規(guī)的直方圖均衡法中,每個(gè)被計(jì)算的直方圖都與圖像中的一個(gè)部分相對(duì)應(yīng);但是,它有著在非正常圖像部分過度擴(kuò)充噪聲的趨勢(shì)。
下面的代碼來自于sci-kit圖像庫的文件。為了使這些代碼能夠在我們cifar10數(shù)據(jù)集的第一張圖像上執(zhí)行以上三種圖像擴(kuò)充,我們對(duì)代碼進(jìn)行了轉(zhuǎn)換和修改。首先,我們將輸入sic-kit圖像庫中的必要單元,然后對(duì)sci-kit圖像文件中的代碼進(jìn)行修改和調(diào)整,以便查看數(shù)據(jù)集第一張圖片的擴(kuò)充圖像集。
# Import skimage modules
from skimage import data, img_as_float
from skimage import exposure# Lets try augmenting a cifar10 image using these techniques
from skimage import data, img_as_float
from skimage import exposure# Load an example image from cifar10 dataset
img = images[0]# Set font size for images
matplotlib.rcParams['font.size'] = 8# Contrast stretching
p2, p98 = np.percentile(img, (2, 98))
img_rescale = exposure.rescale_intensity(img, in_range=(p2, p98))# Histogram Equalization
img_eq = exposure.equalize_hist(img)# Adaptive Equalization
img_adapteq = exposure.equalize_adapthist(img, clip_limit=0.03)#### Everything below here is just to create the plot/graphs ####
# Display results
fig = plt.figure(figsize=(8, 5))
axes = np.zeros((2, 4), dtype=np.object)
axes[0, 0] = fig.add_subplot(2, 4, 1)
for i in range(1, 4):
axes[0, i] = fig.add_subplot(2, 4, 1+i, sharex=axes[0,0], sharey=axes[0,0])
for i in range(0, 4):
axes[1, i] = fig.add_subplot(2, 4, 5+i)ax_img, ax_hist, ax_cdf = plot_img_and_hist(img, axes[:, 0])
ax_img.set_title('Low contrast image')y_min, y_max = ax_hist.get_ylim()
ax_hist.set_ylabel('Number of pixels')
ax_hist.set_yticks(np.linspace(0, y_max, 5))ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_rescale, axes[:, 1])
ax_img.set_title('Contrast stretching')ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_eq, axes[:, 2])
ax_img.set_title('Histogram equalization')ax_img, ax_hist, ax_cdf = plot_img_and_hist(img_adapteq, axes[:, 3])
ax_img.set_title('Adaptive equalization')ax_cdf.set_ylabel('Fraction of total intensity')
ax_cdf.set_yticks(np.linspace(0, 1, 5))# prevent overlap of y-axis labels
fig.tight_layout()
plt.show()
Here are the modified images of a low contrast cat from the cifar10 dataset. As you can see, the results are not as striking as they might be with a low contrast grayscale image, but still help improve the quality of the images.
下面這張圖是一張修改后的圖像,是由cifar10數(shù)據(jù)集中的一張對(duì)比度較低的貓咪圖片修改得到的。正如你所看到的,最后修改的圖像成果可能并不像在低對(duì)比度灰度圖像中得到的圖像成果那么令人驚艷,但總的來說圖像的畫質(zhì)還是得到了提高。
現(xiàn)在,我們已經(jīng)成功地修改了cifar10數(shù)據(jù)集中的一張圖像,我們接下來將要討論如何調(diào)整或改變keras.preprocessing圖像文件,從而執(zhí)行這些不同的直方圖修改方法,就像我們利用ImageDataGenerator()函數(shù)進(jìn)行keras圖像擴(kuò)充一樣。以下是我們將采取的幾個(gè)步驟:
找出keras.preprocessing image py文件
把image py文件復(fù)制到你的文件或者筆記本上。
給每個(gè)均衡方法添加一個(gè)屬性到ImageDataGenerator()init函數(shù)中。
把“IF”的表達(dá)語句添加到隨即轉(zhuǎn)換的方法中,這樣,我們?cè)谑褂胐atagenfit()函數(shù)的時(shí)候,圖像擴(kuò)充就會(huì)被執(zhí)行。
對(duì)keras.preprocessing的圖像py文件進(jìn)行修改和調(diào)整的最簡(jiǎn)單的方式之一就是將文件中的內(nèi)容復(fù)制、粘貼到我們的代碼中。這么做的好處是省略了我們下一個(gè)輸入文件內(nèi)容的步驟。你可以點(diǎn)擊此處查看github上的圖像文件。但是,為了確保你拿到的文件是之前輸入的文件的相同版本,你最好取你的機(jī)器上已有的圖像文件。
運(yùn)行print(keras._file_)將會(huì)打印出你機(jī)器上的keras庫的路徑,其路徑(針對(duì)IMac用戶)大致如下:
/usr/local/lib/python3.5/dist-packages/keras/__init__.pyc
這給我們提供了本機(jī)機(jī)器上的路徑,沿著路徑導(dǎo)航,然后進(jìn)入preprocessing文件夾;在preprocessing文件夾中你就會(huì)看到圖像py文件,你可以將其中的內(nèi)容復(fù)制到你的代碼中。這個(gè)文件有點(diǎn)長(zhǎng),但對(duì)于初學(xué)者來說,這應(yīng)該是最簡(jiǎn)單的方法了。
你可以在圖片頂部添加一行注釋:from..import backend as K
到這里,你還需要再次檢查,以確保你輸入的是必須的scikit-image單元,這樣復(fù)制的image.py才能識(shí)別出。
from skimage import data, img_as_float
from skimage import exposure
現(xiàn)在,我們需要給ImageDataGenerator類的方法添加六行代碼,這樣它就有三個(gè)屬性來表示我們將要添加的圖像擴(kuò)充類型。下面的代碼是從我現(xiàn)在的image.py中復(fù)制得來的:
def __init__(self,
contrast_stretching=False, #####
histogram_equalization=False,#####
adaptive_equalization=False, #####
featurewise_center=False,
samplewise_center=False,
featurewise_std_normalization=False,
samplewise_std_normalization=False,
zca_whitening=False,
rotation_range=0.,
width_shift_range=0.,
height_shift_range=0.,
shear_range=0.,
zoom_range=0.,
channel_shift_range=0.,
fill_mode=’nearest’,
cval=0.,
horizontal_flip=False,
vertical_flip=False,
rescale=None,
preprocessing_function=None,
data_format=None):
if data_format is None:
data_format = K.image_data_format()
self.counter = 0
self.contrast_stretching = contrast_stretching, #####
self.adaptive_equalization = adaptive_equalization #####
self.histogram_equalization = histogram_equalization #####
self.featurewise_center = featurewise_center
self.samplewise_center = samplewise_center
self.featurewise_std_normalization = featurewise_std_normalization
self.samplewise_std_normalization = samplewise_std_normalization
self.zca_whitening = zca_whitening
self.rotation_range = rotation_range
self.width_shift_range = width_shift_range
self.height_shift_range = height_shift_range
self.shear_range = shear_range
self.zoom_range = zoom_range
self.channel_shift_range = channel_shift_range
self.fill_mode = fill_mode
self.cval = cval
self.horizontal_flip = horizontal_flip
self.vertical_flip = vertical_flip
self.rescale = rescale
self.preprocessing_function = preprocessing_function
下面的random_transform()函數(shù)呼應(yīng)我們之前傳輸至ImageDataGenerator函數(shù)的參數(shù)。如果我們把“對(duì)比度擴(kuò)展”、“自適應(yīng)均衡”或“直方圖均衡”的參數(shù)設(shè)置為“True”,那么當(dāng)我們調(diào)用ImageDataGenerator函數(shù)的時(shí)候,random_transform()函數(shù)就會(huì)執(zhí)行所需的圖像擴(kuò)充。
def random_transform(self, x):
img_row_axis = self.row_axis - 1
img_col_axis = self.col_axis - 1
img_channel_axis = self.channel_axis - 1# use composition of homographies
# to generate final transform that needs to be applied
if self.rotation_range:
theta = np.pi / 180 * np.random.uniform(-self.rotation_range, self.rotation_range)
else:
theta = 0 if self.height_shift_range:
tx = np.random.uniform(-self.height_shift_range, self.height_shift_range) * x.shape[img_row_axis]
else:
tx = 0 if self.width_shift_range:
ty = np.random.uniform(-self.width_shift_range, self.width_shift_range) * x.shape[img_col_axis]
else:
ty = 0 if self.shear_range:
shear = np.random.uniform(-self.shear_range, self.shear_range)
else:
shear = 0 if self.zoom_range[0] == 1 and self.zoom_range[1] == 1:
zx, zy = 1, 1
else:
zx, zy = np.random.uniform(self.zoom_range[0], self.zoom_range[1], 2)transform_matrix = None
if theta != 0:
rotation_matrix = np.array([[np.cos(theta), -np.sin(theta), 0],
[np.sin(theta), np.cos(theta), 0],
[0, 0, 1]])
transform_matrix = rotation_matrix if tx != 0 or ty != 0:
shift_matrix = np.array([[1, 0, tx],
[0, 1, ty],
[0, 0, 1]])
transform_matrix = shift_matrix if transform_matrix is None else np.dot(transform_matrix, shift_matrix) if shear != 0:
shear_matrix = np.array([[1, -np.sin(shear), 0],
[0, np.cos(shear), 0],
[0, 0, 1]])
transform_matrix = shear_matrix if transform_matrix is None else np.dot(transform_matrix, shear_matrix) if zx != 1 or zy != 1:
zoom_matrix = np.array([[zx, 0, 0],
[0, zy, 0],
[0, 0, 1]])
transform_matrix = zoom_matrix if transform_matrix is None else np.dot(transform_matrix, zoom_matrix) if transform_matrix is not None:
h, w = x.shape[img_row_axis], x.shape[img_col_axis]
transform_matrix = transform_matrix_offset_center(transform_matrix, h, w)
x = apply_transform(x, transform_matrix, img_channel_axis,
fill_mode=self.fill_mode, cval=self.cval) if self.channel_shift_range != 0:
x = random_channel_shift(x, self.channel_shift_range, img_channel_axis) if self.horizontal_flip:
if np.random.random() < 0.5:
x = flip_axis(x, img_col_axis) if self.vertical_flip:
if np.random.random() < 0.5:
x = flip_axis(x, img_row_axis)
if self.contrast_stretching: #####
if np.random.random() < 0.5: #####
p2, p98 = np.percentile(x, (2, 98)) #####
x = exposure.rescale_intensity(x, in_range=(p2, p98)) #####
if self.adaptive_equalization: #####
if np.random.random() < 0.5: #####
x = exposure.equalize_adapthist(x, clip_limit=0.03) #####
if self.histogram_equalization: #####
if np.random.random() < 0.5: #####
x = exposure.equalize_hist(x) #####
return x
現(xiàn)在,所有必備的代碼都已經(jīng)準(zhǔn)備就緒了,那么我們就可以調(diào)用ImageDataGenerator()函數(shù)執(zhí)行直方圖修改的方法了。當(dāng)我們將所有的參數(shù)設(shè)置為True后,部分圖像就會(huì)變成這樣:
# Initialize Generator
datagen = ImageDataGenerator(contrast_stretching=True, adaptive_equalization=True, histogram_equalization=True)# fit parameters from data
datagen.fit(x_train)# Configure batch size and retrieve one batch of images
for x_batch, y_batch in datagen.flow(x_train, y_train, batch_size=9):
# Show the first 9 images
for i in range(0, 9):
pyplot.subplot(330 + 1 + i)
pyplot.imshow(x_batch[i].reshape(img_rows, img_cols, 3))
# show the plot
pyplot.show()
break
我不推薦在任何給定的數(shù)據(jù)集中將一個(gè)以上的參數(shù)設(shè)置為True,你需要確保你的數(shù)據(jù)集實(shí)驗(yàn)有助于你提高分類器的準(zhǔn)確性。對(duì)于彩色圖像,我發(fā)現(xiàn)“對(duì)比度擴(kuò)展”的成效優(yōu)于“直方圖修改”或“自適應(yīng)均衡”的成效。
最后一步,訓(xùn)練我們的卷積神經(jīng)網(wǎng)絡(luò),并使用 model.fit_generator() 函數(shù)驗(yàn)證這個(gè)模型,從而實(shí)現(xiàn)在擴(kuò)充圖像上的神經(jīng)網(wǎng)絡(luò)的訓(xùn)練和驗(yàn)證。
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Conv2D, MaxPooling2Dbatch_size = 64
num_classes = 2
epochs = 10model = Sequential()
model.add(Conv2D(4, kernel_size=(3, 3),activation='relu',input_shape=input_shape))
model.add(Conv2D(8, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(16, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(2, activation='softmax'))
model.compile(loss=keras.losses.categorical_crossentropy,
optimizer=keras.optimizers.Adadelta(),
metrics=['accuracy'])datagen.fit(x_train)
history = model.fit_generator(datagen.flow(x_train, y_train, batch_size=batch_size),
steps_per_epoch=x_train.shape[0] // batch_size,
epochs=20,
validation_data=(x_test, y_test))
雷峰網(wǎng)特約稿件,未經(jīng)授權(quán)禁止轉(zhuǎn)載。詳情見轉(zhuǎn)載須知。