目次をクリックすると移動できます
はじめに
画像系AIの中で、最も高度な分析を行っているのが、セマンティックセグメンテーションです。画像内の各画素が何なのかを返してくれます。自動運転などの大規模なプロジェクトで使われていますね。
今回は、比較的シンプルなセマンティックセグメンテーションのサンプルコードを初心者の方向けに解説していきます。
↓↓↓オリジナルはこちらです。tensorflow kerasですね。U-net のようなモデルを使っています。
Keras documentation: Image segmentation with a U-Net-like architecture
Keras documentation
google colaboで開くと、実行しながら確認出来て便利ですよ。
サンプルコード解説
データのダウンロード
!curl -O https://thor.robots.ox.ac.uk/~vgg/data/pets/images.tar.gz
!curl -O https://thor.robots.ox.ac.uk/~vgg/data/pets/annotations.tar.gz
!tar -xf images.tar.gz
!tar -xf annotations.tar.gz
データをダウンロードします。
google colabo上では先頭に!を忘れずに。
ローカルPCで実行される方は、!を外して、コマンドラインで実行してください。
画像データとマスク(正解の塗り絵画像)ファイルのパス設定
import os
input_dir = "images/"
target_dir = "annotations/trimaps/"
img_size = (160, 160)
num_classes = 3
batch_size = 32
input_img_paths = sorted(
[
os.path.join(input_dir, fname)
for fname in os.listdir(input_dir)
if fname.endswith(".jpg")
]
)
target_img_paths = sorted(
[
os.path.join(target_dir, fname)
for fname in os.listdir(target_dir)
if fname.endswith(".png") and not fname.startswith(".")
]
)
print("Number of samples:", len(input_img_paths))
for input_path, target_path in zip(input_img_paths[:10], target_img_paths[:10]):
print(input_path, "|", target_path)
画像と答え(mask)のファイルパス設定です。
- 3行目:画像ファイルパス
- 4行目:アノテーション(マスク)ファイルパス
- 5行目:画像サイズ160×160pix。小さめですね。。。実運用では使えないかな。
- 6行目:クラス数 3。3種類の分類です
- 7行目:バッチサイズ。一度にメモリにのせて計算させる画像データ数。
- 9~14行目:画像ファイルのパス のリストを作成しています。
12行目のos.listdir()でフォルダ内のファイルとフォルダ一覧を作成して、for分を回し
13行目で最後が.jpgで終わるものだけ残します。
11行目で、フォルダのパスとファイル名を合体します。
ここは、glob使った方がいい気がしますね。。。。 - 16~22行目:マスク画像のファイルパス のリストを作成しています。
画像ファイルのパスと一緒ですね。 - 24行目:サンプルファイルの数を表示します。
- 26行目:画像とマスクのパスを10個まで、まとめてfor文回します。
- 27行目:画像とマスクのパスを表示します。セットで。
画像を1個開いてみて、セグメンテーションの答えがどんな感じか確認します。
from IPython.display import Image, display
from tensorflow.keras.preprocessing.image import load_img
from PIL import ImageOps
# Display input image #7
display(Image(filename=input_img_paths[9]))
# Display auto-contrast version of corresponding target (per-pixel categories)
img = ImageOps.autocontrast(load_img(target_img_paths[9]))
display(img)
- 1行目:IPythonの表示関係のライブラリをインポートします
- 2行目:tensorflowの画像ロードライブラリをインポート
- 3行目:PillowのImageOpsをインポート。画像補正関連ですね。
- 6行目:9番目の画像を読み込んで表示します。
- 9行目:9番目のマスク画像(正解を塗り絵した画像)を読み込みます。コントラスト自動調整を入れています。そのままだと見ずらいのですかね。
- 10行目:マスク画像表示
データを引き出す便利なクラスを作成(データセットの準備)
from tensorflow import keras
import numpy as np
from tensorflow.keras.preprocessing.image import load_img
class OxfordPets(keras.utils.Sequence):
"""Helper to iterate over the data (as Numpy arrays)."""
def __init__(self, batch_size, img_size, input_img_paths, target_img_paths):
self.batch_size = batch_size
self.img_size = img_size
self.input_img_paths = input_img_paths
self.target_img_paths = target_img_paths
def __len__(self):
return len(self.target_img_paths) // self.batch_size
def __getitem__(self, idx):
"""Returns tuple (input, target) correspond to batch #idx."""
i = idx * self.batch_size
batch_input_img_paths = self.input_img_paths[i : i + self.batch_size]
batch_target_img_paths = self.target_img_paths[i : i + self.batch_size]
x = np.zeros((self.batch_size,) + self.img_size + (3,), dtype="float32")
for j, path in enumerate(batch_input_img_paths):
img = load_img(path, target_size=self.img_size)
x[j] = img
y = np.zeros((self.batch_size,) + self.img_size + (1,), dtype="uint8")
for j, path in enumerate(batch_target_img_paths):
img = load_img(path, target_size=self.img_size, color_mode="grayscale")
y[j] = np.expand_dims(img, 2)
# Ground truth labels are 1, 2, 3. Subtract one to make them 0, 1, 2:
y[j] -= 1
return x, yd
ここでは、画像と回答(マスク画像)を呼び出すクラスを作成しています。データセットの準備ですね。まだ、kerasやtensorflowでsegmentation向けのジェネレータは実装されていないようですね。。。
- 1~3行目:tensorflwo,numpy,tensorflowの画像読み込みライブラリインポート
- 6行目:クラス定義。オックスフォードペット?ですかね?
- 9~13行目:変数初期値定義。
9行目:バッチサイズ
10行目:画像サイズ
11行目:画像データのパスリスト
12行目:マスク画像のパスリスト - 15~16行目:バッチ数を返す関数定義
- 18行目:特殊メソッド定義。画像のidを入力すると、画像データとマスク画像を返します。
- 20行目:画像の番号にします
- 21行目:今のバッチにおける、画像のパスリスト。例えばバッチ数32なら、32個の画像分のパスです。
- 22行目:今のバッチにおける、ターゲット(マスク)の画像パスリスト。
- 23行目:numpy形式で、箱を用意しておきます。中身は全部ゼロです。
- 24行目:画像のパスリストでfor分を回します。パスをひとつづ取り出します。
- 25行目:画像をロードします。引数target_sizeで画像サイズをそろえます。
- 26行目:画像の箱、xに入れておきます
- 27行目:マスク画像を入れておく箱を、numpy形式で作成します。中身はすべてゼロです。
- 28行目:マスク画像のパスリストから、for分を回して、ひとつづパスを取り出します。
- 29行目:マスク画像を読み込みます。target_sizeで画像をそろえて、グレイスケールで読み込みます。
- 30行目:マスク画像はグレイスケールの2次元データなので、chの次元を追加してから、マスク画像の箱に入れておきます。
- 32行目:マスク画像は全部から1引いておきます。もともと1,2,3の3クラスなのですが、pythonだけに0,1,2にするため。。。
- 33行目:入力画像の箱(バッチ分の画像)と、マスク画像の箱(バッチ分のマスク)を返します。
Xceptionの構造をもつU-NETモデルを構築
from tensorflow.keras import layers
def get_model(img_size, num_classes):
inputs = keras.Input(shape=img_size + (3,))
### [First half of the network: downsampling inputs] ###
# Entry block
x = layers.Conv2D(32, 3, strides=2, padding="same")(inputs)
x = layers.BatchNormalization()(x)
x = layers.Activation("relu")(x)
previous_block_activation = x # Set aside residual
# Blocks 1, 2, 3 are identical apart from the feature depth.
for filters in [64, 128, 256]:
x = layers.Activation("relu")(x)
x = layers.SeparableConv2D(filters, 3, padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.Activation("relu")(x)
x = layers.SeparableConv2D(filters, 3, padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.MaxPooling2D(3, strides=2, padding="same")(x)
# Project residual
residual = layers.Conv2D(filters, 1, strides=2, padding="same")(
previous_block_activation
)
x = layers.add([x, residual]) # Add back residual
previous_block_activation = x # Set aside next residual
### [Second half of the network: upsampling inputs] ###
for filters in [256, 128, 64, 32]:
x = layers.Activation("relu")(x)
x = layers.Conv2DTranspose(filters, 3, padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.Activation("relu")(x)
x = layers.Conv2DTranspose(filters, 3, padding="same")(x)
x = layers.BatchNormalization()(x)
x = layers.UpSampling2D(2)(x)
# Project residual
residual = layers.UpSampling2D(2)(previous_block_activation)
residual = layers.Conv2D(filters, 1, padding="same")(residual)
x = layers.add([x, residual]) # Add back residual
previous_block_activation = x # Set aside next residual
# Add a per-pixel classification layer
outputs = layers.Conv2D(num_classes, 3, activation="softmax", padding="same")(x)
# Define the model
model = keras.Model(inputs, outputs)
return model
# Free up RAM in case the model definition cells were run multiple times
keras.backend.clear_session()
# Build model
model = get_model(img_size, num_classes)
model.summary()
本題のモデル構築です。モデル構造を理解しつつ、美しさを味わいましょう。
- 1行目:tensorflowのレイヤーをインポート
- 4行目:モデル構築関数。引数は、画像のサイズとクラスの数です。
- 5行目:画像入力層です。サイズを変えています
- ここから、ダウンサンプリング。画像を小さくしていきます。
- 10行目:畳み込み層
- 11行目:バッチノーマリゼーション(バッチごとの正規化)
- 12行目:活性化関数。レルーですね
- 14行目:出力をresidualとしてとっておいて。分岐みたいなものです。
- 17行目:フィルタサイズ変えて3回繰り返します。
- 18行目:活性化関数
- 19行目:ちょっと特殊な畳み込みを行います。xceptionで使うものです。
- 20行目:バッチごとの正規化
- 22~24行目:もう1セット。活性化関数、特殊な畳み込み、バッチごとの正規化。
- 26行目:マックスプーリング
- 29~31行目:residual層を畳み込みで作ります。14行目で分岐させてとっておいた入力使います。
- 32行目:本流と、支流(residual)を合体
- 33行目:また、分岐用に出力をとておきます。
- ここからは、アップサンプリングです。画像をもとのサイズに戻していきます。
- 37行目:4種類のフィルタ種類でfor文回します
- 38行目:活性化関数
- 39行目:転置畳み込み。
- 40行目:バッチごとの正規化
- 42~44行目:もう1セット、活性化関数、転置畳み込み、バッチごとの正規化
- 46行目:アップサンプリング。画像を2倍にします。
- 49行目:residual層。33行目でとっておいた、支流を入力として、2倍にアップサンプリング。
- 50行目:1回畳み込んで
- 51行目:主流と、支流(residual)合体
- 52行目:支流を保存しておく。
- 55行目:softmaxでつないで、各画素値が、3クラス分類の確率になるようにする。
- 58行目:モデルを作っておきます。引数が、入力層と出力層のところ
- 63行目:メモリを開放するおまじない
- 66行目:モデルクラスを作成
- 67行目:モデルのサマリ表示
データの一部をValidationデータに分けます。
import random
# Split our img paths into a training and a validation set
val_samples = 1000
random.Random(1337).shuffle(input_img_paths)
random.Random(1337).shuffle(target_img_paths)
train_input_img_paths = input_img_paths[:-val_samples]
train_target_img_paths = target_img_paths[:-val_samples]
val_input_img_paths = input_img_paths[-val_samples:]
val_target_img_paths = target_img_paths[-val_samples:]
# Instantiate data Sequences for each split
train_gen = OxfordPets(
batch_size, img_size, train_input_img_paths, train_target_img_paths
)
val_gen = OxfordPets(batch_size, img_size, val_input_img_paths, val_target_img_paths)
学習データと検証データに分けるところです
- 1行目:乱数のライブラリインポート
- 4行目:検証データは100個
- 5行目:乱数で、入力画像のパスたちをシャッフル
- 6行目:乱数で、答え(マスク画像)のパスたちをシャッフル。シード同一だから、画像と答えはセットだよ。
- 7行目:前の方が学習データのパス。画像
- 8行目:前の方が学習データのパス。マスク
- 9行目:後ろ100個が検証データのパス。画像
- 10行目:後ろ100個が検証データのパス。マスク
- 13~15行目:学習画像のジェネレータを作成。自作クラスが活躍しています!
- 16行目:検証画像のジェネレータを作成。こちらも自作クラスが活躍しています!
model学習
# Configure the model for training.
# We use the "sparse" version of categorical_crossentropy
# because our target data is integers.
model.compile(optimizer="rmsprop", loss="sparse_categorical_crossentropy")
callbacks = [
keras.callbacks.ModelCheckpoint("oxford_segmentation.h5", save_best_only=True)
]
# Train the model, doing validation at the end of each epoch.
epochs = 15
model.fit(train_gen, epochs=epochs, validation_data=val_gen, callbacks=callbacks)
学習です。
- 4行目:モデルコンパイル。optimizerは”rmsprop”。lossは”sparce categorical crossentropy”です。ターゲットデータ(マスク画像)が整数なので、sparceでよいそうです。
- 6~8行目:コールバックを定義。ベスト解が出たら、重みを保存しておきます。
- 11行目:エポック数は15。そんなもんですかね。。。もう少し増やした方が。。。
- 12行目:学習実行。画像と答えはジェネレータで入れています。
予測結果を可視化
# Generate predictions for all images in the validation set
val_gen = OxfordPets(batch_size, img_size, val_input_img_paths, val_target_img_paths)
val_preds = model.predict(val_gen)
def display_mask(i):
"""Quick utility to display a model's prediction."""
mask = np.argmax(val_preds[i], axis=-1)
mask = np.expand_dims(mask, axis=-1)
img = ImageOps.autocontrast(keras.preprocessing.image.array_to_img(mask))
display(img)
# Display results for validation image #10
i = 10
# Display input image
display(Image(filename=val_input_img_paths[i]))
# Display ground-truth target mask
img = ImageOps.autocontrast(load_img(val_target_img_paths[i]))
display(img)
# Display mask predicted by our model
display_mask(i) # Note that the model only sees inputs at 150x150.
学習したモデルで、予測して結果を可視化します
- 3行目:検証データのジェネレータを作成して
- 4行目:学習済みのモデルを使って予測
- 7行目:予測結果 各画素をクラス分けしたものを表示する関数
- 9行目:マスクは、確率が一番大きかったクラス
- 10行目:次元を1次元増やして。chの次元を追加します。
- 11行目:マスクデータを画像データに変換。kerasのarray_to_imageというメソッドを使っています。Pillowのautocontrastでコントラストを自動調整いています。
- 12行目:画像表示
- 16行目:検証データの10番目をつかうよ
- 19行目:推論につかった入力画像を表示
- 22行目:正解のマスク画像を読み込んで。。。
- 23行目:表示
- 26行目:予測結果を表示
まとめ
今回は、segmentationのサンプルコードに解説を加えてみました。segmentationはアノテーション(色塗り)作業が大変であまり活用が進みませんでしたが、ラベリングツールが改善されたこともあり、徐々に活用例が増え始めています。
みなさんの、コードの理解におやくにたてれば光栄です。
最後までお付き合いいただいた、勉強熱心な皆さんへは、よろしければ、こちらの記事もいかがでしょう
tensorflow keras vision transformerによる物体検知のサンプルコード解説。 初心者向けです。
画像認識の主流となりつつなるアルゴリズム、Vision Transformerですが、物体検知(object detection)タスクへの利用も提案されています。今回は、Tensorflwo kerasを用いて、ViTを物体検出へ適用したサンプルコードを初心者向けに解説します。