こしあん
2019-10-07

Numpyだけで複数の画像をタイルし1つの画像にまとめる方法

Pocket
LINEで送る
Delicious にシェア

2.6k{icon} {views}



「torchvision.utilsのmake_gridやテンソルをタイルして保存するのって便利だよね。でも、いちいちこのためにPyTorchのテンソルに変えるのって面倒だよね」ということで同じことをNumpyでも実装してみました。Numpy配列の扱い方を工夫すればいけます。

コード

make_gridという関数がこれにあたります。「imgs」は複数の画像を格納した4階テンソル(batch, height, width, ch)のshape、「nrow」はタイル後に1行あたり何枚の画像をタイルするか、「padding」はオプションでグリッドの間隔です。

import numpy as np

def make_grid(imgs, nrow, padding=0):
    """Numpy配列の複数枚の画像を、1枚の画像にタイルします

    Arguments:
        imgs {np.ndarray} -- 複数枚の画像からなるテンソル
        nrow {int} -- 1行あたりにタイルする枚数

    Keyword Arguments:
        padding {int} -- グリッドの間隔 (default: {0})

    Returns:
        [np.ndarray] -- 3階テンソル。1枚の画像
    """
    assert imgs.ndim == 4 and nrow > 0
    batch, height, width, ch = imgs.shape
    n = nrow * (batch // nrow + np.sign(batch % nrow))
    ncol = n // nrow
    pad = np.zeros((n - batch, height, width, ch), imgs.dtype)
    x = np.concatenate([imgs, pad], axis=0)
    # border padding if required
    if padding > 0:
        x = np.pad(x, ((0, 0), (0, padding), (0, padding), (0, 0)),
                   "constant", constant_values=(0, 0)) # 下と右だけにpaddingを入れる
        height += padding
        width += padding
    x = x.reshape(ncol, nrow, height, width, ch)
    x = x.transpose([0, 2, 1, 3, 4])  # (ncol, height, nrow, width, ch)
    x = x.reshape(height * ncol, width * nrow, ch)
    if padding > 0:
        x = x[:(height * ncol - padding),:(width * nrow - padding),:] # 右端と下端のpaddingを削除
    return x

解説

例えば、100枚の32×32のカラー画像を、1行あたり12枚のフォームでグリッドを作ることを想定します。このとき、imgsのshapeは(100, 32, 32, 3)、nrow=12となります。

まず、nrowに対応するncolを計算します。入力のnrowが1行あたりの枚数なので、ncolは1列あたりの枚数、つまり行数を表します。小数を使ってもいいですが浮動小数点のバグが怖いので、整数商+Modのsign関数でやりました。「nrow×ncol-入力の枚数」の不足分の画像を0として埋めます。この0埋め画像をimgsと結合した変数を「x」とします。ここはnp.concatenateを使えばいいです(ディープラーニングやっている人にはおなじみ)

次が少し難しいですが、グリッドを作ったときの画像間の隙間を作ります。発想的にはディープラーニングのZero Paddingと同じです。少し扱い方が難しいですが、np.padで同じことはできます。詳しくは公式ドキュメント見ていろいろ試してみるのが早いですが、「(0, 0), (0, padding), 」の表すものは、テンソルの各軸に対して左右どれだけの要素数を埋めるかを表します。ここでやっているのは画像の右と下にだけZero paddingをすることです。もしここをただのTupleとして指定するとすべての軸(不要なバッチやチャンネルの軸)にPaddingがかかってしまうので、縦と横(axis=1,2)だけ指定します。np.padはmodeにいろんな値が指定できるので、鏡像反転するようにPaddingなどもできます。もし今回のように「mode="constant", constant_values=(0, 0)」だとZero paddingになります。Paddingによって画像の縦と横のサイズが変わるので、忘れないように足しましょう。

次からがポイントで、「n, height, width, ch」というテンソルを、「ncol, nrow, height, width, ch」という5階テンソルにreshapeします。ここでやっているのは1次元に並んでいる画像を、縦横に分解しているということです。画像を横に並べているようなイメージです。

次からが黒魔術で、軸の入れ替えを行います。これはtransposeでできるのですが、「ncol, nrow, height, width, ch」→「ncol, height, nrow, width, ch」となるように入れ替えます。縦は縦、横は横となるように軸を入れ替えているということです。これは後のreshapeのためのもので、軸を入れ替えてreshapeするといい感じに並ぶからです。

最後に軸の結合です。これもreshapeで行います。0・1、2・3の軸を結合し、「ncol, height, nrow, width, ch」→「ncol * height, nrow * width, ch」とします。これは3階テンソルで1枚の画像のshapeとしては適切です。複数の画像をタイルし、グリットとして並べられた1枚の画像ができたということです。ただし、右と下の最後の1回のpaddingが不要なので、スライスして調整します。

例1~RGB画像~

Toy problemとして、1枚目が赤、2枚目が緑、3枚目が青の画像をタイルしてみましょう。

from PIL import Image

def rgb():
    x = np.zeros((3, 64, 64, 3), np.uint8)
    x[0,:,:,0] = 255 # 1枚目はred
    x[1,:,:, 1] = 255 # 2枚目はgreen
    x[2,:,:, 2] = 255 # 3枚目はblue
    stacked = make_grid(x, 2, padding=0)
    with Image.fromarray(stacked) as img:
        img.save("rgb.png")

タイルする方向は、左から右→上から下の順番です。torchvisionのグリッド化と同じです。torchvisionだとテンソル化したり、channels_firstにコンバートしないといけなかったり、Numpy配列をタイルする場合は面倒なんですよね。

Padding=10としてタイルしてみましょう。

隙間がちょっと大きくなりました。

例2~CIFAR-10~

CIFAR-10の訓練画像の最初の100枚をタイルしてみましょう。CIFAR-10のデータの読み込みにはKerasを使っています。

import tensorflow as tf

def cifar():
    (X, _), (_, _) = tf.keras.datasets.cifar10.load_data() # cifar10読み込むためにKerasを使う
    X = X[:100]  # (100, 32, 32, 3)
    stacked = make_grid(X, 12, padding=2)
    with Image.fromarray(stacked) as img:
        img.save("cifar.png")

ほぼtorchvisionですよね。Numpyだけでタイルしたい場合は便利ではないでしょうか。

まとめ

Numpyでもreshapeやtransposeを駆使することで画像のタイルまでできちゃいますよ、という話でした。テンソル使いこなせると楽しいですね。



Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

【新刊】インフィニティNumPy――配列の初期化から、ゲームの戦闘、静止画や動画作成までの221問

「本当の実装力を身につける」ための221本ノック――
機械学習(ML)で避けて通れない数値計算ライブラリ・NumPyを、自在に活用できるようになろう。「できる」ための体系的な理解を目指します。基礎から丁寧に解説し、ディープラーニング(DL)の難しいモデルで遭遇する、NumPyの黒魔術もカバー。初心者から経験者・上級者まで楽しめる一冊です。問題を解き終わったとき、MLやDLなどの発展分野にスムーズに入っていけるでしょう。

本書の大きな特徴として、Pythonの本でありがちな「NumPyとML・DLの結合を外した」点があります。NumPyを理解するのに、MLまで理解するのは負担が大きいです。本書ではあえてこれらの内容を書いていません。行列やテンソルの理解に役立つ「従来の画像処理」をNumPyベースで深く解説・実装していきます。

しかし、問題の多くは、DLの実装で頻出の関数・処理を重点的に取り上げています。経験者なら思わず「あー」となるでしょう。関数丸暗記では自分で実装できません。「覚える関数は最小限、できる内容は無限大」の世界をぜひ体験してみてください。画像編集ソフトの処理をNumPyベースで実装する楽しさがわかるでしょう。※紙の本は電子版の特典つき

モザイク除去から学ぶ 最先端のディープラーニング

「誰もが夢見るモザイク除去」を起点として、機械学習・ディープラーニングの基本をはじめ、GAN(敵対的生成ネットワーク)の基本や発展型、ICCV, CVPR, ECCVといった国際学会の最新論文をカバーしていく本です。
ディープラーニングの研究は発展が目覚ましく、特にGANの発展型は市販の本でほとんどカバーされていない内容です。英語の原著論文を著者がコードに落とし込み、実装を踏まえながら丁寧に解説していきます。
また、本コードは全てTensorFlow2.0(Keras)に対応し、Googleの開発した新しい機械学習向け計算デバイス・TPU(Tensor Processing Unit)をフル活用しています。Google Colaboratoryを用いた環境構築不要の演習問題もあるため、読者自ら手を動かしながら理解を深めていくことができます。

AI、機械学習、ディープラーニングの最新事情、奥深いGANの世界を知りたい方にとってぜひ手にとっていただきたい一冊となっています。持ち運びに便利な電子書籍のDLコードが付属しています。

「おもしろ同人誌バザールオンライン」で紹介されました!(14:03~) https://youtu.be/gaXkTj7T79Y?t=843

まとめURL:https://github.com/koshian2/MosaicDeeplearningBook
A4 全195ページ、カラー12ページ / 2020年3月発行

Shikoan's ML Blog -Vol.1/2-

累計100万PV超の人気ブログが待望の電子化! このブログが電子書籍になって読みやすくなりました!

・1章完結のオムニバス形式
・機械学習の基本からマニアックなネタまで
・どこから読んでもOK
・何巻から読んでもOK

・短いものは2ページ、長いものは20ページ超のものも…
・通勤・通学の短い時間でもすぐ読める!
・読むのに便利な「しおり」機能つき

・全巻はA5サイズでたっぷりの「200ページオーバー」
・1冊にたっぷり30本収録。1本あたり18.3円の圧倒的コストパフォーマンス!
・文庫本感覚でお楽しみください

北海道の駅巡りコーナー

日高本線 車なし全駅巡り

ローカル線や秘境駅、マニアックな駅に興味のある方におすすめ! 2021年に大半区間が廃線になる、北海道の日高本線の全区間・全29駅(苫小牧~様似)を記録した本です。マイカーを使わずに、公共交通機関(バス)と徒歩のみで全駅訪問を行いました。日高本線が延伸する計画のあった、襟裳岬まで様似から足を伸ばしています。代行バスと路線バスの織り成す極限の時刻表ゲームと、絶海の太平洋と馬に囲まれた日高路、日高の隠れたグルメを是非たっぷり堪能してください。A4・フルカラー・192ページのたっぷりのボリュームで、あなたも旅行気分を漫喫できること待ったなし!

見どころ:日高本線被災区間(大狩部、慶能舞川橋梁、清畠~豊郷) / 牧場に囲まれた絵笛駅 / 窓口のあっただるま駅・荻伏駅 / 汐見の戦争遺跡のトーチカ / 新冠温泉、三石温泉 / 襟裳岬

A4 全192ページフルカラー / 2020年11月発行


Pocket
LINEで送る

Add a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です