Pythonを用いてサイゼリヤの間違い探しを攻略

# はじめに

子どもの頃、没頭したことを大人になって再度やってみると思いの外、
楽しかったりしますよね!?

僕の場合はサイゼリヤの間違い探しに命を燃やしていました！
間違い探しの難易度が地味に高く、最後の一つが中々見つからなかった記憶があります...

そこで大人になった今、人生で得てきた経験を活かし、
Pythonのライブラリを用いて画像の比較を行い、
差分をハイライトした画像を生成してイゼリヤの間違い探しを攻略していこうと思います!!!

# 概要

今回比較に使用する画像はこちらになります！
画像は下記サイトから過去問を取得して画像を分割しました！
https://www.saizeriya.co.jp/entertainment/

a.png
![a.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713139/64bd97b8-3f89-1c0a-e12e-9d9a312c5773.png)

b.png
![b.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713139/ce8e351e-14fc-d6c8-49da-216c20e8dbdf.png)

目視で間違いを探すそうとすると思いのほか時間がかかってしまうと思います...




<font color="red">**しかし、そんな時にPythonを用いれば下記の通り、簡単に間違いを可視化できるのです!!!**</font>





<details><summary><font color="red">※ネタバレ注意</font>
間違い探し中の方は開かないでください！</summary>

Answer.png
![Answer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713139/1808b44c-78eb-e3e0-131d-0b4fc2f6416d.png)


</details>



# 前提条件

・Pythonがインストールされていること
・必要なライブラリ(cv2,numpy)がインストールされていること
※cv2は下記のコマンドでインストールしてください
```bat:cv2_install.bat
pip install opencv-contrib-python
```
・比較する画像(2枚)が準備できていること

# プログラム

プログラムは大きく分けて1つの共通で使用する変数の定義と3つの機能に分かれています。

まず初めに完成形のソースコードを添付して順に解説していきたいと思います。

<details><summary>完成形のソースコード</summary>

```python:Image_Comparison.py
##############################################
# Name : Image_Comparison.py                 #
# Author : Terada Necromancer                #
# date : 2024/02/21                          #
# Editor :                                   #
# Editdate : 20XX/XX/XX                      #
# Version 1.0                                #
#                                            #
# Document path :                            #
#                                            #
##############################################

##############################################
# import                                     #
##############################################

import os
import subprocess
from datetime import datetime, timedelta
import cv2
import numpy as np

##############################################
# variable setting                           #
##############################################

# 実行しているpyファイルのディレクトリの取得
cullent_dir = os.getcwd()
# 1つ前のディレクトリに移動
os.chdir('../')
# 移動後ディレクトリの取得
cullent_dir = subprocess.getoutput("cd")
# 比較画像の格納場所
input_dir = fr"{cullent_dir}\input"
# 合成画像の格納場所
output_dir = fr"{cullent_dir}\output"

# 比較画像のファイルのパス
image1 = cv2.imread(fr"{input_dir}\a.png")
image2 = cv2.imread(fr"{input_dir}\b.png")
# 画像の比率指定
height = image1.shape[0]
width = image1.shape[1]
img_size = (int(width), int(height))
# 比較のため、画像サイズを同じサイズにリサイズ
image1 = cv2.resize(image1, img_size)
image2 = cv2.resize(image2, img_size)

##############################################
# define                                     #
##############################################

# ■■■■■ Def1.画像のヒストグラム比較による類似度の算出 ■■■■■
def Diff_Histogram():

    # 画像をヒストグラム化する
    image1_hist = cv2.calcHist([image1], [2], None, [256], [0, 256])
    image2_hist = cv2.calcHist([image2], [2], None, [256], [0, 256])

    # ヒストグラムした画像の類似度算出
    Histogram = str(cv2.compareHist(image1_hist, image2_hist, 0))
    
    # アクセストークンを引数で返す
    return Histogram
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

# ■■■■■ Def2.画像の画素数比較による類似度の算出 ■■■■■
def Diff_Pixels():

    # 画素数が一致している割合算出
    Pixels = str(np.count_nonzero(image1 == image2) / image2.size)
    
    # アクセストークンを引数で返す
    return Pixels
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

# ■■■■■ Def3.画像を比較して差分をハイライト ■■■■■
def Diff_Highlight():

    # ２画像の差分を計算
    im_diff = image1.astype(int) - image2.astype(int)

    # 差分を2で割り小数点以下切り捨てたものに（灰色：128）を足し合わせる
    im_diff_center = np.floor_divide(im_diff, 2) + 128

    # 差分をハイライトをして表示した画像を保存
    cv2.imwrite(fr"{output_dir}/highlight.png", im_diff_center)
    image3 = cv2.imread(fr"{output_dir}/highlight.png")

    # 画像の合成
    image = cv2.addWeighted(image1, 0.05, image3, 0.95, 0.0) 
    cv2.imwrite(fr"{output_dir}/{today}_Comparison.png", image)
# ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■
        
##############################################
# programs                                   #
##############################################

# 日付取得
today = datetime.now()
yesterday = today - timedelta(1)

# gt : YYYY/MM/DD hh:mm:ss
gt = str(today)[:19].replace('-', '/')
# nowaday : YYYY/MM/DD
nowaday = str(today)[:10].replace('-', '/')
# today : YYYYMMDD
today = str(today)[:10].replace('-', '')

# Def1を用いて画像のヒストグラム比較による類似度の算出
Digree_of_Histogram = float(Diff_Histogram())

# Def2を用いて画像の画素数比較による類似度の算出
Digree_of_Pixels = float(Diff_Pixels())

print(fr"ヒストグラム類似度 : {Digree_of_Histogram}")
print(fr"画素数類似度 : {Digree_of_Pixels}")

if Digree_of_Histogram == 1 and Digree_of_Pixels == 1 :
    print(f'\n画像の比較結果 : 画像は同じです。')
else :
   print(f'\n画像の比較結果 : 画像は違います。\n比較画像を生成しました。')
   # Def3を用いて画像の差異を確認し、画像の合成
   Diff_Highlight()
```

</details>


### 1.共通の変数定義

■ 概要
Pythonのプログラムを実行するにあたって必要な、
ライブラリのimportやパスやファイル名の変数定義を行っています。

■ 詳細
今回はPythonの実行ファイルを下記のようなファイルに配置を行い実行しました。
```
[Image_Comparison]
　L_[script]
　|　　　L_[Image_Comparison.py]
　L_[input]
　|　　　L_[a.png]
　|　　　L_[b.png]
　L_[output]
```
生成した画像を[output]ディレクトリに出力するため、
カレントディレクトリを取得してパスを指定しています。
```
# 実行しているpyファイルのディレクトリの取得
cullent_dir = os.getcwd()
# 1つ前のディレクトリに移動
os.chdir('../')
# 移動後ディレクトリの取得
cullent_dir = subprocess.getoutput("cd")
# 比較画像の格納場所
input_dir = fr"{cullent_dir}\input"
# 合成画像の格納場所
output_dir = fr"{cullent_dir}\output"
```


その後に画像のパスを指定して、比較を行うために画像サイズが同じになるようリサイズします。
```
# 比較画像のファイルのパス
image1 = cv2.imread(fr"{input_dir}\a.png")
image2 = cv2.imread(fr"{input_dir}\b.png")
# 画像の比率指定
height = image1.shape[0]
width = image1.shape[1]
img_size = (int(width), int(height))
# 比較のため、画像サイズを同じサイズにリサイズ
image1 = cv2.resize(image1, img_size)
image2 = cv2.resize(image2, img_size)
```

これでプログラムの実行に必要な下準備が完了となります。



### 2-1.画像のヒストグラム比較による類似度の算出

■ 概要
1で定義した画像ファイルのヒストグラムを計算し、
正規化を行い、比較して画像の類似性を計算します。

■ 詳細
画像のPixelsごとの色の明るさをレベル別にデータ化された配列を取得します。
```
# 画像をヒストグラム化する
image1_hist = cv2.calcHist([image1], [2], None, [256], [0, 256])
image2_hist = cv2.calcHist([image2], [2], None, [256], [0, 256])
```

その後、正規化を行って2枚の画像を比較します。
```
# ヒストグラムした画像の類似度算出
Histogram = str(cv2.compareHist(image1_hist, image2_hist, 0))
```

■ 結果
ヒストグラムの類似性が取得できます。
```結果
ヒストグラム類似度 : 0.9888518272552516
```



### 2-2.画像の画素数比較による類似度の算出

■ 概要
1で定義した画像ファイルの画素数を比較して類似性を計算します。

■ 詳細
画像のPixelsごとの画素数を比較します。
```
# 画素数が一致している割合算出
Pixels = str(np.count_nonzero(image1 == image2) / image2.size)
```

■ 結果
画素数の類似性が取得できます。
```結果
画素数類似度 : 0.5584542788016243
```

※画像の切り抜きが下手だったため、類似性が下がってしまっています...



### 2-3.画像を比較して差分をハイライト

■ 概要
もし、比較した2枚の画像が同じでない(ヒストグラム類似度と画素数類似度が1でない)場合、差分を取得してハイライトした画像を生成し、
ハイライトをした画像を比較元の画像に合成を行う。

■ 詳細
画像の差分を整数型で取得します。
```
# ２画像の差分を計算
im_diff = image1.astype(int) - image2.astype(int)
```


整数型で取得した差分を2で割り、小数点以下を切り捨て灰色を示す128を足し合わせます。
※BGRを0~255の範囲に押えるため、2で割り小数点以下切り捨て128を足し合わせる。
```
# 差分を2で割り小数点以下切り捨てたものに（灰色：128）を足し合わせる
im_diff_center = np.floor_divide(im_diff, 2) + 128
```
    
差分をハイライトをして表示した画像を保存してimage3と変数定義します。
```
# 差分をハイライトをして表示した画像を保存
cv2.imwrite(fr"{output_dir}/highlight.png", im_diff_center)
image3 = cv2.imread(fr"{output_dir}/highlight.png")
```

画像を元画像 5％ : ハイライト画像 95％の比率で合成をして保存します。
```
# 画像の合成
image = cv2.addWeighted(image1, 0.05, image3, 0.95, 0.0) 
cv2.imwrite(fr"{output_dir}/{today}_Comparison.png", image)
```

■ 結果
差分がハイライトされた画像の作成ができました！
![20240221_Comparison.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3713139/47640364-fd92-3b00-316f-f2d64c544bc0.png)



# さいごに

画像の編集が下手だったため、明確な差分以外も表示されていまいましたが、
きちんと間違いは見つけられる精度となりました！

また、今回はヒストグラム類似度が 98.885％ 程となりましたが、
ヒストグラム類似度を各間違い探しに適用することによって、
サイゼリヤの間違い探しの難易度が測定できるかもしれません!!!

有志の方がいらっしゃれば、ぜひ難易度の測定を行い、
Tier表など作成していただきたいです!!!