OpenCVを使ってサイゼリヤの間違い探しを簡単に解いちゃう!!

この記事はリンク情報システムの[2021新春アドベントカレンダー Tech Connect](https://qiita.com/dgkz/items/eabde6515457d8acec9b)のリレー記事です。
engineer.hanzomon のグループメンバによってリレーされます。
（リンク情報システムのFacebookは[こちら](https://ja-jp.facebook.com/lis.co.jp/)から）

2021新春アドベントカレンダー Tech Connect インデックスは[こちら]()

---------------------------------------

#はじめに
明けましておめでとうございます。
最近私はOpenCVの齧っておりまして、これを使い何か面白いこと出来ないかと思い、サイゼリヤの間違い探しを何とかして解けんじゃね？と思いチャレンジしてみました。

#環境, 利用技術
Windows10 Pro
Python 3.8.1
OpenCV 4.3
Numpy

#今回解く間違い探し
サイゼリヤさんのHPにある間違い探しの画像を使用させていただきました。
リンクは[こちら](https://www.saizeriya.co.jp/entertainment/)

今回はこちらの間違い探しを解いてみます。
![saize_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/950967/674d9872-f908-2fc3-1ee5-92963b87d10e.png)
(2020年7月 暑い夏、たくさん食べて明るく元気になろう！)
Copyright Saizeriya Co,. Ltd All rights reserved.

#実装
##左右の画像差分を抽出
左右の画像差がなるべく小さくなるよう余白(padding)を選び、absdiffで差分抽出します。

公式の画像は、左右の端に白帯があったり、左右同じ大きさで分けても若干ずれていたりします。
その為、左右差が一番少ない部分をまずは探し当てます。(これが一番面倒)

```python
    # 余白を取り除いたときに2つの画像が最も一致するような適切な余白（padding）の幅を見つける
    img_src = img
    img_diffs = []
    paddings = []
    for padding in range(1, 50):
        # 画像の余白を削除
        img = img_src[:, padding:-padding]

        # 画像を左右で分割する
        height, width, channels = img.shape[:3]
        img1 = img[:, :width//2]
        img2 = img[:, width//2:]

        # 画像サイズを合わせる(小さい方に)
        img1, img2 = FitImageSize_small(img1, img2)

        # 2つの画像の差分を算出
        img_diff = cv2.absdiff(img2, img1)
        img_diff_sum = np.sum(img_diff)

        img_diffs.append((img_diff, img_diff_sum))
        paddings.append(padding)

    # 差分が最も少ないものを選ぶ
    img_diff, _ = min(img_diffs, key=lambda x: x[1])
    index = img_diffs.index(min(img_diffs, key=lambda x: x[1]))
    cv2.imshow("img_diff", img_diff)
```

![diff.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/950967/a39b8b31-d77e-281e-9e30-aa07ecfb24be.png)

##右側画像の切り取り。
次に、「左右の余白を削除」「左右を分割」「右側を表示」を行います。

```python
    padding = paddings[index]
    # 画像の余白を削除
    img = img_src[:, padding:-padding]
    # 画像を左右で分割する
    height, width, channels = img.shape[:3]
    img1 = img[:, :width//2]
    img2 = img[:, width//2:]
    cv2.imshow("img2",img2)
```

![right.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/950967/a360308d-a6a0-3e30-800c-e91be9970381.png)


##差分抽出結果と、右画像を合成
差分画像に合わせるために、右画像はグレースケール化します。

```python
    # 画像サイズを合わせる(小さい方に)
    img2, img_diff = FitImageSize_small(img2, img_diff)
    # 画像合成
    add = ImageComposition(img2, img_diff)
    cv2.imshow("add",add)
```

![重ね合わせ.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/950967/9b89f3b3-9539-86f6-8474-989c8c675a9e.png)

##じゃーん！。。。。あれなんか足りない...？
間違いは全部で10個のはず...
![解答数え.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/950967/8a541389-561a-91e7-fed1-c9fd8daedc13.png)

##よく探したらここでした。。。
位置や物が異なる場合はわかりやすかったのですが、色だけが異なる場合は弱いですね。。。
![10個目さがし.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/950967/9f0ae83c-6095-e381-34f8-361e22ea871c.png)

##ソースコード全体

```python
import cv2
import math
import numpy as np

# --------------------------------------------------- #
# 画像合成                                             #
# --------------------------------------------------- #
def ImageComposition(img2, result):
    img3 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY) # グレースケール化
    img3 = cv2.cvtColor(img3,cv2.COLOR_GRAY2BGR) # グレースケールのままカラー画像にする
    # コントラスト、明るさを変更する。
    img3 = adjust(img3, alpha=0.25)
    add = cv2.add(img3, result) # 画像を合成する
    return add

# α  はゲイン (gain) 、βはバイアス (bias)
def adjust(img, alpha=1.0, beta=0.0):
    # 積和演算を行う。
    dst = alpha * img + beta
    # [0, 255] でクリップし、uint8 型にする。
    return np.clip(dst, 0, 255).astype(np.uint8)

def FitImageSize_small(img1, img2):
    # height
    if img1.shape[0] > img2.shape[0]:
        height = img2.shape[0]
        width = img1.shape[1]
        img1 = cv2.resize(img1, (width, height))
    else:
        height = img1.shape[0]
        width = img2.shape[1]
        img2 = cv2.resize(img2, (width, height))
    
    # width
    if img1.shape[1] > img2.shape[1]:
        height = img1.shape[0]
        width = img2.shape[1]
        img1 = cv2.resize(img1, (width, height))
    else:
        height = img2.shape[0]
        width = img1.shape[1]
        img2 = cv2.resize(img2, (width, height))
    return img1, img2

try:
    img = cv2.imread('元画像パス')

    if img is None:
        print('ファイルを読み込めません')
        import sys
        sys.exit()

    cv2.imshow("img", img)

    # 余白を取り除いたときに2つの画像が最も一致するような適切な余白（padding）の幅を見つける
    img_src = img
    img_diffs = []
    paddings = []
    for padding in range(1, 50):
        # 画像の余白を削除
        img = img_src[:, padding:-padding]

        # 画像を左右で分割する
        height, width, channels = img.shape[:3]
        img1 = img[:, :width//2]
        img2 = img[:, width//2:]

        # 画像サイズを合わせる(小さい方に)
        img1, img2 = FitImageSize_small(img1, img2)

        # 2つの画像の差分を算出
        img_diff = cv2.absdiff(img2, img1)
        img_diff_sum = np.sum(img_diff)

        img_diffs.append((img_diff, img_diff_sum))
        paddings.append(padding)

    # 差分が最も少ないものを選ぶ
    img_diff, _ = min(img_diffs, key=lambda x: x[1])
    index = img_diffs.index(min(img_diffs, key=lambda x: x[1]))
    cv2.imshow("img_diff", img_diff)

    
    padding = paddings[index]
    # 画像の余白を削除
    img = img_src[:, padding:-padding]
    # 画像を左右で分割する
    height, width, channels = img.shape[:3]
    img1 = img[:, :width//2]
    img2 = img[:, width//2:]
    cv2.imshow("img2",img2)

    # 画像サイズを合わせる(小さい方に)
    img2, img_diff = FitImageSize_small(img2, img_diff)
    # 画像合成
    add = ImageComposition(img2, img_diff)
    cv2.imshow("add",add)

    cv2.waitKey(0)
    cv2.destroyAllWindows()
except:
    import sys
    print("Error:", sys.exc_info()[0])
    print(sys.exc_info()[1])
    import traceback
    print(traceback.format_tb(sys.exc_info()[2]))
```
#まとめ
今回は、サイゼリヤさんの公式サイトの画像を引用させていただき解いてみましたが、
理想を言えば店頭で使えるよう、スマホで間違い探しを写真で撮り、スマホ上で上記のような差分抽出が出来ると良いですよね。
(今回はそこまでの余力はなかったです。。。)


明日は@marumomijiです！引き続きよろしくお願いします！

--------------------------
[リンク情報システム株式会社](https://www.lis.co.jp/)では**一緒に働く仲間**を[随時募集](https://www.lis.co.jp/employ_top)しています！
また、**お仕事のご依頼、ビジネスパートナー様も募集**しております。[お気軽にご連絡](https://www.lis.co.jp/inquiry)ください。
Facebookは[こちら](https://ja-jp.facebook.com/lis.co.jp/)。