2つの画像を比較して違いを見つける

# 最初に

2つの画像を比較して、違っているところを見つける「間違い探し」をするプログラムを作りたいと思い、トライしてみました。
手順は、まず2つの画像を読込み、位置合わせを行った後、輝度の差をとり、差があるところは違っているところとして、赤枠で囲み、画像を表示させるという流れになっています。

この間違い探しのプログラムですが、使い方によっては、良品画像と検査対象の画像を比較して、違っているところがあれば、そこを赤枠で囲んで知らせる「外観検査」にも使えると思います。

画像1（元画像）　　　　　　　　　　　　　　　　画像2（比較画像）
![moto_hikaku.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245171/f5b82f9a-18da-d1c5-8e98-64987117eb5b.jpeg)

ぱっと見ても、どこが違うのか分かりませんが、プログラムを実行すると、
![result.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3245171/9f1d5f50-d46b-6257-d955-23c65f18e059.jpeg)

違いを見つけることができます。

# ライブラリのインポートと画像を読み込む
Open CVとnumpyを使いますので、インポートします。
比較する2つの画像（元画像と比較画像）を準備します。
```python
import cv2
import numpy as np

#保存された画像読込　OpenCVでパスは漢字やかなは使えないので注意
moto=cv2.imread('C:\\Users\\○○○\\moto.jpg') #元画像
hikaku=cv2.imread('C:\\Users\\○○○\\hikaku.jpg') #比較画像
```

# 画像サイズを揃える
画像サイズが元画像と比較画像で違う場合は、合わせておく必要があります。
例えば、ネットで間違い探しの画像をスクリーンショットで2つ保存した場合、画像サイズが違っている場合があります。その場合は、縮小や拡大しないで、トリミングして、サイズを合わせるようにします。

```python
#画像サイズ（縦、横）の取り込み
h_1, w_1, _=moto.shape
h_2, w_2, _=hikaku.shape

#縦、横サイズ、それぞれ小さい方を見つける
if h_1>h_2:
    h_min=h_2
else:
    h_min=h_1
if w_1>w_2:
    w_min=w_2
else:
    w_min=w_1

#同じサイズにトリミング
moto=moto[0:h_min, 0:w_min]
hikaku=hikaku[0:h_min, 0:w_min]
```
# 画像の位置合わせを行う
次に、元画像と比較画像の画像位置合わせを行います。
位置合わせには、テンプレートマッチングや特徴点を使って合わせる方法がありますが、ここではそれぞれの画像の特徴点をマッチングさせる方法をとります。
そして、一致する特徴点を使って、射影変換して位置合わせを行います。

```python
#特徴量抽出アルゴリズム ORB (Oriented FAST and Rotated BRIEF)
detector = cv2.ORB_create()

#それぞれの画像の特徴点と特徴量
moto_kp, moto_desc = detector.detectAndCompute(moto, None)
hikaku_kp, hikaku_desc = detector.detectAndCompute(hikaku, None)

#マッチングの準備
matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
#マッチング
matches = matcher.match(moto_desc, hikaku_desc)

#画像同士の対応する座標リストの作成
coordinate_moto=[]
coordinate_hikaku=[]

#良いマッチングのものだけ抜き出す
for i in range(len(matches)):
    if matches[i].distance<20:
        coordinate_moto.append([moto_kp[matches[i].queryIdx].pt[0],moto_kp[matches[i].queryIdx].pt[1]])
        coordinate_hikaku.append([hikaku_kp[matches[i].trainIdx].pt[0],hikaku_kp[matches[i].trainIdx].pt[1]])

#numpyアレイに変更
coordinate_moto=np.float32(coordinate_moto)
coordinate_hikaku=np.float32(coordinate_hikaku)

#マッチングした座標で変換行列を作成
matrix, _ =cv2.findHomography(coordinate_hikaku, coordinate_moto, cv2.RANSAC) 
#比較画像を元画像に合わせるための射影変換
hikaku = cv2.warpPerspective(hikaku, matrix, (w_min, h_min)) 
```
# 画像の比較を行う
画像の輝度の差を、違っている箇所として見つけ出します。

まず、準備として、２つの画像をグレー画像に変換します。
また、画像のノイズや位置合わせ精度などにより、意図しない違いまで検出してしまうことがありますので、調整用のパラメータも準備しておきます。
```python
#グレー画像の輝度の差で違いを見つけるため、グレーに変換
moto_gray=cv2.cvtColor(moto, cv2.COLOR_BGR2GRAY)
hikaku_gray=cv2.cvtColor(hikaku, cv2.COLOR_BGR2GRAY)
#ここで重ね合わせ画像も作っておく
moto_and_hikaku=cv2.addWeighted(src1=moto, alpha=0.5, src2=hikaku, beta=0.5, gamma=0)

#調整用パラメータ
th_value=3  #輝度の差のしきい値
kernel_value=3  #ノイズを消すピクセルサイズ
```
輝度の差をとり、その差がしきい値以上あれば、白く、それ以外は黒にします。
ノイズや位置合わせのちょっとした違いなどは、モルフォロジー変換を使って白いところを消します。
```python
#画像の比較、輝度の差がしきい値以上のものは、白く、それ以外のところは黒にする
img_diff=cv2.absdiff(moto_gray, hikaku_gray)
_, img_diff=cv2.threshold(img_diff, th_value, 255, cv2.THRESH_BINARY)
kernel=np.ones((kernel_value,kernel_value), dtype=np.uint8)
img_diff=cv2.morphologyEx(img_diff, cv2.MORPH_OPEN, kernel)
```
# 違っているところを表示する
白く残っているところが、違っているところなので、輪郭を検出します。
その輪郭の座標を使って、赤枠の四角を準備し、画像に合わせます。
```python
#白い部分の輪郭を検出
contours, _=cv2.findContours(img_diff, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

#輪郭を赤枠の四角に変換
rectangle_img=np.zeros((h_min,w_min,3),np.uint8) #真っ黒の画像を作る
for ct in contours:
    x, y, ww, hh= cv2.boundingRect(ct) #輪郭を位置を抽出
    rectangle_img=cv2.rectangle(rectangle_img, (x-5,y-5),(x+ww+5,y+hh+5),(0, 0, 255), 2)

#画像に赤枠を重ね合わせ
img_result = cv2.addWeighted(src1=rectangle_img,alpha=1,src2=moto_and_hikaku,beta=1,gamma=0)

#画像を確認
cv2.imshow('img_result', img_result)
cv2.waitKey()
cv2.destroyAllWindows()

#結果を保存
cv2.imwrite('C:\\Users\\○○○\\result.jpg',img_result)
```
いかがでしたでしょうか。もっとスマートな方法があると思いますので、お気づきの点がありましたら、ご教示ください。

# 参考ページ
https://qiita.com/suuungwoo/items/9598cbac5adf5d5f858e

http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/index.html