NumPyで画像処理

概要

実践 コンピュータビジョンを読んで、出てきたサンプルコードを試したりいじったりする企画、その1-3。

NumPyを利用した画像処理。画像の表示にはMatplotlibを利用。配列に対して効果を加えることで画像にどういう変化が出るかを見るのは、やっていてけっこう楽しい作業。

NumPy, Matplotlib, Pillowは既に利用可能になっているものとする。

@CretedDate 2015/01/22
@Versions Python 2.7.6, NumPy 1.9.1

画像の読み込みと表示

foo.jpgという名前で適当な画像ファイルを置いておく。

# 必要なものをimport
from PIL import Image
import numpy as np
from matplotlib import pylab as plt

# 画像の読み込み
img = np.array( Image.open('foo.jpg') )

# 画像の表示
plt.imshow( img )

配列の内容確認

下記のような赤青黄緑白黒が並ん4*2ピクセルの画像で、配列の中身を確認してみる。

サンプル画像

拡大してるのでボケてるけど、実物はしっかり色分けされた8ixelの画像。

shapeで次元を確認。

img = np.array( Image.open('foo.jpg') )
img.shape
  #=> (2, 4, 3)

2 * 4 * 3のshapeと表示される。画像は2 * 4で、1pixelごとにrgbの3が付くので。

左上のピクセル(赤いはず)を見てみる。

img[0][0]
  # array([252,   0,   0], dtype=uint8)

適当にカラーチェッカーから選んだので255じゃない赤になってるけど、だいたい赤っぽい数値。

右隣りのピクセル(青いはず)を見てみる。

img[0][1]
  # array([  0,  28, 249], dtype=uint8)

これまた若干適当だけど、青っぽい数値が入っている。

floatでの読み込み

デフォルトだとdtypeがuint8になってるので、floatで読み込ませてみる。

img = np.array( Image.open('foo.jpg'), 'f' )
plt.imshow( img )

サンプル画像

おー、なんか凄いことになった。値は普通なんだけどね。

imp.shape
  #=> (2, 4, 3)

img[0][0]
  #=> array([ 252.,    0.,    0.], dtype=float32)

convert('L')でグレースケールにして出してみる。

img = np.array( Image.open('foo.jpg').convert('L'), 'f' )

# グレースケールだと2*4の情報だけになる
img.dtype
  #=> (2, 4)

plt.imshow( img )

サンプル画像

グレースケール画像の明るさ反転

サンプルとしてこの画像を使う。

img = np.array( Image.open('foo.jpg').convert('L'), 'f' )
plt.imshow( img )

サンプル画像

グレースケールは1pixelごとに0〜255の値が振られているので、255 - xとすれば各pixelの明るさが反転する。

img = 255 - img
plt.imshow( img )

サンプル画像

街頭が黒く、闇夜が白く、キレイに反転した。

グレースケールの明るさの幅を狭く

0〜255の値を50を最大にしてみる。黒くなるはず。

img = np.array( Image.open('foo.jpg').convert('L'), 'f' )
img = np.minimum(img, 50)
plt.imshow( img )

サンプル画像

あれ? 白い。

imshowは与えられた値のminとmaxを加味して絵を表示しているらしい。全部50以下にしたので50がMAX=白だと思い込んでいるらしい。

255がMAXであることを伝えてshowする。

img = np.array( Image.open('foo.jpg').convert('L'), 'f' )
img = np.minimum(img, 50)
plt.imshow( img, vmin=0, vmax=255 )

サンプル画像

おっけー。ちゃんと暗くなった。

ヒストグラム平坦化

ヒストグラムが平坦になるようにすると、画像がより鮮明になるらしい。試しにやってみよう。

まずはヒストグラムを普通に取ってplotしてみる。使う画像はさっきの夜景。numpy.histogramを利用。

# ヒストグラムの取得
img = np.array( Image.open('foo.jpg').convert('L') )
hist, bins = np.histogram( img.flatten(), bins=256 )

# 0〜256までplot
plt.plot( hist )
plt.xlim(0, 256)

サンプル画像

暗い画像なので色のほとんどが0〜40付近に集中していることがわかる。これがもっと平坦になれば見やすくなる?

書籍のサンプルコードを参考に変換してみる。まずはcumsumは累積分布を取る。

# 累積分布を取る
cdf = hist.cumsum()
# 正規化(0〜255の分布にする)
cdf = 255 * cdf / cdf[-1]

# とりあえずここまででplot
plt.plot( cdf )
plt.xlim( 0, 256 )
plt.ylim( 0, 256 )

サンプル画像

累積分布で見ると、150のところで全体のほとんど(249/255)を占めている。これでは、いけませんね。これが0:0〜255:255に向けての直線に近くなるのが理想。

次にnumpy.interpで線形補完。binsは長さが257なので最後の1つを省いて渡す。

# 線形補間
img2 = np.interp( img.flatten(), bins[:-1], cdf)

# 出来上がった配列のhistogramを見てみる
hist2, bins2 = np.histogram( img2, bins=255 )
plt.plot( hist2 )
plt.xlim(0, 256)

サンプル画像

最初と比べるとだいぶ均された感がある。

じゃ、これで画像を表示してみる。

plt.imshow( img2.reshape( img.shape ) )

変換前

サンプル画像

変換後

サンプル画像

なるほど、こうなるのか。

平均画像

2つの画像の平均値を出してみる。試しに上の夜景の画像にうさぎの画像を混ぜてみる。

透明部分を使いたいのでどちらもアルファチャンネルを追加して保存しておく。

img1 = np.array( Image.open('foo.png') )
img2 = np.array( Image.open('bar.png') )

img1.shape
  #=> (500, 750, 4)

するとこれまでshapeのtupleは3つ目の要素が3(RGB)を返していたのが、4(RGBA)になってる。

利用するうさぎ画像。背景は透明化してある。

サンプル画像

# 足して2で割りゃ平均が出る
img3 = (img1 + img2) / 2

# 表示
plt.imshow( img3 )

サンプル画像

ありゃ、なんかアカン感じになった。多分、透明と不透明の画像を混ぜたのでその辺の値が全部127になってしまってる。

ということで透明化を解除。

mg3[:,:,3] = 255
plt.imshow( img3 )

サンプル画像

ちょっとマシになった。平均取ってるだけなのでこんなもんでしょう。

主成分分析(PCA)

主成分分析してみる。試しに「あ」という文字の画像を20枚ほど集めて、すべて64*64で保存する。

# 20枚読み込む
images = [np.array(Image.open('a/a{0}.png'.format(i))) for i in range(1, 21) ]

# 並べて表示してみる
for i in range(1, 21):
  plt.subplot(5, 4, i)
  plt.imshow( images[i - 1] )

マウスで書いたので酷いことになってるけど、まあ、「あ」と認識できる形にはなってるか。

サンプル画像

linalg.svdを使って特異値分解してみる。

# flattenしたimageを使う
images = [np.array( Image.open('a/a{0}.png'.format(i)).convert('L'), 'f' ).flatten() for i in range(1, 21)]
images = np.concatenate( [images], axis=0 )

num_data, dim = images.shape
  #=> 20, 4096

# 特異値分解
U, S, V = np.linalg.svd( images )
V = V[:num_data]

# 結果を表示
import matplotlib.cm as cm
for i in range(0, 20):
  plt.subplot(5, 4, i)
  plt.imshow(V[i].reshape(64, 64), cmap = cm.Greys_r)

サンプル画像