こしあん
2019-10-04

画像のピラミッドを1枚の画像として出力するサンプル

Pocket
LINEで送る


同一画像で繰り返し半分に縮小しながら積み重ねていく操作(ピラミッド)が必要になったので、ピラミッドを1枚の画像として出力するサンプルを作ってみました。

ピラミッド

同一画像の解像度をある一定比率(よくある例では半分)で繰り返し縮小しながら積み重ねていくことを、ピラミッドと言います。OpenCVのドキュメントでは、ガウシアンピラミッドやラプラシアンピラミッドが紹介されています(ラプラシアンピラミッドはGANの評価指標・SWDに使われます)。

例えば、128×128の解像度の画像があったとしましょう。これを高レベル(低解像度)方向にピラミッドを作っていくと、

  • 128×128の画像(オリジナル)
  • 64×64の画像(オリジナルを半分に縮小)
  • 32×32の画像(さらに半分に縮小)
  • 16×16の画像(さらに半分に縮小)

という具合に積み重なっていきます。

この記事でやりたいことは、出来上がったピラミッドを1枚の画像として結合し、出力するということです。

考え方

出力画像の解像度をなるべく抑えようとすると次のように考えられます。

まず、オリジナル画像の解像度に対して、縦か横を1.5倍した画像を「キャンバス」として考えます。そのキャンバスに対してピラミッドの各画像をペーストしていくようにします。最後にそのキャンバスをファイルとして保存すれば完成です。

ここで問題になるのは、ピラミッドの各画像をペーストする際、キャンバスでのどの座標に配置するか?ということです。これは書き出して行くと規則性が見えて、

解像度 左上x 左上y 右下x 右下y
128 0 0 128 128
64 0 128 64 192
32 64 128 96 160
16 64 160 80 176

ピラミッドの1枚目(オリジナル画像)が、キャンバスの(0,0)の座標に配置します。解像度は縦横128pxなので、右下・左下は(128,128)となります。

ピラミッドの2枚目は、1枚目の下に配置します。つまり、キャンバスでの座標(0, 128)にペーストします。この画像の解像度は64pxなので、始点に+64したのが右下の座標になります。

ピラミッドの3枚目は、1枚目の下・2枚目の右に配置します。ピラミッドは高レベルになるほど解像度が下がるのでこういう配置が可能です。

以下同様です。つまり、下→右→下→右→…のように配置していきます。

ここで、各画像のペースト先のキャンバス上での座標に対して、「2枚目-1枚目」、「3枚目-2枚目」の差分を取ります。そうすると次のようになります。

idx 差分x 差分y
0 0 128
1 64 0
2 0 32
3 16 0

規則性が見えてきました。「idxが偶数ならyに、idxが奇数ならx」に足せばよいのです。あとはこれを実装するだけです。

実装

オリジナル画像を「train.jpg」とします。

from PIL import Image

def tile_pyramid(n_repeat):
    with Image.open("train.jpg") as img:
        width, height = img.size
        with Image.new(img.mode, (width, height * 3 // 2)) as out: # 出力サイズは縦だけ1.5倍
            x, y = 0, 0
            out.paste(img, (x, y))
            for i in range(n_repeat):
                if i % 2 == 0:
                    y += height // (2 ** i) # 0, 2, 4..でy方向にシフト
                else:
                    x += width // (2 ** i) # 1, 3, 5..でx方向にシフト
                paste_img = img.resize((width // (2 ** (i + 1)), height // (2 ** (i + 1))), Image.LANCZOS)
                out.paste(paste_img, (x, y))
            out.save("out.png")

if __name__ == "__main__":
    tile_pyramid(6)

「n_repeat」はピラミッドを繰り返す回数を表します。この例では6回としました(最終的な解像度は1/64になります)。

結果

もうちょっと良い配置あるかも。

別バージョン

互い違いに足すことは変わりないですが、奇数偶数で縦横足さない場合でも微小な差分を足すことをしてみます。

def tile_pyramid(n_repeat):
    with Image.open("train.jpg") as img:
        width, height = img.size
        with Image.new(img.mode, (width, height * 3 // 2)) as out: # 出力サイズは縦だけ1.5倍
            x, y = 0, 0
            out.paste(img, (x, y))
            for i in range(n_repeat):
                if i % 2 == 0:
                    x += width // (2 ** (i + 3))                    
                    y += height // (2 ** i) # 0, 2, 4..でy方向にシフト
                else:
                    x += width // (2 ** i)  # 1, 3, 5..でx方向にシフト
                    y += height // (2 ** (i + 3))                    
                paste_img = img.resize((width // (2 ** (i + 1)), height // (2 ** (i + 1))), Image.LANCZOS)
                out.paste(paste_img, (x, y))
            out.save("out.jpg")

どっちが良いかはお好みで。

Related Posts

Numpyだけで複数の画像をタイルし1つの画像にまとめる方法... 「torchvision.utilsのmake_gridやテンソルをタイルして保存するのって便利だよね。でも、いちいちこのためにPyTorchのテンソルに変えるのって面倒だよね」ということで同じことをNumpyでも実装してみました。Numpy配列の扱い方を工夫すればいけます。 コード make...
OpenCVで作成した動画がブラウザで正常に表示できない場合の解決法... OpenCVで作成した動画をサイトで表示する場合、ローカルで再生できていても、ブラウザ上では突然プレビューがでなり、ハマることがあります。原因の特定が難しい現象ですが、動画を作成する際にH.264形式でエンコードするとうまくいきました。その方法を解説します。 MPV4は手軽だが… OpenCV...
ML Study Jams中級編終わらせてきた ML Study JamsというGoogle Cloudが提供している無料の学習プログラムの第二弾がオープンしています。今度は中級編が追加されており、全部終わらせてきたのでその報告と感想を書いていきたいと思います。 前回の記事 QWIKLABSの使い方とかはこっち。 ML Study Jam...
Spectral Normalization(SNGAN)を実装していろいろ遊んでみた... GANの安定化の大きなブレイクスルーである「Spectral Normalization」をPyTorchで実装していろいろ遊んでみました。従来のGANよりも多クラスの出力がかなりやりやすくなりました。確かにGANの安定化についてはものすごい効いているので、ぜひ皆さんも遊んでみてください。 ※ア...
PyTorchで双方向連結リストなデータ構造のモデルを作る... ディープラーニングのモデルには、訓練の途中でレイヤーを追加するなど特殊な訓練をするものがあります(Progressive-GANなど)。そのとき、モデルを「レイヤーやブロックの連結リスト」として定義しておくと見通しがよくなることがあります。その例を見ていきます。 訓練中に継ぎ足していくモデル ...
Pocket
Delicious にシェア

Add a Comment

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