AIの画像認識なんかでよく聞く畳み込み処理。
内部では何をやっているんだろう?
このページでは、こんな悩みを解決するためにある画像を指定した(3×3)のフィルターを使って実際の畳み込み処理を手動で行い、出てきた画像がどんなものなのかを確認していこうと思います。
処理の流れを把握することでAIの内部計算でどんなことをやっているのか理解できるはずです。
加えてなぜ畳み込み処理をする意味があるのか、というところまで解説していきます。
それではさっそくやっていきましょう。
畳み込み処理の計算方法
まずは畳み込みの計算がどんなことをやっているのかをエクセルを使って解説します。
事前に準備するものは画像の輝度配列(以下画像のC3:G8セル)とフィルターとなる配列(以下画像のJ2:L4セル)です。
この輝度配列はあとで紹介する処理を実施するために、周囲を0で埋めています。
(理由は後程のわかると思いますので、いったん受け流してください。)
それではこれらの準備物をつかって畳み込み処理をしてみましょう。
以下のQ3:U8セルが畳み込み処理を実施した結果です。
小さくてわかりにくいかもしれませんが、やっていることとしては画像の輝度配列とフィルター配列の各要素を掛け算してすべての要素を足し合わせてるという作業を各ピクセルごとに実施していくというものです。
(上の画像は左上のC3セルに対して計算している様子)
以下はE6セルに対して計算している様子です。
イメージはつかめましたかね?
見ての通り、周囲に0が配置されていなければ端っこのピクセルで計算できないことになります。
それを避けるために畳み込み処理では周囲を0で埋める作業を行うことが多いです。
(多いだけでやらないことあります。)
今回は3×3のフィルターなので0は1層でOKですが、当然フィルターサイズが大きくなるとこの0の層を増やしていく必要があります。
いずれにせよ畳み込み計算の概要がわかったと思います。
畳み込み処理の意味について
畳み込み処理の計算方法はわかったと思いますが、こんなことして何になるのかまだイメージがわかないと思います。
次は画像認識なんかにおける畳み込み処理の意味について解説していきましょう。
例えば以下の輝度配列を持った画像があったとします。
0, 0, 0
255,255,255
0, 0, 0
この画像に対してまずは以下のフィルターで畳み込みをしたとします。
1, 0, 0
0 ,1 ,0
0, 0, 1
処理の結果は255ですよね。
次は以下のフィルターで畳み込みをしたとします。
0, 1, 0
0 ,1 ,0
0, 1, 0
これも処理の結果は255ですよね。
次は以下のフィルターで畳み込みをしたとします。
0, 0, 0
1 ,1 ,1
0, 0, 0
結果は255*3=765ですよね。
フィルターと、もとの画像の輝度が強い場所が一致するとこのように大きな数値が出力されるわけですね。
何が言いたいかと言うと、一つ目のフィルターは斜めに輝度が大きい場所を探索するフィルター、二つ目のフィルターは縦に輝度が大きい場所を探索するフィルター、三つ目は横に輝度が大きい場所を探索するフィルターであるということです。
このように様々なフィルターをかけてあげることによって、どこに、どんな形で輝度が大きい領域が存在するかを探索できるというとわけです。
さっきの例で言うと、画像に横線が入っているということをコンピューターが理解できるようになったわけです。
こんな感じでAIは様々なフィルターを駆使して、目を探索したり毛を探索したりして、画像に写っているものが犬だとか猫だとかを判断しているわけです。
ざっくりですが、畳み込み処理をする意味としてはこんな感じです。
というわけで、畳み込みの計算方法、畳み込みを実施する意味を理解できたところで、次はpython-openCVを使って実際の畳み込み処理を実演していきます。
python-openCVで畳み込み処理を実演
今回は以下の画像に対して畳み込み処理を行っていきます。
この画像がsample.jpgとしてプログラム実行フォルダに保存されていることとします。
フィルターは先ほどの例と同様に以下のフィルターを設定します。
それではこれらの前提で畳み込み処理を実演してみましょう。
以下がそのサンプルコードです。
import cv2
import numpy as np
import matplotlib.pyplot as plt
file_name='sample.jpg'
img=cv2.imread(file_name,cv2.IMREAD_GRAYSCALE)
h,w=img.shape[:2]
#フィルター設定
cnv_filter=np.array([[1,0,0],
[0,1,0],
[0,0,1]])
#画像輝度の周囲に0を埋める作業
pad=np.zeros((h+2,w+2),np.uint8)
pad[1:h+1,1:w+1]=img
#畳み込み処理実施
cnv=np.zeros((h,w),np.uint8)
for i in range(h):
for j in range(w):
cnv[i,j]=np.sum(pad[i:i+3,j:j+3]*cnv_filter)
#画像化する際の輝度調整
cnv=cnv/np.max(cnv)*255
cv2.imwrite('cnv.jpg',cnv)
先ほどエクセルで紹介した畳み込み処理を行っているのが、16~18行目ですね。
pythonでは行列の各要素を掛け算したい場合は2つの行列を * で掛けてあげるだけでOKです。
それで出てきた3×3の結果をnumpyのsumで合計しています。
いずれにせよ、こいつを実行すると以下の画像が出力されました。
先ほどの意味を考えると、今回の処理では斜めに線が入っている領域を探索しているようなものですね。
画像の物体の周囲をよく見てもらえればわかると思いますが、フィルターと一致している左下や、右上の\方向のラインは白く(畳み込み処理の結果、数値が大きくなっている)、逆に左上や右下の/方向のラインは黒く(畳み込み処理の結果、数値が小さくなっている)なっていることがわかると思います。
python-openCVでfilter2D機能を使う場合も実演
先ほどの処理はfor文をゴリゴリ回して計算していたわけですが、実はopenCVには畳み込み処理を簡単に実施する機能があらかじめ備わっています。
それがfilter2Dという機能です。
簡単に使い方を説明しておきます。
2つ目の引数は変換後の画像のビット数を指定する場所なのですが、元の画像と同じビット数にしたい場合は-1を指定しておけばOKです。
この機能で先ほどの画像、フィルターを使って際実行してみましょう。
以下がそのサンプルコードです。
import cv2]
file_name='sample.jpg'
img=cv2.imread(file_name,cv2.IMREAD_GRAYSCALE)
#フィルター指定
cnv_filter=np.array([[1,0,0],
[0,1,0],
[0,0,1]])
#畳み込み処理
cnv2=cv2.filter2D(img,-1,cnv_filter)
cv2.imwrite('cnv2.jpg',cnv)
先ほどのfor文と比べると非常に簡単ですね。
出力された画像を見てみましょう。
比較用に先ほど手動で作成した画像も載せておきます。
全く一緒ですね。
実務上はcv2.filter2Dを使えば問題ないと思いますが、内部で何が行われているかは重要ですので、前半に紹介した内容をしっかりと理解しておきましょう。
おわりに
というわけで今回はpython-openCVを使って、画像の畳み込み処理について、解説&実演
してみました。
AI習得に向けて少しでも参考になっていれば幸いです。
このように、私のブログでは様々なスキルを紹介しています。
今は仕事中で時間がないかもしれませんが、ぜひ通勤時間中などに他の記事も読んでいただけると嬉しいです。
⇒興味をもった方は【ヒガサラ】で検索してみてください。
確実にスキルアップできるはずです。
最後に、この記事が役に立ったという方は、ぜひ応援よろしくお願いします。
↓ 応援ボタン
にほんブログ村
それではまた!
Follow @HigashiSalary
コメント
説明わかりやすくて参考になります。
エクセルでの説明のフィルターですが
1,0,0
0,1,0
0,1,1
になってないでしょうか
ご指摘ありがとうございます。
確かになってますね(^^;;
エクセル内の数式見るとフィルター部分を掛け忘れてしまってました。
時間があるときに修正しておきます!