"BOKU"のITな日常

BOKUが勉強したり、考えたことを頭の整理を兼ねてまとめてます。

頭の体操を兼ねて、Pythonで画像データ変形メソッドを手作りする

f:id:arakan_no_boku:20210912183619p:plain

目次

頭の体操を兼ねて、Pythonで画像データ変形メソッドを手作りする

(2017年3月1日投稿記事のリライトです)

手書き数字画像データの変形なんて、Pythonのライブラリでやれば超簡単です。

たとえば「Pillow(PIL)」を使うなら。

from PIL import Image

img = Image.open(data_dir_path + '/' + file_name)

tmp = img.transpose(Image.ROTATE_90)

みたいに「transpose」メソッドで以下のパラメータを指定するだけで、上下左右反転、90°ごとの回転などができます。

  • Image.FLIP_LEFT_RIGHT
  • Image.FLIP_FLIP_TOP_BOTTOM
  • Image.FLIP_ROTATE_90
  • Image.FLIP_ROTATE_180
  • Image.FLIP_ROTATE_270
  • Image.FLIP_TRANSPOSE
  • Image.FLIP_TRANSVERSE

回転させるだけなら「rotate」で角度を指定してもできます。

tmp = img.rotate(45)

でも、ふと「これをライブラリ使わずにやったらどんな感じかな」と思いつきました。

ちょっとした頭の体操によさげなので、やってみることにします。

 

手書き数字の画像を使ってみる

手書き数字の画像を使ってみます。

28×28のモノクロの「9」の数字を使います。

それを回転させてみようと思います。

とりあえず、90度回転、180度回転、270度回転をやってみます。

元画像はこれです。

これを、こんな感じにしてみようというわけです。

 

さて・・やり方を考えてみます。

 

画像データを配列に変換して処理をする

手書き数字画像をpythonでNumpy配列に変換してやればよさげです。 

28×28なので、784バイトの数字の配列になります。 

[ 0 0 0 0 255 255 0 0 128 0 .........]みたいな感じですね。 

なので、回転させると言っても、実態としては配列データの入れ替えです。 

元の添字をもとにして回転の規則性にそって計算し、移動先の添字を求めて値を移動させるような処理になるはずです。

さて、回転の規則性の確認ですが、いきなり「28×28」でやると見づらいので、ここは小さなモデル「4×4」(一次元にした場合サイズ16の配列になる)でやってみます。

以下の表は、b(入力配列)の添え字に対応する値をResult(結果配列)の どの添え字に対応させれば、目指す結果(回転)になるかの規則性を整理してみたものです。

2つの変数「i」と「j」でやってみると、どうも「横幅(例では「4」)* i + j」という同じ式でセットすべき添字を計算できそうな規則性が見つかりました。

f:id:arakan_no_boku:20161203170110j:plain

これを横幅28に拡張してやってみます。

 

手書き数字画像データ加工のソース

ソースに落とし込んでみます。

画像データをnumpy配列に変換したり、numpy配列から画像イメージに戻すのには、「Pillow(PIL)」を使います。

なので、そのまま「Pillow(PIL)」で処理しないで、わざわざnumpy配列にして加工するのは、まったく無駄な処理ですけど・・まあ、頭の体操なので・・ご容赦を。

ソースは以下です。

import numpy as np
from PIL import Image


# 90度回転
def rotate90(x, w=28):
    size = len(x)
    outwk = [0] * size
    b = 0
    for j in range(0, w):
        for i in reversed(range(0, w)):
            outb = w * i + j
            outwk[outb] = x[b]
            b = b + 1
    out = np.array(outwk)
    return out


# 180度回転
def rotate180(x, w=28):
    size = len(x)
    outwk = [0] * size
    b = 0
    for i in reversed(range(0, w)):
        for j in reversed(range(0, w)):
            outb = w * i + j
            outwk[outb] = x[b]
            b = b + 1
    out = np.array(outwk)
    return out


# 270度回転
def rotate270(x, w=28):
    size = len(x)
    outwk = [0] * size
    b = 0
    for j in reversed(range(0, w)):
        for i in range(0, w):
            outb = w * i + j
            outwk[outb] = x[b]
            b = b + 1
    out = np.array(outwk)
    return out


img_array = np.array(Image.open('in/9.png').convert('L'))
tmp_img_array = rotate90(img_array.ravel())
img = Image.fromarray(tmp_img_array.reshape(28, 28))
img.save('out/9-1.png')
tmp_img_array = rotate180(img_array.ravel())
img = Image.fromarray(tmp_img_array.reshape(28, 28))
img.save('out/9-2.png')
tmp_img_array = rotate270(img_array.ravel())
img = Image.fromarray(tmp_img_array.reshape(28, 28))
img.save('out/9-3.png')

 

補足します。

関数が一次元の配列で渡ってくる想定しかしていないのと、元になる配列もモノクロ画像の二次元しか想定していないので、画像(例では inフォルダにある9.png)を読み込んでnumpy配列にするさいに

 np.array(Image.open('in/9.png').convert('L')) 

のように「convert('L')」でモノクロ化してます。

さらに、その二次元配列を

img_array.ravel()

のように「ravel()」を使って一次元化して関数の引数に渡してます。

一次元化する方法として、ravel()とflatten()の2種類あって結果は同じですが。flatten()は常にコピーを返すためにメモリを新たに確保する分、ravel()よりも遅い・・といわれてます・・まあ、どっちでも、この程度の処理では変わりませんが。

そうして一次元で処理しているので、再度画像に戻すときは

img = Image.fromarray(tmp_img_array.reshape(28, 28))

みたいに二次元化します。

動かしてみたら、いちおう目指す結果になりました。

実用性はほぼゼロですけど、頭の体操としてはOKです。

今回はこんなところで。

ではでは。