本サイトは広告・プロモーションが含まれています
Python

【Numpy】画像の平滑化【画像をぼやかす】

工場で画像を扱う時は、検査や確認のためが多いため
「見やすい画像に編集する」など画像をくっきりさせる事が多いです。

しかし、まれですが画像をぼかす、ちょっとぼかし方をこだわった画像を依頼されることが
あります。

もちろん有力ソフトでもできる内容ですが
Pythonでも簡単に実装できますし、なにより関数を使うことで
自分が思うような編集も可能なので、是非この記事の内容を活用してください。

まえおき

今回も神奈川工科大学さんのwebサイトから標準画像データベースSIDBAを使わせてもらいました。

以前の記事では、入力画像の画素ごとのピクセルを関数を使って変換して画像をはっきりさせてました。
こちらの知識があると、より今回の記事が面白く感じるかもしれません。

ただ今回は入力画像の対応する画素値だけでなく、周辺の画素も含めた領域内の画素値を使って計算していきます。

平均化

同一な値を使った平均化

ぼやけた画像とは、ピントがくっきりあっている画像に比べて、となりあった画素値の値の差が小さい画像になります。
それを実現するのが平均化フィルタというものです。

例えば3×3画素の平均化フィルタを画像にかけた例を数値で図示するとこんな感じになります。

ちょっとカッコいい感じに書きましたが、やっていることは
赤い画素を中心に周りの画素を足し合わせて1/9するというシンプルなものです。
そうすると赤マスと青マスの画素値の差が、50→17(ななめだと34)と小さくなります。

実際に画像に平均化フィルタをかけた例を以下に示します。

左がオリジナルのボートの写真で、3×3, 5×5の平均化フィルターをかけた例にないます。
フィルターをかけるとピントがあっていないように写真がぼやけてみえますよね。
またフィルターの大きさが大きい程ぼやけ方が大きいかなと思います。

Pythonコード

Pythonで書くとこんな感じになります。

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import math
from PIL import Image

#フィルターの作成
def create_averaging_filter(size):
  return np.full(size, 1 / (size[0] **2))

def convolve2d(image, filter):
  shape = (image.shape[0] - filter.shape[0] + 1, image.shape[0] - filter.shape[0] + 1) + filter.shape
  strides = image.strides * 2
  strided_image = np.lib.stride_tricks.as_strided(image, shape, strides)
  return np.einsum('kl,ijkl->ij', filter, strided_image)

im = np.array(Image.open('BOAT.bmp'))
print('画像の大きさ', im.shape)

filter3 = create_averaging_filter((3, 3))
filter5 = create_averaging_filter((5, 5))

output = convolve2d(im, filter3)
output2 = convolve2d(im, filter5)

fig = plt.figure()
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132)
ax3 = fig.add_subplot(133)

#オリジナル画像
ax1.axis("off")
ax1.imshow(im, cmap = "gray")
ax1.set_title('original')

#3×3のファィルター適応画像
ax2.axis("off")
ax2.imshow(output, cmap = "gray")
ax2.set_title('3×3 filter')

#5×5のファィルター適応画像
ax3.axis("off")
ax3.imshow(output2, cmap = "gray")
ax3.set_title('5×5 filter')

#描画
plt.show()
plt.close()

実装したコードで特に難しいのは関数convolve2d(image, filter):の中の
strided_image = np.lib.stride_tricks.as_strided(image, shape, strides)
でしょうか。

実装は上のコードで大丈夫ですので
これについてはまた次回解説いたします。

重み付きの平均化

単純な平均化ではなく、フィルタの中央に近いほど大きな重みをつけて平滑化する方法もあります。
それが重み付きの平滑化といいます。

代表的なものが、ガウス分布に近づけたガウシアンフィルタと呼ばれるものです。

$$f(x, y) = \frac{1}{\sqrt{2\pi \sigma^2}} \exp \left(-\frac{(x^2 + y^2)}
{2\sigma^2} \right)
$$

という計算式を使ってフィルターをかけていきます。
例えば、3×3, 5×5フィルタのガウシアンフィルタは以下のようになります。

このフィルタをボートの写真にかけたものと、平均化フィルタをかけたものとの比較した物を下に示します。

左がオリジナルのボートの写真で、真ん中が5×5のガウシアンフィルタをかけた写真、右が5×5の平均化フィルタをかけた例になります。
違いがかなり分かりにくいです(笑)。

いちおうガウシアンの方が自然にぼかす、と言われてはいます。

Pythonコード

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import math
from PIL import Image

#フィルターの作成
def create_averaging_filter(size):
  return np.full(size, 1 / (size[0] * *2))

#
def create_gaussian_filter(size, sigma):
  center = ((size[0] - 1) / 2, (size[0] - 1) / 2)
  sigma2 = 2 * sigma * sigma
  filter = np.fromfunction(lambda y, x: np.exp(-((x - center) ** 2 + (y - center[0]) ** 2) / sigma2), size)
  filter = filter / np.sum(filter)
  return filter

#畳み込み
def convolve2d(image, filter):
  shape = (image.shape[0] - filter.shape[0] + 1, image.shape - filter.shape + 1) + filter.shape
  strides = image.strides * 2
  strided_image = np.lib.stride_tricks.as_strided(image, shape, strides)
  return np.einsum('kl,ijkl->ij', filter, strided_image)

im = np.array(Image.open('BOAT.bmp'))
print('画像の大きさ', im.shape)


gaussian_kernel = create_gaussian_filter((5, 5), 1)
#gaussian_image = convolve2d(im, gaussian_kernel)

filter5 = create_averaging_filter((5, 5))

output = convolve2d(im, gaussian_kernel)
output2 = convolve2d(im, filter5)

fig = plt.figure()
ax1 = fig.add_subplot(131)
ax2 = fig.add_subplot(132)
ax3 = fig.add_subplot(133)

#オリジナル画像
ax1.axis("off")
ax1.imshow(im, cmap = "gray")
ax1.set_title('original')

#3×3のファィルター適応画像
ax2.axis("off")
ax2.imshow(output, cmap = "gray")
ax2.set_title('5×5 gaussian filter')

#5×5のファィルター適応画像
ax3.axis("off")
ax3.imshow(output2, cmap = "gray")
ax3.set_title('5×5 averagefilter')


#描画
plt.show()
plt.close()

今回色々と難しいコードになっております。
特にnp.fromfunction使ったりはかなり面白いかなと思います。
解説記事を書きましたので、参考にしていただければ幸いです。

lambda式に関してはこちらの記事を参考にしていただけると幸いです。

特定方向の平滑化


平均的にぼかすやり方以外にも
手ブレ感のある、画像のぼかし型もあります。

例えば以下のように対角要素だけに成分が入ったフィルターを適用する方法です。

例えば15×15の上のような平滑化フィルタをかけるとボートの画像は以下のような感じになります。

ものすごく手ブレしてるような画像になったのではないでしょうか。
普段こういう加工はしたことがないのですが、こんな感じで画像をぼかすことも可能です。

Python コード

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import math
from PIL import Image

#フィルターの作成
def create_filter(size):
  filter = np.identity(size[0])
  filter = filter/15
  return filter

def convolve2d(image, filter):
  shape = (image.shape[0] - filter.shape[0] + 1, image.shape - filter.shape + 1) + filter.shape
  strides = image.strides * 2
  strided_image = np.lib.stride_tricks.as_strided(image, shape, strides)
  return np.einsum('kl,ijkl->ij', filter, strided_image)

im = np.array(Image.open('BOAT.bmp'))
print('画像の大きさ', im.shape)

filter1 = create_filter((15, 15))
print(filter1)
output = convolve2d(im, filter1)

fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)

#オリジナル画像
ax1.axis("off")
ax1.imshow(im, cmap = "gray")
ax1.set_title('original')

#3×3のファィルター適応画像
ax2.axis("off")
ax2.imshow(output, cmap = "gray")
ax2.set_title('filterd')

#描画
plt.show()
plt.close()

これは上で紹介した平均化フィルタと比べても目新しいものはないかな?という印象。

コードの参考にさせていただいたサイト

【画像処理】Numpyで平滑化フィルター

おわりに

ここまで読んでいただき、ありがとうございました。

今の仕事につく前は、OpenCVだったりを使って
ライブラリの力でなんとか処理を行う、みたいなことをしていたのですが

最近はnumpyで自分がどうい処理をしているか理解しながらやると
かなり楽しいことに気づいてきました。

今後とも皆様の参考になる記事を書ければ嬉しいです。

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA