すだちキャンパス

すだちキャンパス

やってみたこと、学んだことなどのメモ。

Pythonで画像処理〜基本とウォーリー探し〜

こんにちは。久々の更新となってしまいました・・・
下書きだけ書いて大量に溜めている状態なので、徐々に更新していきたいと思います。

さて、今期の授業でPythonの画像処理を扱うので、自分のメモ的に書いていきたいと思います。

環境

jupyter notebook 5.7.0
python 3.7
numpy 1.15.2
scipy 1.1.0
matplotlib 3.0.0

openCVは使わない方針(みたい)です。

画像の読み込みと表示

jupyter notebook では、最初に

%matplotlib inline

と書いておかないと画像が表示されないようです。

import matplotlib.pyplot as plt

#画像の読み込み
img = matplotlib.pyplot.imread('image.png')

#画像の表示
plt.figure(1)
plt.imshow(img, interpolation='none')

#グレースケールで表示
img_gray = img[:, :, 0]
plt.figure(2)
plt.imshow(img_gray, cmap='gray', interpolation='none')
plt.colorbar()

f:id:sweetgohan:20191029033115p:plain
f:id:sweetgohan:20191029033106p:plain
こんな感じで表示されます。

ガウシアンフィルタ

ガウシアンフィルタとは、平滑化(ぼかし)を行うフィルタのようです。
原理としては、ある画像(行列)に対してあるフィルタ(行列)でたたみ込み演算を行うことでその結果(行列)が新たな画像として出てくる
といった感じです。
scipyには関数が実装されているのですが、関数を使わない方法も書きました。

import scipy.ndimage as nd

#実装されている関数
img_smoothed = nd.filters.gaussian_filter(img_gray,1)

#関数を使わない方法
#重み(カーネル)?の計算(式に代入しただけ・9*9の行列の場合です)
sigma = 1.0
kernel_gauss = np.full((9,9),0.0)
a = np.arange(-4.0, 5.0)
nums = np.arange(0, 9)

for i in nums:
    for j in nums:
        a[j] = math.exp(-((i-4)**2 + (j-4)**2) / (2 * sigma**2)) / (2 * math.pi * sigma**2)
    kernel_gauss[i] = a

#たたみ込みの計算
beach_smoothed = nd.filters.convolve(img_gray, kernel_gauss)

f:id:sweetgohan:20191029033319p:plain

たたみ込みを計算する関数が

nd.filters.convolve()
nd.convolve()

の2つあるのですが、違いがあまりわかりませんでした・・・。

ラプラシアンフィルタ

ラプラシアンフィルタは、画像の輪郭を取り出すフィルタだそうです。

#実装されている関数
img_edges = nd.filters.laplace(img_gray)

#関数を使わない方法
#カーネルは決まっているので与えるだけ
kernel_laplace =  kernel_laplace =  np.array([np.ones(9), np.ones(9), np.ones(9), [1, 1, 1, -8, -8, -8, 1, 1, 1], [1, 1, 1, -8, -8, -8, 1, 1, 1], [1, 1, 1, -8, -8, -8, 1, 1, 1], np.ones(9), np.ones(9), np.ones(9)])

#たたみ込みの計算
img_edges = nd.convolve(img_smoothed, kernel_laplace)

f:id:sweetgohan:20191029034903p:plain

画像の中から特定の部分を探す

いわゆるウォーリーを探せ的な事をする時に使います。
相関係数を求めることで画像のどこが探したい画像に一番似ているのかがわかります。

#相関係数を求める
correlation = nd.correlate(img_gray, wally_edges)

#その中の最大値を抜き出す
index = correlation.argmax()

#最大値が画像のどの部分なのかを求める
sh = np.unravel_index(index, correlation.shape)

#求めた場所が画像の中心なので幅と高さを指定して画像を表示
cs = wally.shape
img_cropped = beach[sh[0]-int(cs[0]/2):sh[0]+int(cs[0]/2), sh[1]-int(cs[1]/2):sh[1]+int(cs[1]/2)]

plt.figure(3)
plt.subplot(121)
plt.imshow(wally, interpolation='none')
plt.subplot(122)
plt.imshow(img_cropped, interpolation='none')

f:id:sweetgohan:20191029035548p:plain

たたみ込み演算の実装

最後に、たたみ込み演算を行うコードも実装したのでメモしておきます。

愚直に計算するバージョン
#カーネルを与える
kernel = np.diag(np.full(5, 0.2))

#画像とカーネルの行列の大きさ
h, w = img.shape
kh, kw = kernel.shape

#端から計算していく
for y in range(h):
    for x in range(w):
        # initialize result for pixel at y, x
        val = 0.
        # loop over all pixels in kernel
        for j in range(kh):
            for i in range(kw):
                imageY = y - 2 + j
                imageX = x - 2 + i
                if(imageY < 0 or imageY >= h):
                    imageY = 0
                if(imageX < 0 or imageX >= w):
                    imageX = 0
                val += img[imageY, imageX] * kernel[j, i]
        result_explicit[y, x] = val

plt.figure(4)
plt.subplot(121)
plt.imshow(img, cmap='gray', interpolation='none')
plt.subplot(122)
plt.imshow(result_explicit, cmap='gray', interpolation='none')

f:id:sweetgohan:20191029213148p:plain

フーリエ変換を用いるバージョン
#カーネルを与える
kernel = np.diag(np.full(5, 0.2))

#元画像のフーリエ変換
img_ft = np.fft.fft2(img)

#カーネルが元画像と同じサイズの行列になるようにゼロパディングを行う?
kernel_pad = np.zeros_like(img, dtype=float)
kernel_pad[h//2-kh//2:h//2+kh//2+1, w//2-kw//2:w//2+kw//2+1] = kernel
kernel_pad = np.fft.ifftshift(kernel_pad)

#カーネルのフーリエ変換
kernel_ft = np.fft.fft2(kernel_pad)

#元画像とカーネルを掛けて逆フーリエ変換して画像出力
#実部を取り出すために.realを用いている
result_fourier = np.fft.ifft2(img_ft * kernel_ft).real

f:id:sweetgohan:20191029220458p:plain