物体のエッジを抽出するプログラムを作りたいが、どのように作ればよいか分からない。エッジ抽出の原理について詳しく知りたい。
そんな悩みを解消すべく本記事では、ノイズを除去しつつエッジ抽出するソーベルフィルタについて解説します。
まず、エッジ抽出原理を解説します。次にソーベルフィルタとは何かについての解説です。最後にPython,OpenCVを用いて画像のエッジを抽出するプログラムを解説します。
それでは始めましょう!
この記事はこんな人におすすめ!
ソーベルフィルタについて理解するためには、エッジ抽出の原理を学ぶ必要があります。ここでは、微分によってエッジ検出する原理を解説します。
画像処理でのエッジとは、画像の明るい部分と暗い部分が急激に変化する勾配部です。この勾配部を抽出するには、画像の輝度値を微分し数値化します。関数\(f(x)\)の微分\(f'(x)\)は以下式で表されますよね。
$$f'(x)=\lim_{h \to 0}\frac{f(x+h)-f(x)}{h}$$
下図のように、輝度値を微分した値が大きい部分がエッジに相当することが分かります。
デジタル画像は2変数かつ1画素ごとの値のため、どうやったら勾配を求められる?
2変数関数の場合は、微分する変数を1つ指定し、それ以外の変数を定数として微分します。このような微分を偏微分といいます。2変数関数を\(f(x,y)\)とし、xで微分した導関数を\(f_x(x,y)\)、yで微分した導関数を\(f_y(x,y)\)とすると
$$f_x(x,y) = \lim_{h \to 0}\frac{f(x+h, y)-f(x, y)}{h}$$
$$f_y(x,y) = \lim_{h \to 0}\frac{f(x, y+h)-f(x, y)}{h}$$
\(f_x(x,y)\)はx方向、\(f_y(x,y)\)はy方向の勾配を表します。
デジタル画像は、1画素ごとの離散値です。そのため、\(h\)は有限です。そこで、\(h=1\)として微分値を求めます。
$$f_x(x,y) = f(x+1, y)-f(x, y)$$
$$f_y(x,y) = f(x, y+1)-f(x, y)$$
これは座標\((x,y)\)とその前方座標\((x+1,y)、(x,y+1)\)の差分をとっていますので前方差分と呼ばれます。
一方、\(h=-1\)の場合は
$$f_x(x,y) = f(x, y)-f(x-1, y)$$
$$f_y(x,y) = f(x, y)-f(x, y-1)$$
となり、座標\((x,y)\)とその前方座標\((x-1,y)\)、\((x,y-1)\)の差分をとることから後方差分と呼ばれます。
また、前方差分と後方差分の平均を求めると
$$f_x(x,y) = \frac{f(x+1, y)-f(x-1, y)}{2}$$
$$f_y(x,y) = \frac{f(x, y+1)-f(x, y-1)}{2}$$
となります。座標\((x+1,y)\)と座標\((x-1,y)\)の差分、座標\((x,y+1)\)と座標\((x,y-1)\)の差分をとることから中心差分と呼ばれます。
空間フィルタリングにおいて線形フィルタは、入力画像を\(f(i,j)\)、出力画像を\(g(i,j)\)、フィルタ係数を\(h(m,n)\)とするとき、以下式で表されました。
$$g(i,j)=\sum_{n=-W}^{W}\sum_{m=-W}^{W}f(i+m,j+n)h(m,n)$$
https://craft-gogo.com/gauss-fillter/
微分をこの形式で表すと微分フィルタは以下表となります。
x方向 | y方向 | |
前方差分 | \begin{pmatrix} 0 & 0 & 0 \\ 0 & -1 & 1 \\ 0 & 0 & 0 \end{pmatrix} | \begin{pmatrix} 0 & 0 & 0 \\ 0 & -1 & 0 \\ 0 & 1 & 0 \end{pmatrix} |
後方差分 | \begin{pmatrix} 0 & 0 & 0 \\ -1 & 1 & 0 \\ 0 & 0 & 0 \end{pmatrix} | \begin{pmatrix} 0 & -1 & 0 \\ 0 & 1 & 0 \\ 0 & 0 & 0 \end{pmatrix} |
中心差分 | \begin{pmatrix} 0 & 0 & 0 \\ -1/2 & 0 & 1/2 \\ 0 & 0 & 0 \end{pmatrix} | \begin{pmatrix} 0 & -1/2 & 0 \\ 0 & 0 & 0 \\ 0 & 1/2 & 0 \end{pmatrix} |
画像にノイズが混入していると、ノイズをエッジと誤検出してしまいます。
そこで登場するのが、ノイズを除去しつつエッジ抽出するソーベルフィルタです。
そこで登場するのがソーベルフィルタです。ソーベルフィルタとは、前節で説明した中央差分したのち、中央に重み付けした平滑化を行ったフィルタです。
下記表は、x方向、y方向の中央差分、重み付き平滑化、ソーベルフィルタのフィルタ係数です。
中央差分: \(h_1(x,y)\) | 重み付き平滑化: \(h_2(x,y)\) | ソーベルフィルタ: \(h_2(x,y)\cdot h_1(x,y)\) | |
x方向 | \(\begin{pmatrix} 0 & 0 & 0 \\ -\frac{1}{2} & 0 & \frac{1}{2} \\ 0 & 0 & 0 \end{pmatrix}\) | \(\begin{pmatrix} 0 & \frac{1}{4} & 0 \\ 0 & \frac{2}{4} & 0 \\ 0 & \frac{1}{4} & 0 \end{pmatrix}\) | \(\frac{1}{8}\begin{pmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{pmatrix}\) |
y方向 | \(\begin{pmatrix} 0 & -\frac{1}{2} & 0 \\ 0 & 0 & 0 \\ 0 & \frac{1}{2} & 0 \end{pmatrix}\) | \(\begin{pmatrix} 0 & 0 & 0 \\ \frac{1}{4} & \frac{2}{4} & \frac{1}{4} \\ 0 & 0 & 0 \end{pmatrix}\) | \(\frac{1}{8}\begin{pmatrix} -1 & -2 & 1 \\ 0 & 0 & 0 \\ -1 & 2 & 1 \end{pmatrix}\) |
中央差分を\(h_1(x,y)\)、重み付き平滑化を\(h_2(x,y)\)とするとき、中央差分と重み付き平滑化を順に施したソーベルフィルタは、\(h_2(x,y)\cdot h_1(x,y)\)で計算できます。
ソーベルフィルタと同様なフィルタにブリューウィットフィルタ(Prewitt filter)というものがありますので紹介しておきます。
中央差分: \(h_1(x,y)\) | 平滑化: \(h_2(x,y)\) | ブリューウィットフィルタ: \(h_2(x,y)\cdot h_1(x,y)\) | |
x方向 | \(\begin{pmatrix} 0 & 0 & 0 \\ -\frac{1}{2} & 0 & \frac{1}{2} \\ 0 & 0 & 0 \end{pmatrix}\) | \(\begin{pmatrix} 0 & \frac{1}{3} & 0 \\ 0 & \frac{1}{3} & 0 \\ 0 & \frac{1}{3} & 0 \end{pmatrix}\) | \(\frac{1}{6}\begin{pmatrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1 \end{pmatrix}\) |
y方向 | \(\begin{pmatrix} 0 & -\frac{1}{2} & 0 \\ 0 & 0 & 0 \\ 0 & \frac{1}{2} & 0 \end{pmatrix}\) | \(\begin{pmatrix} 0 & 0 & 0 \\ \frac{1}{3} & \frac{1}{3} & \frac{1}{3} \\ 0 & 0 & 0 \end{pmatrix}\) | \(\frac{1}{6}\begin{pmatrix} -1 & -1 & 1 \\ 0 & 0 & 0 \\ -1 & 1 & 1 \end{pmatrix}\) |
こちらは中央差分と重みなしの平滑化を順に施したフィルタです。
では、PythonとOpenCVを用いて画像のエッジを抽出してみましょう。
下記は、画像を読み込み、ソーベルフィルタを適用したサンプルプログラムです。
import cv2 as cv
def concat_tile(im_list_2d):
return cv.vconcat([cv.hconcat(im_list_h) for im_list_h in im_list_2d])
img = cv.imread("sample.jpg") # 写真の読み込み
img_sobelx = cv.Sobel(img, cv.CV_64F, 1, 0, ksize=3) # 3x3の横方向ソーベルフィルタ
img_sobelx = cv.convertScaleAbs(img_sobelx)
img_sobely = cv.Sobel(img, cv.CV_64F, 0, 1, ksize=3) # 3x3の縦方向ソーベルフィルタ
img_sobely = cv.convertScaleAbs(img_sobely)
img_gradient = cv.addWeighted(img_sobelx, 0.5, img_sobely, 0.5, 0) # 勾配の大きさ
imgs = concat_tile([[img, img_sobelx], [img_sobely, img_gradient]])
cv.imshow("img", imgs)
cv.imwrite("output.jpg", imgs)
if cv.waitKey(0) & 0xFF == ord('q'):
cv.destroyAllWindows()
左上が元画像、右上が横方向エッジ抽出、左下が縦方向のエッジ抽出、右下が勾配の大きさになります。
花の輪郭が抽出されているのが分かりますよね。
サンプルプログラムを解説していきましょう。
ソーベルフィルタ適用は、cv.Sobel()関数を呼び出します。
cv.Sobel(img, cv.CV_64F, 1, 0, ksize=3)
第1引数に読み込む画像、第2引数は、出力画像のビット深度を指定します。第3,4引数はx,yに関する微分の次数です。(1, 0)でx方向、(0, 1)でy方向のエッジが抽出できます。ksizeはフィルタ係数の大きさです。今回は3×3のフィルタサイズを指定しました。
ソーベルフィルタは微分値をとるため、負の値にもなります。しかし、負の値が含まれていると画像表示することが出来ません。そこで、cv.convertScaleAbs()関数を呼び出して絶対値にします。
cv.convertScaleAbs(img)
縦方向と横方向のエッジを合成するには、cv.addWeighted()関数を呼び出します。
cv.addWeighted(src1, alpha1, src2, beta, gamma)
出力結果は、\(\alpha\cdot src1 + \beta \cdot src2 + \gamma\)となります。
今回は、ノイズを除去しつつエッジ抽出するソーベルフィルタについて解説しました。
これで、物体のエッジを抽出するプログラムが簡単に作れますね。
この記事が画像処理を学ぶ皆さんのためになれば幸いです。
独学が大変な方は、書籍やスクールを活用するのも手です。私も活用しているものを載せておきますので参考にして下さい。
最後までお読み頂きありがとうございました!
画像のごま塩ノイズを除去したい。そんなときはメディアンフィルタの登場です。本記事では、ノイズを除去するメディアンフィルタについて解説します。
この記事はこんな人におすすめ!
メディアンフィルタとは空間フィルタリングの一種です。
メディアン(median)とは中央値のこと。メディアンフィルタは、周辺画素の画素値を降(昇)順に並べ、中央値を出力します。周辺画素に対して著しく外れた値(ノイズ)は、メディアンフィルタを通過すると除去されます。
下図は3×3メディアンフィルタでノイズが除去されるイメージ図です。
座標(i,j)の画素は上下左右斜めに対して著しく外れた「10」ですが、メディアンフィルタを通過すると、上下左右斜めの画素中央値である「2」に変換され、ノイズが除去されることが分かります。
PythonとOpenCVライブラリを用いれば、簡単にメディアンフィルタで画像のノイズを除去することが出来ます。
こちらがメディアンフィルタのPythonサンプルプログラムです。
import cv2 as cv
def concat_tile(im_list_2d):
return cv.vconcat([cv.hconcat(im_list_h) for im_list_h in im_list_2d])
img = cv.imread("sample.jpg") # 写真の読み込み
img_median3 = cv.medianBlur(img, 3) # 3x3のメディアンフィルタ
img_median5 = cv.medianBlur(img, 5) # 5x5のメディアンフィルタ
img_median7 = cv.medianBlur(img, 7) # 7x7のメディアンフィルタ
imgs = concat_tile([[img, img_median3], [img_median5, img_median7]])
cv.imshow("img", imgs)
cv.imwrite("output.jpg", imgs)
if cv.waitKey(0) & 0xFF == ord('q'):
cv.destroyAllWindows()
左上が元画像。右上、左下、右下がそれぞれ3×3、5×5、7×7のメディアンフィルタを適用した画像となります。
3×3フィルタサイズで、ごま塩ノイズが十分除去されていることが分かりますね。フィルタサイズを大きくしすぎると、画像全体がぼやけてしまうので注意が必要です。
cv.medianBlur(img, N) # NxNのメディアンフィルタ
第1引数に入力画像、第2引数にフィルタサイズを設定します。
ぼかしの基本であるガウシアンフィルタでも、ごま塩ノイズが除去できるか試してみましょう。
ガウシアンフィルタについては、下記リンク先参照して下さい。
https://craft-gogo.com/gauss-fillter/
左上が元画像。右上、左下、右下がそれぞれσ=1、σ=2、σ=3のガウシアンフィルタを適用した画像です。
σを大きくしないと、ごま塩ノイズは除去できないことが分かります。ごま塩ノイズ除去できたとしても、画像はかなりぼやけてしまいますね。
本記事では、ノイズを除去するメディアンフィルタについて解説しました。
画像のごま塩ノイズを除去したければ、メディアンフィルタを適用すればよいことが分かりましたね。
この記事が画像処理を学ぶ皆さんのためになれば幸いです。
独学が大変な方は、書籍やスクールを活用するのも手です。私も活用しているものを載せておきますので参考にして下さい。
最後までお読み頂きありがとうございました!
]]>写真の主役を引き立たせるために背景をぼかしたり、プライバシー保護のため写真の顔をぼかしたりしますよね?
Photoshopなどの画像処理ソフトで画像をぼかそうとすると、種類がたくさんあって何を使ったらいいものか…。
本記事では、ぼかしの基本であるボックスフィルタ、ガウシアンフィルタについて解説します。また、PythonとOpenCVで実際に写真をぼかす実践をします。この記事を読めば、画像をぼかしたかったら、悩まずガウシアンフィルタを選択すれば無難なことが分かります。
もう、ぼかしで悩むことから脱却しましょう!
この記事はこんな人におすすめ!
ぼかしを学ぶ前に空間フィルタリングを理解する必要があります。フィルタを直訳すると、ろ過器やこし器などです。画像処理では、画像をこの空間フィルタで処理することで様々な画像に変換します。空間フィルタには、ぼかすためのフィルタ、エッジを抽出するためのフィルタ、ノイズを除去するためのフィルタなどがあります。
空間フィルタの概念は、コーヒーの抽出で例えることができます。コーヒーは、コーヒーの粉をコーヒーフィルタに入れ、お湯を注ぐと作ることができますよね。澄んだお湯がコーヒーフィルタを介すと黒く濁ったコーヒーになる。空間フィルタリングも同様なイメージです。入力画像は、お湯です。空間フィルタリングは、コーヒーフィルタです。コーヒーの粉は、ぼかしフィルタなどフィルタの種類です。出力画像は、コーヒーです。入力画像をぼかしフィルタで処理すると、出力画像は入力画像がぼけた結果になります。
空間フィルタは、線形フィルタと非線形フィルタに分けられます。ボックスフィルタ、ガウシアンフィルタは線形フィルタのため、ここでは線形フィルタについて解説していきます。
線形フィルタは、入力画像を\(f(i,j)\)、出力画像を\(g(i,j)\)とするとき、以下の式により計算されます。
$$g(i,j)=\sum_{n=-W}^{W}\sum_{m=-W}^{W}f(i+m,j+n)h(m,n)$$
\(h(m,n)\)はフィルタの係数を表す配列であり、フィルタの種類によってこの配列は変わってきます。ボックスフィルタ、ガウシアンフィルタの係数は次章で詳しく説明します。
フィルタの大きさは、\((2W+1)\times (2W+1)\)です。\(W=1\)の場合、\(3\times 3\)行列、\(W=2\)の場合、\(5\times 5\)行列となります。
数式見てもイメージしづらいですよね…。
そこで数式に具体的な数値を代入し、イメージしやすくしましょう。下図は3×3フィルタの計算例です。
入力画像の(i,j)座標の画素は「10」でしたが、フィルタを通すと(i,j)座標の画素は「7」となります。
3×3フィルタの場合は、入力画像のある画素に対して、上下左右斜めの8画素が影響し出力画素となっていますよね。これが空間フィルタリングと呼ばれる理由です。
空間フィルタリングの知識は身に付いたと思いますので、画像をぼかすためのボックスフィルタ、ガウシアンフィルタについて解説していきます。
ボックスフィルタは、非常に簡単なぼかし(平滑化)フィルタです。フィルタの範囲を平均化するのみです。3×3フィルタの場合は、3×3=9画素をフィルタしますので、各フィルタ係数は9分の1となります。同様に5×5フィルタの場合は、各フィルタ係数は25分の1となります。
ガウシアンフィルタは、フィルタ係数がガウス分布となっているものです。
ガウス分布?
1次元のガウス分布は以下式で表されます。σは標準偏差です。
$$h_g (x) = \frac{1}{\sqrt{2\pi}\sigma}\exp(-\frac{x^2}{2\sigma^2})$$
グラフにすると下図です。
σが小さいと尖った山となり、周辺画素の影響は小さくなります。σが大きくなるにつれて、山は平坦となり、周辺画素の影響が大きくなります。ぼかしを強くしたいならσを大きくすればよいことが分かります。
画像は2次元ですので、2次元ガウス分布が必要です。2次元ガウス分布は以下式で表されます。
$$h_g(x) = \frac{1}{2\pi\sigma^2}\exp(-\frac{x^2+y^2}{2\sigma^2})$$
σ=2を代入してグラフにするとこんな感じ。
この式からNxNフィルタ係数行列を作れば、ガウシアンフィルタの出来上がりです。
PythonとOpenCVライブラリを用いれば、簡単にボックスフィルタとガウシアンフィルタで画像をぼかすことが出来ます。
まず、ボックスフィルタのサンプルプログラムです。
import cv2 as cv
def concat_tile(im_list_2d):
return cv.vconcat([cv.hconcat(im_list_h) for im_list_h in im_list_2d])
img = cv.imread("sample.jpg") # 写真の読み込み
img = cv.resize(img, None, fx=0.5, fy=0.5) # 写真サイズを2分の1
img_blur3 = cv.blur(img, (3, 3)) # 3×3のボックスフィルタ
img_blur5 = cv.blur(img, (5, 5)) # 5×5のボックスフィルタ
img_blur7 = cv.blur(img, (7, 7)) # 7x7のボックスフィルタ
imgs = concat_tile([[img, img_blur3], [img_blur5, img_blur7]])
cv.imshow("img", imgs)
cv.imwrite("output.jpg", imgs)
if cv.waitKey(0) & 0xFF == ord('q'):
cv.destroyAllWindows()
左上が元画像。右上、左下、右下がそれぞれ3×3、5×5、7×7のボックスフィルタを適用した画像です。
フィルタサイズが大きくなるほど、ぼかしが強くなっていくことが分かります。
cv.blur(img, (N, N)) # N×Nのボックスフィルタ
第1引数に入力画像、第2引数にフィルタサイズを設定します。
次にガウシアンフィルタのサンプルプログラムです。
import cv2 as cv
def concat_tile(im_list_2d):
return cv.vconcat([cv.hconcat(im_list_h) for im_list_h in im_list_2d])
img = cv.imread("sample.jpg") # 写真の読み込み
img = cv.resize(img, None, fx=0.5, fy=0.5) # 写真サイズを2分の1
img_gauss1 = cv.GaussianBlur(img, (0, 0), 1) # σ=1のガウシアンフィルタ
img_gauss2 = cv.GaussianBlur(img, (0, 0), 2) # σ=2のガウシアンフィルタ
img_gauss3 = cv.GaussianBlur(img, (0, 0), 3) # σ=3のガウシアンフィルタ
imgs = concat_tile([[img, img_gauss1], [img_gauss2, img_gauss3]])
cv.imshow("img", imgs)
cv.imwrite("output.jpg", imgs)
if cv.waitKey(0) & 0xFF == ord('q'):
cv.destroyAllWindows()
左上が元画像。右上、左下、右下がそれぞれσ=1、σ=2、σ=3のガウシアンフィルタを適用した画像です。
σを大きくすると、ぼかしが強くなります。
cv.GaussianBlur(img, (0, 0), N) # σ=Nのガウシアンフィルタ
第1引数に入力画像、第2引数にフィルタサイズ、第3引数にσを設定します。
ここでフィルタサイズが0?と思われるでしょう。
openCVでは、フィルタサイズを0に設定するとσに応じてフィルタサイズが自動設定されます。
フィルタサイズ設定に困ったら、σだけ設定すればよいです。
ボックスフィルタとガウシアンフィルタを比べてみると、ガウシアンフィルタの方がぼかしが自然かと思われます。画像をぼかしたかったら、ガウシアンフィルタを選んでおけば間違いないです。
本記事では、ぼかしの基本であるボックスフィルタ、ガウシアンフィルタについて解説しました。また、PythonとOpenCVで実際に写真をぼかす実践をしました。
画像をぼかしたかったら、悩まずガウシアンフィルタを選択すれば無難なことが分かりましたね。
これで、写真の主役を引き立たせるために背景をぼかしたり、プライバシー保護のため写真の顔をぼかしたりが出来ると思います。
この記事が画像処理を学ぶ皆さんのためになれば幸いです。最後まで読んで頂きありがとうございました!
独学が大変な方は、書籍やスクールを活用するのも手です。私も活用しているものを載せておきますので参考にして下さい。
]]>