PIL(pillow)画像をnumpyにしたり戻したり

画像オブジェクトをnumpy配列に変換

import numpy as np
from PIL import Image
im = Image.open('sample20x15.bmp')
im

f:id:chiyoh:20190504145157p:plain

im_paltte = im.quantize() # パレットカラー 1chカラー
im_CMYK = im.convert('CMYK') # CMYK 4chカラー
im_grey = im.convert('L') # グレースケール 1chカラー
im_bw = im.convert('1') # 白黒 1chカラー
im_i = im.convert('I') # どのように変換されるかわからんが int型に変換
im_f = im.convert('F') #  どのように変換されるかわからんが float型に変換

print("--RGB--")
print("画像モード = ",im.mode)
print("省略")
print("--paltte--")
print("画像モード = ",im_paltte.mode)
print("省略")
print("--CMYK--")
print("画像モード = ",im_CMYK.mode)
print("画像サイズ(x,y) = ",im_CMYK.size)
print("pixel格納形式 = ",im_CMYK.getbands())
print("pixel(0,0) = ",im_CMYK.getpixel((0,0)))
print("pixel(0,0)の形式 = ",type(im_CMYK.getpixel((0,0))))
print("各チャンネルの形式 = ",type(im_CMYK.getpixel((0,0))[0]),type(im_CMYK.getpixel((0,0))[1]),
      type(im_CMYK.getpixel((0,0))[2]),type(im_CMYK.getpixel((0,0))[3]))
print("--grey--")
print("画像モード = ",im_grey.mode)
print("画像サイズ(x,y) = ",im_grey.size)
print("pixel格納形式 = ",im_grey.getbands())
print("pixel(0,0) = ",im_grey.getpixel((0,0)))
print("pixel(0,0)の形式 = ",type(im_grey.getpixel((0,0))))
print("各チャンネルの形式 = ",type(im_grey.getpixel((0,0)))) # 1chなのでそのまま
print("--binary--")
print("画像モード = ",im_bw.mode)
print("画像サイズ(x,y) = ",im_bw.size)
print("pixel格納形式 = ",im_bw.getbands())
print("pixel(0,0) = ",im_bw.getpixel((0,0)))
print("pixel(0,0)の形式 = ",type(im_bw.getpixel((0,0))))
print("各チャンネルの形式 = ",type(im_bw.getpixel((0,0)))) # 1chなのでそのまま
print("--int--")
print("画像モード = ",im_i.mode)
print("省略")
print("--float--")
print("画像モード = ",im_f.mode)
print("省略")
--RGB--
画像モード =  RGB
省略
--paltte--
画像モード =  P
省略
--CMYK--
画像モード =  CMYK
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('C', 'M', 'Y', 'K')
pixel(0,0) =  (55, 219, 76, 0)
pixel(0,0)の形式 =  <class 'tuple'>
各チャンネルの形式 =  <class 'int'> <class 'int'> <class 'int'> <class 'int'>
--grey--
画像モード =  L
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('L',)
pixel(0,0) =  101
pixel(0,0)の形式 =  <class 'int'>
各チャンネルの形式 =  <class 'int'>
--binary--
画像モード =  1
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('1',)
pixel(0,0) =  0
pixel(0,0)の形式 =  <class 'int'>
各チャンネルの形式 =  <class 'int'>
--int--
画像モード =  I
省略
--float--
画像モード =  F
省略

読み込んだ画像RGBオブジェクトを各モードの画像に変換した。いままで見てなかったCMYKとLグレスケと1白黒については内部データも確認しておく

numpy配列に変換

dat_RGB = np.asarray(im)
dat_paltte = np.asarray(im_paltte)
dat_CMYK = np.asarray(im_CMYK)
dat_grey = np.asarray(im_grey)
dat_bw = np.asarray(im_bw)
dat_i = np.asarray(im_i)
dat_f = np.asarray(im_f)
dat_paltte += dat_paltte/2
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-4-d0f6116c4334> in <module>
----> 1 dat_paltte += dat_paltte/2


ValueError: output array is read-only

書き換えNGのようだ

dat_paltte.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : False
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False
dat_paltte.flags.writeable = True
dat_paltte += dat_paltte/2
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-6-612dcc4be249> in <module>
----> 1 dat_paltte.flags.writeable = True
      2 dat_paltte += dat_paltte/2


ValueError: cannot set WRITEABLE flag to True of this array

なるほど、np.asarray()で変換した配列は参照ようなのね
np.array()を使って変換すると

dat2_paltte = np.array(im_paltte)
dat2_paltte.flags
  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

np.asarray()で変換すると配列書き換え不可、np.array()で行えば書き換え可能
だけど大体numpyの場合演算すると新しいオブジェクト生成されてしまうのでnp.asarray()で変換して速度優先にするって感じか

numpy配列に変換した中身

print("--RGB--")
print("オブジェクト型 = ",type(dat_RGB))
print("配列要素 = ",dat_RGB.dtype)
print("配列形 = ",dat_RGB.shape)
print("配列要素サイズ = ",dat_RGB.size)
print("シリーズに並べた時の隣軸との距離(Y軸,X軸,ch間) = ",dat_RGB.strides)
print("次元数 = ",dat_RGB.ndim)
print("1つの配列要素の長さ(バイト単位) = ",dat_RGB.itemsize)
print("使用されている総バイト数 = ",dat_RGB.nbytes)
print("メモリレイアウト情報 = ",dat_RGB.flags)
--RGB--
オブジェクト型 =  <class 'numpy.ndarray'>
配列要素 =  uint8
配列形 =  (15, 20, 3)
配列要素サイズ =  900
シリーズに並べた時の隣軸との距離(Y軸,X軸,ch間) =  (60, 3, 1)
次元数 =  3
1つの配列要素の長さ(バイト単位) =  1
使用されている総バイト数 =  900
メモリレイアウト情報 =    C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : False
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

基本のRGBを見るとRGBの各チャンネルに1軸を使っていて、3次元配列になっているのね

print("ベースになるオブジェクト = ",dat_RGB.base)
print("len(base) = ",len(dat_RGB.base))
print("配列の先頭のオブジェクト = ",dat_RGB.data)
print("type(data) = ",type(dat_RGB.data))
print("len(data) = ",len(dat_RGB.data))
ベースになるオブジェクト =  b"\xc8$\xb3\xc5%\xb5\xc0%\xb6\xbc%\xb7\xb8&\xb9\xb3&\xbb\xad'\xbc\xa9(\xbe\xa4(\xbf\x9e)\xc2\x99)\xc4\x94*\xc5\x8e+\xc7\x89+\xc9\x84,\xcb~,\xccx-\xcfs.\xd0n.\xd2h.\xd4\xc5$\xb4\xc1$\xb6\xbd%\xb7\xb8&\xb9\xb3'\xba\xae'\xbb\xa9(\xbe\xa4(\xbf\x9f(\xc1\x9a)\xc3\x94*\xc5\x90*\xc7\x8a+\xc8\x85+\xca\x7f,\xcdz-\xcet-\xd0n.\xd2i/\xd4c/\xd6\xc2$\xb5\xbe%\xb6\xb9&\xb8\xb5&\xba\xaf'\xbc\xaa(\xbd\xa6(\xbf\xa1(\xc1\x9c)\xc2\x96*\xc4\x90+\xc6\x8b+\xc8\x86+\xca\x80,\xcc{-\xceu-\xd0p.\xd1j.\xd4e/\xd5_0\xd7\xbe%\xb6\xba%\xb8\xb5&\xba\xb1'\xbb\xac'\xbc\xa7'\xbf\xa2)\xc0\x9c)\xc2\x97*\xc4\x91*\xc6\x8c*\xc8\x86+\xca\x81,\xcc|,\xcdv-\xcfq.\xd2k.\xd3f/\xd5a0\xd7\\0\xd8\xbb%\xb8\xb6&\xba\xb2'\xbb\xac'\xbd\xa8'\xbe\xa3(\xc0\x9d(\xc2\x98)\xc3\x92*\xc5\x8e+\xc7\x87+\xc9\x83,\xcb}-\xcdw.\xcfr-\xd0l.\xd3h/\xd5a0\xd6]1\xd8W1\xda\xb7&\xb9\xb3'\xbb\xad&\xbc\xa9'\xbe\xa4(\xc0\x9e(\xc1\x99*\xc3\x93*\xc5\x8f*\xc7\x89+\xc9\x83,\xcb~,\xccy-\xcfs.\xd0n/\xd2h.\xd4b/\xd6^0\xd7X0\xd9S1\xdb\xb4&\xba\xaf'\xbc\xaa'\xbe\xa4(\xbf\xa0)\xc2\x9a)\xc3\x95*\xc5\x8f+\xc6\x8a+\xc9\x85,\xca\x7f,\xccz-\xcet-\xd0o.\xd2j/\xd3d/\xd5_0\xd7Y0\xdaT1\xdbO2\xdd\xb0'\xbb\xab'\xbd\xa5'\xbf\xa0)\xc0\x9c)\xc2\x96)\xc4\x90*\xc6\x8c*\xc8\x85+\xca\x80,\xcc{-\xceu-\xcfp.\xd1k/\xd3e0\xd5_0\xd7[0\xd9U1\xdaP1\xddK2\xde\xab'\xbd\xa6(\xbf\xa2(\xc0\x9d)\xc2\x97)\xc4\x92*\xc6\x8c+\xc8\x87+\xc9\x82,\xcc|,\xcdw-\xd0q.\xd1l/\xd3f/\xd5a0\xd7\\1\xd9W1\xdaQ2\xdcL2\xdeG3\xdf\xa8(\xbe\xa3)\xc0\x9e)\xc2\x99)\xc4\x92*\xc6\x8d+\xc8\x88+\xc9\x82,\xcb},\xcdx-\xcfr-\xd0m.\xd3g/\xd4b/\xd6]0\xd8X1\xd9R1\xdcM2\xddH2\xdfC3\xe1\xa4(\xbf\x9e(\xc1\x99*\xc3\x94*\xc6\x8f+\xc6\x89+\xc9\x84,\xca\x7f,\xcdy-\xces.\xd0n.\xd2h/\xd4c/\xd6^0\xd8X0\xd9S2\xdbN2\xddI2\xdfD3\xe0?3\xe3\x9f(\xc1\x9a)\xc3\x95*\xc5\x90+\xc7\x8b*\xc9\x85+\xca\x7f,\xccz-\xceu.\xd0o.\xd2j.\xd4d/\xd6_0\xd7Z0\xd9T1\xdbO2\xdcJ2\xdfE2\xe0A3\xe2<4\xe3\x9c)\xc2\x96*\xc4\x91*\xc6\x8c+\xc8\x86+\xca\x81,\xcc{-\xceu-\xcfq.\xd2j/\xd3f/\xd5`0\xd7Z1\xd8U1\xdaP2\xdcK2\xdeF3\xe0B3\xe1=4\xe384\xe5\x97)\xc4\x92*\xc6\x8d+\xc7\x87,\xca\x81,\xcb|-\xcdw-\xcfq.\xd1k/\xd3f/\xd5a/\xd7\\0\xd8V1\xdbR1\xdcL2\xdeG2\xdfB3\xe1>4\xe394\xe455\xe5\x93*\xc5\x8e+\xc7\x89,\xc9\x82,\xca},\xcdx-\xcer.\xd0m.\xd2h/\xd4b/\xd6]0\xd8X0\xdaR1\xdcN1\xddI2\xdfC3\xe1?3\xe3;4\xe465\xe615\xe6"
len(base) =  900
配列の先頭のオブジェクト =  <memory at 0x000001AEDC7E67C8>
type(data) =  <class 'memoryview'>
len(data) =  15

なるほど、baseはim.tobytes()な感じかな。dataは列要素に入っている行要素が15という感じで行要素にはch要素の3があるって感じなのかな。

各要素にアクセス

print("pixel(0,0) = ",im.getpixel((0,0)))
print("numpy配列(0,0) = ",dat_RGB[0,0])
print("pixel(1,0) = ",im.getpixel((1,0)))
print("numpy配列(1,0) = ",dat_RGB[1,0])
print("numpy配列(0,1) = ",dat_RGB[0,1])
print("numpy配列(0,1,:) = ",dat_RGB[0,1,:])
print("numpy配列(0,1)R = ",dat_RGB[0,1,0])
print("numpy配列(0,1)G = ",dat_RGB[0,1,1])
print("numpy配列(0,1)B = ",dat_RGB[0,1,2])
print("numpy配列(Y軸=0) = ",dat_RGB[0])
pixel(0,0) =  (200, 36, 179)
numpy配列(0,0) =  [200  36 179]
pixel(1,0) =  (197, 37, 181)
numpy配列(1,0) =  [197  36 180]
numpy配列(0,1) =  [197  37 181]
numpy配列(0,1,:) =  [197  37 181]
numpy配列(0,1)R =  197
numpy配列(0,1)G =  37
numpy配列(0,1)B =  181
numpy配列(Y軸=0) =  [[200  36 179]
 [197  37 181]
 [192  37 182]
 [188  37 183]
 [184  38 185]
 [179  38 187]
 [173  39 188]
 [169  40 190]
 [164  40 191]
 [158  41 194]
 [153  41 196]
 [148  42 197]
 [142  43 199]
 [137  43 201]
 [132  44 203]
 [126  44 204]
 [120  45 207]
 [115  46 208]
 [110  46 210]
 [104  46 212]]

PILでは、(x,y)のタプルで表現していたがnumpy配列に変換すると[y,x,ch]の順番になる。x,yの順番が逆なので注意
[y,x]と表現すれば[R G B]の3要素が表現できる。[0]1次元表記すると(X軸とch)の要素すべてが表現できるのね

1チャンネルのパレットカラーの場合はどうか?

print("--paltte--")
print("オブジェクト型 = ",type(dat_paltte))
print("配列要素 = ",dat_paltte.dtype)
print("配列形 = ",dat_paltte.shape)
print("配列要素サイズ = ",dat_paltte.size)
print("シリーズに並べた時の隣軸との距離(Y軸,X軸,ch間) = ",dat_paltte.strides)
print("次元数 = ",dat_paltte.ndim)
print("1つの配列要素の長さ(バイト単位) = ",dat_paltte.itemsize)
print("使用されている総バイト数 = ",dat_paltte.nbytes)
print("メモリレイアウト情報 = ",dat_paltte.flags)
--paltte--
オブジェクト型 =  <class 'numpy.ndarray'>
配列要素 =  uint8
配列形 =  (15, 20)
配列要素サイズ =  300
シリーズに並べた時の隣軸との距離(Y軸,X軸,ch間) =  (20, 1)
次元数 =  2
1つの配列要素の長さ(バイト単位) =  1
使用されている総バイト数 =  300
メモリレイアウト情報 =    C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : False
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

その他の画像

def print_np_type(dat):
    print("配列要素 = ",dat.dtype)
    print("次元数 = ",dat.ndim)
    print("配列形 = ",dat.shape)
    print("配列要素サイズ = ",dat.size)

print("--CMYK--")
print_np_type(dat_CMYK)
print("--gray--")
print_np_type(dat_grey)
print("--白黒--")
print_np_type(dat_bw)
print("--int--")
print_np_type(dat_i)
print("--float--")
print_np_type(dat_f)
--CMYK--
配列要素 =  uint8
次元数 =  3
配列形 =  (15, 20, 4)
配列要素サイズ =  1200
--gray--
配列要素 =  uint8
次元数 =  2
配列形 =  (15, 20)
配列要素サイズ =  300
--白黒--
配列要素 =  bool
次元数 =  2
配列形 =  (15, 20)
配列要素サイズ =  300
--int--
配列要素 =  int32
次元数 =  2
配列形 =  (15, 20)
配列要素サイズ =  300
--float--
配列要素 =  float32
次元数 =  2
配列形 =  (15, 20)
配列要素サイズ =  300

なるほど、色要素としてチャンネルがあるものは3次元、無いのは縦横の2次元
要素タイプは種別ごとに違う感じか

numpy配列からPIL(pillow)オブジェクトに

im = Image.frombytes()でなく、Image.fromarray()のほうがいろいろとケアしてくれるみたいです 配列情報からサイズや画像モードなんかを読み取ってくれるみたいです

im2_RGB = Image.fromarray(dat_RGB)
im2_paltte = Image.fromarray(dat_paltte)
im2_CMYK = Image.fromarray(dat_CMYK)
im2_grey = Image.fromarray(dat_grey)
im2_bw = Image.fromarray(dat_bw)
im2_i = Image.fromarray(dat_i)
im2_f = Image.fromarray(dat_f)

def print_image_type(im):
    print("画像モード = ",im.mode)
    print("画像サイズ(x,y) = ",im.size)
    print("pixel格納形式 = ",im.getbands())
    print("pixel(0,0) = ",im.getpixel((0,0)))
    print("pixel(0,0)の形式 = ",type(im.getpixel((0,0))))

def print_image_type2(im):
    print_image_type(im)
    print("pixel要素形式 = ",type(im.getpixel((0,0))[0]))
    
print("--RGB--")
print_image_type2(im2_RGB)
print("--CMYK--")
print_image_type2(im2_CMYK)
print("--パレット--")
print_image_type(im2_paltte)
print("--グレスケ--")
print_image_type(im2_grey)
print("--白黒--")
print_image_type(im2_bw)
print("--int--")
print_image_type(im2_i)
print("--float--")
print_image_type(im2_f)
--RGB--
画像モード =  RGB
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('R', 'G', 'B')
pixel(0,0) =  (200, 36, 179)
pixel(0,0)の形式 =  <class 'tuple'>
pixel要素形式 =  <class 'int'>
--CMYK--
画像モード =  RGBA
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('R', 'G', 'B', 'A')
pixel(0,0) =  (55, 219, 76, 0)
pixel(0,0)の形式 =  <class 'tuple'>
pixel要素形式 =  <class 'int'>
--パレット--
画像モード =  L
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('L',)
pixel(0,0) =  0
pixel(0,0)の形式 =  <class 'int'>
--グレスケ--
画像モード =  L
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('L',)
pixel(0,0) =  101
pixel(0,0)の形式 =  <class 'int'>
--白黒--
画像モード =  1
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('1',)
pixel(0,0) =  0
pixel(0,0)の形式 =  <class 'int'>
--int--
画像モード =  I
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('I',)
pixel(0,0) =  101
pixel(0,0)の形式 =  <class 'int'>
--float--
画像モード =  F
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('F',)
pixel(0,0) =  101.33799743652344
pixel(0,0)の形式 =  <class 'float'>

結果は、'P'と'L','CMYK'と'RGBA'が元に戻らない感じですね
チャンネルの数と要素タイプで区別しているみたいです

チャンネル 1 タイプ boolが '1'
チャンネル 1 タイプ uint8が 'L'
チャンネル 1 タイプ intが 'I'
チャンネル 1 タイプ floatが 'F'
チャンネル 3 タイプ uint8が 'RGB'
チャンネル 4 タイプ uint8が 'RGBA'
に自動変換でしょうか

Image.fromarray(obj, mode=None)なのでパラメータでモードを指定すれば問題無し

im3_paltte = Image.fromarray(dat_paltte,'P')
im3_CMYK = Image.fromarray(dat_CMYK,'CMYK')

print("--パレット--")
print_image_type(im3_paltte)
print("--CMYK--")
print_image_type2(im3_CMYK)
--パレット--
画像モード =  P
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('P',)
pixel(0,0) =  0
pixel(0,0)の形式 =  <class 'int'>
--CMYK--
画像モード =  CMYK
画像サイズ(x,y) =  (20, 15)
pixel格納形式 =  ('C', 'M', 'Y', 'K')
pixel(0,0) =  (55, 219, 76, 0)
pixel(0,0)の形式 =  <class 'tuple'>
pixel要素形式 =  <class 'int'>

パレットカラーモードについて

dat_paltte = np.asarray(im_paltte)で配列に変換してもこれだけではIndexカラー番号のみが変換されて
色情報がありません。パレットカラーを取り扱う必要があります
data=im.getpalette()im.putpalette(data)でパレットの情報の出し入れが出来る
パレット情報はリスト形式の[R0,G0,B0,R1,G1,B1, ... R255,G255,B255]なので(R+G+B)*256 = 768要素になる

pc_list = im_paltte.getpalette()
print("形式 = ",type(pc_list))
print("要素数 = ",len(pc_list))
print("要素数形式 = ",type(pc_list[0]))
形式 =  <class 'list'>
要素数 =  768
要素数形式 =  <class 'int'>

pythonが通常扱えるリストで、整数形式の768要素になっている。numpy配列にする

pc_array = np.array(im_paltte.getpalette())
print_np_type(pc_array)
print("頭から16要素 = ",pc_array[0:16])
print("--画像っぽくRGBを分ける--")
pc_array2 = pc_array.reshape((256,3))
print_np_type(pc_array2)
print("インデックス番号5の色 = ",pc_array2[5])
配列要素 =  int32
次元数 =  1
配列形 =  (768,)
配列要素サイズ =  768
頭から16要素 =  [200  36 179 197  37 181 197  36 180 194  36 181 193  36 182 192]
--画像っぽくRGBを分ける--
配列要素 =  int32
次元数 =  2
配列形 =  (256, 3)
配列要素サイズ =  768
インデックス番号5の色 =  [192  37 182]

reshapeすれば扱いやすくなる。例えばインデックスカラーの1番を黒にするとかカラーオブジェクトを使っていろいろできるかも

pc_array2[1]= (0,0,0) #インデックス2を黒に
pc_list = pc_array2.tolist()
print("頭から8 = ",pc_list[0:8])
pc_list2 = pc_array2.flatten().tolist()
print("頭から8 = ",pc_list2[0:8])
頭から8 =  [[200, 36, 179], [0, 0, 0], [197, 36, 180], [194, 36, 181], [193, 36, 182], [192, 37, 182], [190, 37, 182], [189, 37, 183]]
頭から8 =  [200, 36, 179, 0, 0, 0, 197, 36]

numpy配列にしたものも、flatten()tolist()を使えば768要素の1次元配列にリストに戻せるので
その後im.putpalette(data)すればよい f:id:chiyoh:20190504145157p:plain