【Python & OpenCV】間違い探しプログラムを作ってみた(検出率97%)

OpenCV

Python+OpenCVを使って間違い探し画像の間違い部分に枠を付けるプログラムを作ってみた。

画像処理に関してシロウトなので正しいアプローチでできているかのよく分かりませんが、かなり高い確率で検出できているのでいいよね。

検証用の間違い探し画像は、イラストACに公開されているくもささんの作品を使わせていただきました。

そそたた
そそたた

答えを載せていいものやら悩みましたが、答えも一緒に公開されているからいいよね?

(;^_^A アセアセ・・・

結果

今回の検証は、くもささんの「7つの間違い探し」シリーズを5つを使用しました。

間違い探し画像検出
7つの間違い探し『留守番』100%(7/7)
7つの間違い探し『ペンギン水槽』100%(7/7)
7つの間違い探し『男の料理』100%(7/7)
7つの間違い探し『未来の農業』 86%(6/7)
7つの間違い探し『黒猫会議』100%(7/7)
合計97%(34/35)
そそたた
そそたた

誤検出も少しありましたが全体で2件でした。

差分画像から間違い部分を検出しているので、同じように見えても少しずれていたりすると誤検出しまくりの結果になったりします。

プログラム的にも検証対象用に微調整している部分もあるので、もっと色んなパターンの間違い画像を試すと全然ダメダメなものもでてくると思います。

7つの間違い探し『留守番』

これは誤検出もなく完璧に7つ検出できてます。

7つの間違い探し『ペンギン水槽』

7つ検出できているけど左上に1つ誤検出してます。

手動で画像を切り出しているので上下左右にゴミがのりやすく、それを検出してしまった感じですね。

7つの間違い探し『男の料理』

これも誤検出もなく完璧に7つ検出できてます。

ふすまの引き手部分が2つに分割されて検出していますが差分画像を見ている関係上しょうがないですね。(引き手の真ん中部分は差分がないので)

7つの間違い探し『未来の農業』

6つ検出できていますが、子供の帽子の形状変化を検出できませんでした。

雲の部分を分割して検出していますが、まぁ間違いではないのでOKにしときます。

そそたた
そそたた

色々試してみたけど、どうしても子供の帽子を検出でけへん!

理由が分からん。ヽ(`⌒´♯)ノ

7つの間違い探し『黒猫会議』

これは7つ検出できてはいますが、右端のしっぽと塀の高さが変わっている部分がいっしょになって微妙な感じですが、おまけして誤検出なしにしときましょう。

ソースコード

作成した間違い探しプログラムのソースコードを載せておきます。

開発環境は、Python3.5.5+OpenCV3.4.2です。

import cv2

path1 = '/???/sample1-1.png'
path2 = '/???/sample2-2.png'
img1 = cv2.imread(path1, cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread(path2, cv2.IMREAD_GRAYSCALE)

clahe = cv2.createCLAHE(clipLimit=30.0, tileGridSize=(10,10))
img1 = clahe.apply(img1)
img2 = clahe.apply(img2)

img1 = cv2.GaussianBlur(img1,(13,13),0)
img2 = cv2.GaussianBlur(img2,(13,13),0)

diff = cv2.absdiff(img1, img2)
ret, diff = cv2.threshold(diff, 60, 255, cv2.THRESH_BINARY)
diff = cv2.GaussianBlur(diff,(11,11),0)

img2 = cv2.imread(path2)
image, contours, hierarchy = cv2.findContours(diff, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
    x, y, w, h = cv2.boundingRect(c)
    if w > 15 and h > 15:
        cv2.rectangle(img2, (x, y), (x + w, y + h), (0, 255, 0), 5)

cv2.imshow('result image', img2)
cv2.waitKey(0)
cv2.destroyAllWindows()

解説

間違い探しプログラムを作成するにあたりGoogle先生にリサーチしてみたところ、大体absdiffかcreateBackgroundSubtractorMOGを使って画像の差分を抽出するものでした。

さっそく両方を試してみたところ、誤検出部分が多すぎて間違い部分のみに枠を付けることできなかったので、少しの工夫&微調整してみました。

そそたた
そそたた

単純にそそたたのやり方が悪いのか、使用する検証用の画像によって変わってくるのだと思います。

それでは、ソースコードの解説をします。

まずは、間違い探し用の2つの画像をグレースケールで取得します。

2つの画像は、同じサイズでない差分が取れず動作しません。

import cv2

path1 = '/???/sample1-1.png'
path2 = '/???/sample2-2.png'
img1 = cv2.imread(path1, cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread(path2, cv2.IMREAD_GRAYSCALE)
そそたた
そそたた

間違い探し用の2つの画像は、手動で切り出しています。

少しでもずれると、検証結果がボロボロになるので注意が必要です。

プログラムにより自動で2つの領域を検出することも可能かもしれませんが、これは別のテーマになりそうなのでばっさりあきらめます。

次に、ヒストグラム平滑化によりコントラストを上げます。

これは、色があまり変わらない部分の検出に対しての工夫となります。

真っ白な部分の検出(例えば雲とか)もこの工夫により改善しました。

clahe = cv2.createCLAHE(clipLimit=30.0, tileGridSize=(10,10))
img1 = clahe.apply(img1)
img2 = clahe.apply(img2)

次は、差分画像を作成する前にガウシアンフィルタで平滑化してノイズを軽減します。

img1 = cv2.GaussianBlur(img1,(13,13),0)
img2 = cv2.GaussianBlur(img2,(13,13),0)
そそたた
そそたた

GaussianBlurのパラメータは、今回の検証で調整した値です。

根拠とか数学的な説明は正直よくわかりませんので、詳しくはGoogle先生に聞いてください。

(・∀・)

次に、2つの画像の差から絶対値を求めた差分画像を閾値により2値化して白黒のみの画像にします。

その差分画像をさらに輪郭抽出しやすくするために平滑化します。

diff = cv2.absdiff(img1, img2)
ret, diff = cv2.threshold(diff, 60, 255, cv2.THRESH_BINARY)
diff = cv2.GaussianBlur(diff,(11,11),0)

次に、結果用にカラー画像を取得して差分画像から輪郭抽出した座標に枠を間違い部分として描画します。

ゴミ検出を除去するために15 x 15未満の小さい領域を対象外としています。

img2 = cv2.imread(path2)
image, contours, hierarchy = cv2.findContours(diff, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
    x, y, w, h = cv2.boundingRect(c)
    if w > 15 and h > 15:
        cv2.rectangle(img2, (x, y), (x + w, y + h), (0, 255, 0), 5)

最後に、結果を表示して完了です。

cv2.imshow('result image', img2)
cv2.waitKey(0)
cv2.destroyAllWindows()

コメント

タイトルとURLをコピーしました