実践 コンピュータビジョンを読んで、出てきたサンプルコードを試したりいじったりする企画、その1-3。
NumPyを利用した画像処理。画像の表示にはMatplotlibを利用。配列に対して効果を加えることで画像にどういう変化が出るかを見るのは、やっていてけっこう楽しい作業。
NumPy, Matplotlib, Pillowは既に利用可能になっているものとする。
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)
これまた若干適当だけど、青っぽい数値が入っている。
デフォルトだと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 )
ちょっとマシになった。平均取ってるだけなのでこんなもんでしょう。
主成分分析してみる。試しに「あ」という文字の画像を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)