よちよちpython

独習 python/Qpython/Pydroid3/termux

画像ファイルとNumpy配列の形状

画像をNumpy配列に変換したときの配列の形状についてのメモ。JupyterNotebookで実行中。



作業フォルダ内のpng画像ファイルを全取得します。

import glob

# 拡張子pngファイルをリストで取得
im_file_list = glob.glob("*.png")
# リストを表示
im_file_list
['scatter1.png',
 'scatter2.png',
 'scatter3.png',
 'upside_right.png',
 'gray.png',
 'rightside_left.png',
 'gyouretsu.png',
 'upside_down.png',
 'whiteside_black.png',
 't.png',
 'im_start.png',
 't2.png',
 'im_90.png',
 'irochigai.png',
 'image_ud.png']



これらをpillowを使って全てNumpy配列に変換し、shapeで形状を表示します。

import numpy as np
from PIL import Image

for i in im_file_list:
    print(np.array(Image.open(i)).shape)
(288, 432, 4)
(288, 432, 4)
(288, 432, 4)
(90, 360, 4)
(180, 180)
(180, 180, 4)
(180, 180, 4)
(180, 180, 4)
(180, 180, 4)
(180, 180, 4)
(90, 90)
(180, 90, 4)
(90, 90)
(180, 180, 4)
(1024, 687, 3)



それぞれの画像の形状が表示されました。

  • 次元 : 3つが二次元で、その他は三次元

  • 三次元形状一番右の値 : 一つだけが3で、残りは4



画像を配列に変換すると、通常は三次元配列になります。



色は3原色を組み合わせて作ります。コンピュータではRGBカラーモデルというもので赤・緑・青を混ぜて色を表現させています。RGBに透明度のパラメータAlpha値を加えたものをRGBAカラーモデルといいます。
パラメータがRGBかRGBAかの違いが、配列形状の3つ目の「3」か「4」の違いになって現れている。



上の画像リストで二次元配列になっているものは、pillowのconvert("L")のような関数を使って「グレイスケール」と呼ばれる白黒写真に変換したので二次元になっています。
「グレイスケール」は機械学習で画像分類などを行う際に、計算量を減らす目的で頻繁に使われます。OpenCVを使って顔検出などやる時も画像を一旦グレイスケールに変換しますね。参考までhttps://chayarokurokuro.hatenablog.com/entry/2019/07/01/Python/openCVで画像の顔検知



ところで、三次元配列の作り方


画像のことは忘れて、基本的なことに立ち返ります。三次元配列を作る。



小学校で数直線を習います。X軸上に点を取ったり。一次元配列。

# 要素が4個の配列
X = np.arange(4)
X
array([0, 1, 2, 3])



算数の授業はやがて、X軸だけの数直線にY軸の次元を加えたX-Y直交座標へ進んでいきます。
一次元配列をあと2つ作ります。

X2 = X + 4
X2
array([4, 5, 6, 7])
X3 = X + 8
X3
array([ 8,  9, 10, 11])

出来た3つの一次元配列をリストにしてNumpyに渡します。

x_y = np.array([X, X2, X3])
x_y
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
x_y.ndim
2

X軸に4個の値のある一次元配列を3つリストにしてNumpyに渡したら、二次元で平面的な行列ができました。
新しい次元軸をy軸とします。

では次。二次元配列をもう一個つくります。

x_y2 = x_y + 12
x_y2
array([[12, 13, 14, 15],
       [16, 17, 18, 19],
       [20, 21, 22, 23]])
x_y2.ndim
2

2個の二次元配列が出来たので、その2個の配列をリストでNumpyに渡します。。

x_y_z = np.array([x_y, x_y2])
x_y_z
array([[[ 0,  1,  2,  3],
        [ 4,  5,  6,  7],
        [ 8,  9, 10, 11]],

       [[12, 13, 14, 15],
        [16, 17, 18, 19],
        [20, 21, 22, 23]]])
print("次元", x_y_z.ndim)
print("形状", x_y_z.shape)
次元 3
形状 (2, 3, 4)

三次元で、形状(2, 3, 4)の配列が作られました。

数直線x軸上的な所に並ばせた4個の一次元配列を、3つ作ってy軸に並ばせた二次元配列を、2つ作ってz軸に並ばせた。

形状(2, 3, 4)は(z, y, x)の軸になってますよね。



画像の次元軸


画像に戻ります。

上で、形状は(z, y, x)の軸になっていると書きました。
一番最初に画像のリストから形状を取り出した際に、形状の一番右の値はほとんどが4でした。

画像を、回転させたり、上下左右ひっくり返したり、切り貼りするような、位置に関わる処理をナンボやっても、実は常に画像の3次元配列形状の一番右は4(または3)で固定されたままになります。x軸は動かない。yとz軸の所だけが入れ替わったりする。

ということは、画像を見る時はy-z軸平面を垂直に見ているという事になると言える。



たとえば上の配列を画像からNumpy配列に変換したものとし、元の画像を左回りに90度回転させるような処理をしてみると、

print("元の配列\n", x_y_z)
print("\n左回り90度回転処理後の配列\n", np.rot90(x_y_z, 1))
print("\n元の配列形状",x_y_z.shape)
print("回転処理後の配列形状", np.rot90(x_y_z, 1).shape)
元の配列
 [[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]

左回り90度回転処理後の配列
 [[[ 8  9 10 11]
  [20 21 22 23]]

 [[ 4  5  6  7]
  [16 17 18 19]]

 [[ 0  1  2  3]
  [12 13 14 15]]]

元の配列形状 (2, 3, 4)
回転処理後の配列形状 (3, 2, 4)

形状は2と3が入れ替わっておりますが、4の所は動いていない。上下左右反転させても4は動かない。
トリミングの場合も4は動かない。(z, y, x)のz軸=画像の行(縦)、y軸=列(横)としてスライスや関数で処理したりします。
色をいじる以外はx軸には触らない。



ということでいいんでしょうかw
画像処理用のライブラリを使えばこんなこと考えなくても関数でやってくれるが、Numpyでやろうとすると配列操作と画像の関係が分かっとかないと出来ないんですよね。狙った加工にならないとかエラーが出るとか。etc.

以上、現時点での理解。