如何用TensorFlow和TF-Slim實現圖像分類與分割

jackwang 8年前發布 | 62K 次閱讀 機器學習 TensorFlow

本文將介紹如何用近日發布的TF-Slim工具包和預訓練的模型來完成圖像分類和圖像分割。

引言

筆者將和大家分享一個結合了TensorFlow和 最近發布 的slim庫的小應用,來實現圖像分類、圖像標注以及圖像分割的任務,圍繞著slim展開,包括其理論知識和應用場景。

之前自己嘗試過許多其它的庫,比如Caffe、Matconvnet、Theano和Torch等。它們各有優劣,而我想要一個可靠靈活的、自帶預訓練模型的python庫。最近,新推出了一款名叫slim的庫,slim自帶了許多預訓練的模型,比如ResNet、VGG、Inception-ResNet-v2( ILSVRC的新贏家 )等等。這個庫和模型都是Google支持開發的。Google的Tensorflow是一個偏底層的庫,實際使用時開發人員需要編寫大量的代碼,閱讀他人的代碼也很費勁,因此大家早就需要這樣一個簡潔的庫。而slim非常干凈,用預訓練的模型對Tensorflow做了輕量級的封裝。

下文中會用到Tensorflow和卷積神經網絡的知識。Tensorflow的網站上有兩者的完美教程,不了解的讀者可以前去 閱讀 。

我是用jupyter notebook完成的寫作。因此,每一段代碼之后都打印了運行結果。讀者們也可以 下載 完整的notebook。本文中有一部分內容借鑒了 此文

安裝

在運行代碼之前,首先需要安裝Tensorflow。我用的是0.11版本。你可以從github的tensorflow/models代碼庫克隆代碼。

git clone https://github.com/tensorflow/models

我還會用到scikit-image和numpy等依賴,把它們都先裝上。在這里我推薦先下載并安裝Anaconda,然后通過conda install命令安裝其它的python庫。

首先,我們指定tensorflow使用第一塊GPU。否則tensorflow默認會占用所有可用的內存資源。其次,添加克隆下來的代碼庫路徑,這樣python執行的時候就能找到需要的代碼。

import sys
import os

os.environ["CUDA_VISIBLE_DEVICES"] = '0'
sys.path.append("/home/dpakhom1/workspace/models/slim")

接著,下載VGG-16模型,我們將用它來對圖像做分類和分割。也可以選用其它占用內存少的網絡模型(比如,AlexNet)。

from datasets import dataset_utils
import tensorflow as tf

url = "http://download.tensorflow.org/models/vgg_16_2016_08_28.tar.gz"

# 指定保存路徑
checkpoints_dir = '/home/dpakhom1/checkpoints'

if not tf.gfile.Exists(checkpoints_dir):
    tf.gfile.MakeDirs(checkpoints_dir)

dataset_utils.download_and_uncompress_tarball(url, checkpoints_dir)

>> Downloading vgg_16_2016_08_28.tar.gz 100.0%
    Successfully downloaded vgg_16_2016_08_28.tar.gz 513324920 bytes.

圖像分類

我們剛剛下載的模型可以將圖像分成 1000類 。類別的覆蓋度非常廣。在本文中,我們就用這個預訓練的模型來給圖片分類、標注和分割,映射到這1000個類別。

下面是一個圖像分類的例子。圖像首先要做預處理,經過縮放和裁剪,輸入的圖像尺寸與訓練集的圖片尺寸相同。

%matplotlib inline

from matplotlib import pyplot as plt

import numpy as np
import os
import tensorflow as tf
import urllib2

from datasets import imagenet
from nets import vgg
from preprocessing import vgg_preprocessing

checkpoints_dir = '/home/dpakhom1/checkpoints'

slim = tf.contrib.slim

# 網絡模型的輸入圖像有默認的尺寸
# 因此,我們需要先調整輸入圖片的尺寸
image_size = vgg.vgg_16.default_image_size

with tf.Graph().as_default():

url = ("https://upload.wikimedia.org/wikipedia/commons/d/d9/"
       "First_Student_IC_school_bus_202076.jpg")

# 連接網址,下載圖片
image_string = urllib2.urlopen(url).read()

# 將圖片解碼成jpeg格式
image = tf.image.decode_jpeg(image_string, channels=3)

# 對圖片做縮放操作,保持長寬比例不變,裁剪得到圖片中央的區域
# 裁剪后的圖片大小等于網絡模型的默認尺寸
processed_image = vgg_preprocessing.preprocess_image(image,
                                                     image_size,
                                                     image_size,
                                                     is_training=False)

# 可以批量導入圖像
# 第一個維度指定每批圖片的張數
# 我們每次只導入一張圖片
processed_images  = tf.expand_dims(processed_image, 0)

# 創建模型,使用默認的arg scope參數
# arg_scope是slim library的一個常用參數
# 可以設置它指定網絡層的參數,比如stride, padding 等等。
with slim.arg_scope(vgg.vgg_arg_scope()):
    logits, _ = vgg.vgg_16(processed_images,
                           num_classes=1000,
                           is_training=False)

# 我們在輸出層使用softmax函數,使輸出項是概率值
probabilities = tf.nn.softmax(logits)

# 創建一個函數,從checkpoint讀入網絡權值
init_fn = slim.assign_from_checkpoint_fn(
    os.path.join(checkpoints_dir, 'vgg_16.ckpt'),
    slim.get_model_variables('vgg_16'))

with tf.Session() as sess:

    # 加載權值
    init_fn(sess)

    # 圖片經過縮放和裁剪,最終以numpy矩陣的格式傳入網絡模型
    np_image, network_input, probabilities = sess.run([image,
                                                       processed_image,
                                                       probabilities])
    probabilities = probabilities[0, 0:]
    sorted_inds = [i[0] for i in sorted(enumerate(-probabilities),
                                        key=lambda x:x[1])]

# 顯示下載的圖片
plt.figure()
plt.imshow(np_image.astype(np.uint8))
plt.suptitle("Downloaded image", fontsize=14, fontweight='bold')
plt.axis('off')
plt.show()

# 顯示最終傳入網絡模型的圖片
# 圖像的像素值做了[-1, 1]的歸一化
# to show the image.
plt.imshow( network_input / (network_input.max() - network_input.min()) )
plt.suptitle("Resized, Cropped and Mean-Centered input to network",
             fontsize=14, fontweight='bold')
plt.axis('off')
plt.show()

names = imagenet.create_readable_names_for_imagenet_labels()
for i in range(5):
    index = sorted_inds[i]
    # 打印top5的預測類別和相應的概率值。
    print('Probability %0.2f => [%s]' % (probabilities[index], names[index+1]))

res = slim.get_model_variables()

Probability 1.00 => [school bus]
Probability 0.00 => [minibus]
Probability 0.00 => [passenger car, coach, carriage]
Probability 0.00 => [trolleybus, trolley coach, trackless trolley]
Probability 0.00 => [cab, hack, taxi, taxicab]

圖片標注和分割

從上面的例子中可以看到,網絡模型只處理了原始圖像中的一部分區域。這種方式只適用于單一預測結果的場景。

某些場景下,我們希望從圖片中獲得更多的信息。舉個例子,我們想知道圖片中出現的所有物體。網絡模型就告訴我們圖片中有一輛校車,還有幾輛小汽車和幾幢建筑物。這些信息可以協助我們搭建一個圖片搜索引擎。以上就是一個圖片標注的簡單應用。

但是,如果我們也想得到物體的空間位置該怎么辦。網絡能告訴我們它在圖片的中央看到一輛校車,在右上角看到幾幢建筑物?這樣,我們就可以創建一個更具體的搜索查詢詞:“我想要找到中間有一輛校車,左上角有幾只花盆的所有符合要求的圖片”。

某些情況下,我們需要對圖像的每個像素進行分類,也被稱作是圖像的分割。想象一下,假如有一個巨大的圖片數據集,需要給人臉打上馬賽克,這樣我們就不必得到所有人的許可之后才能發布這些照片。例如,谷歌街景都對行人的臉做了模糊化處理。當然,我們只需要對圖片中的人臉進行模糊處理,而不是所有的內容。圖片分割可以幫助我們實現類似的需求。我們可以分割得到屬于人臉的那部分像素,并只對它們進行模糊處理。

下面將介紹一個簡單的圖片分割例子。我們可以使用現有的卷積神經網絡,通過完全卷積的方式進行分割。若想要輸出的分割結果與輸入圖像尺寸保持一致,可以增加一個去卷積層。

from preprocessing import vgg_preprocessing

# 加載像素均值及相關函數
from preprocessing.vgg_preprocessing import (_mean_image_subtraction,
                                            _R_MEAN, _G_MEAN, _B_MEAN)

# 展現分割結果的函數,以不同的顏色區分各個類別
def discrete_matshow(data, labels_names=[], title=""):
    #獲取離散化的色彩表
    cmap = plt.get_cmap('Paired', np.max(data)-np.min(data)+1)
    mat = plt.matshow(data,
                      cmap=cmap,
                      vmin = np.min(data)-.5,
                      vmax = np.max(data)+.5)
    #在色彩表的整數刻度做記號
    cax = plt.colorbar(mat,
                       ticks=np.arange(np.min(data),np.max(data)+1))

    # 添加類別的名稱
    if labels_names:
        cax.ax.set_yticklabels(labels_names)

    if title:
        plt.suptitle(title, fontsize=14, fontweight='bold')


with tf.Graph().as_default():

    url = ("https://upload.wikimedia.org/wikipedia/commons/d/d9/"
           "First_Student_IC_school_bus_202076.jpg")

    image_string = urllib2.urlopen(url).read()
    image = tf.image.decode_jpeg(image_string, channels=3)

    # 減去均值之前,將像素值轉為32位浮點
    image_float = tf.to_float(image, name='ToFloat')

    # 每個像素減去像素的均值
    processed_image = _mean_image_subtraction(image_float,
                                              [_R_MEAN, _G_MEAN, _B_MEAN])

    input_image = tf.expand_dims(processed_image, 0)

    with slim.arg_scope(vgg.vgg_arg_scope()):

        # spatial_squeeze選項指定是否啟用全卷積模式
        logits, _ = vgg.vgg_16(input_image,
                               num_classes=1000,
                               is_training=False,
                               spatial_squeeze=False)

    # 得到每個像素點在所有1000個類別下的概率值,挑選出每個像素概率最大的類別
    # 嚴格說來,這并不是概率值,因為我們沒有調用softmax函數
    # 但效果等同于softmax輸出值最大的類別
    pred = tf.argmax(logits, dimension=3)

    init_fn = slim.assign_from_checkpoint_fn(
        os.path.join(checkpoints_dir, 'vgg_16.ckpt'),
        slim.get_model_variables('vgg_16'))

    with tf.Session() as sess:
        init_fn(sess)
        segmentation, np_image = sess.run([pred, image])

# 去除空的維度
segmentation = np.squeeze(segmentation)

unique_classes, relabeled_image = np.unique(segmentation,
                                            return_inverse=True)

segmentation_size = segmentation.shape

relabeled_image = relabeled_image.reshape(segmentation_size)

labels_names = []

for index, current_class_number in enumerate(unique_classes):

    labels_names.append(str(index) + ' ' + names[current_class_number+1])

discrete_matshow(data=relabeled_image, labels_names=labels_names, title="Segmentation")

我們得到的結果顯示網絡模型確實可以從圖片中找到校車,以及左上角顯示不太清晰的交通標志。而且,模型可以找到左上角建筑物的窗戶,甚至猜測說這是一個圖書館(我們無法判斷是否屬實)。它做出了一些不那么正確的預測。這些通常是由于網絡在預測的時候只能看到當前像素周圍的一部分圖像。網絡模型表現出來的這種特性被稱為感受視野。在本文中,我們使用的網絡模型的感受視野是404像素。所以,當網絡只能觀察到校車的一部分圖片時,與出租車和皮卡車混淆了。

正如我們在上面所看到的,我們得到了圖片的一個簡單分割結果。它不算很精確,因為最初訓練網絡是用來進實現分類任務,而不是圖像分割。如果想得到更好的結果,我們還是需要重新訓練一個模型。不管怎么說,我們得到的結果是可以用作圖像標注的。

使用卷積神經網絡進行圖像分割,可以被看作是對輸入圖像的不同部分進行分類。我們將網絡聚焦于某個像素點,進行預測判斷,并輸出該像素的類別標簽。這樣,我們給分類和分割的結果增加了空間信息。

小結

本文介紹了用slim庫實現圖像的分類和分割,并且簡要闡述了技術原理。

自帶預訓練模型的slim庫是一款強大而靈活的工具,可以配合tensorflow使用。由于最近剛剛發布,文檔不是很完善,有時候甚至要閱讀代碼來幫助理解。Google正在加快進度完善后續的工作。

 

 

來自:http://geek.csdn.net/news/detail/126133

 

 本文由用戶 jackwang 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!