« iPad mini (第6世代)を使って一週間 | トップページ | Windows 11の悪さ? »

2022年2月22日 (火)

OpenCVでサイゼリヤの間違い探しを解くやつ

最近、なぜかサイゼリヤがトレンドに上がってますね。

いいお店だと思います。あそこのミラノ風ドリア、私は好きです。

と、そんな話は置いておき、最近、2つの画像を比較して、その変化部分を抽出するやつを作る必要に迫られました。

このため、まずサイゼリヤの間違い探しをさせてみよう、ということになった次第です。

参考サイトは、以下。

 エンターテイメント|サイゼリヤ

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

サイゼリヤの間違い探しを画像処理で解いてみた - Kasasagi’s memorandum

まず、サイゼリヤのサイトから、間違い探しの画像を一つチョイスし、それの左右をトリムして、別々の画像ファイルとして保存しておきます。

その際に、ちょっとだけ左右画像のカット位置を変えておきました。この両者、あえて位置をずらしてます。

Zeria1

Zeria2

その位置補正までを、プログラムにやらせるのが目的です。

で、コードは以下。


import cv2
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 = img1[:height,:width] # ゼロ点合わせで大きい方の画像の下をカット
    else:
        height = img1.shape[0]
        width = img2.shape[1]
        img2 = img2[:height,:width]

    # width
    if img1.shape[1] > img2.shape[1]:
        height = img1.shape[0]
        width = img2.shape[1]
        img1 = img1[:height,:width] # ゼロ点合わせで大きい方の画像の右をカット
    else:
        height = img2.shape[0]
        width = img1.shape[1]
        img2 = img2[:height,:width]
    return img1, img2

try:
    # === 画像位置合わせを実施 ===
    # 使う画像は「サイゼリヤの間違い探し」: https://www.saizeriya.co.jp/entertainment/
    # 参照: https://qiita.com/suuungwoo/items/9598cbac5adf5d5f858e
    #       https://qiita.com/h-yanai/items/1e33fd93e5cb1ac98398?utm_campaign=popular_items&utm_medium=feed&utm_source=popular_items

    float_img = cv2.imread('img/zeria1.jpg')
    ref_img = cv2.imread('img/zeria2.jpg')

    akaze = cv2.AKAZE_create()
    float_kp, float_des = akaze.detectAndCompute(float_img, None)
    ref_kp, ref_des = akaze.detectAndCompute(ref_img, None)

    bf = cv2.BFMatcher()
    matches = bf.knnMatch(float_des, ref_des, k=2)

    good_matches = []
    for m, n in matches:
        if m.distance < 0.75 * n.distance:
            good_matches.append([m])

    # 適切なキーポイントを選択
    ref_matched_kpts = np.float32(
        [float_kp[m[0].queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
    sensed_matched_kpts = np.float32(
        [ref_kp[m[0].trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)

    # ホモグラフィを計算
    H, status = cv2.findHomography(
        ref_matched_kpts, sensed_matched_kpts, cv2.RANSAC, 5.0)

    # 画像を変換
    warped_image = cv2.warpPerspective(
        float_img, H, (float_img.shape[1], float_img.shape[0]))

    #cv2.imwrite('warped.jpg', warped_image) # 確認用:変換後の画像を保存

    # ==== ここから、間違い探し部分 ====

    img_diffs = []
    paddings = []
    for padding in range(1, 50):
        img1 =  warped_image
        img2 = ref_img


        # 画像サイズを合わせる(小さい方に)
        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)
    cv2.imwrite('img_diff.jpg', img_diff)

    padding = paddings[index]
    img1 = float_img
    img2 = ref_img
    cv2.imshow("img1",img1)
    cv2.imshow("img2",img2)

    # 画像サイズを合わせる(小さい方に)
    img2, img_diff = FitImageSize_small(img2, img_diff)
    # 画像合成
    add = ImageComposition(img2, img_diff)
    cv2.imshow("add",add)
    cv2.imwrite('img_diff_add.jpg', 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]))
   

これを「pydiff.py」という名前で保存しておきます。

御覧の通り、OpenCVとnumpyしか使ってませんね。

これで、平行位置と台形歪み程度なら補正し、2画像の位置と大きさを合わせた後に、左右の差分を検出してくれます。

キーになるのは、AKAZEという画像特徴量を使った位置合わせと、画像サイズ合わせ、差分を取るところですが……その辺りは、コードを解読願います(適当)

これを実行すると、差分のみのファイル「img_diff.jpg」と、それを元画像と重ねた「img_diff_add.jpg」とが得られます。

Img_diff

Img_diff_add

10か所の間違いがありますが、すべて、あぶりだしております(うっすらと、中央のどでかいストローの刺さったコップの水面が出てますが……ここだけは違います)

うん、正解ですね。なかなか優秀じゃん。

と思いきや、これがサイトの綺麗な画像ではなく、写真を使うと、いろいろと問題が出ました。

明るさや色がちょっとでも異なると、途端に差分が増えます。

写真に対して使う際は、コントラスト調整や畳み込み処理などを駆使して、どうにかその差分のある所だけを出せるようにする工夫が要ります。

その前段階の、位置合わせと差分位置を出す部分だけを公開しておきます。

例によって、自己責任にてお使いください。

なお、よくTwitter上に現れる間違い探し広告なんかも、これを使えば解けてしまいそうです。

にしても、サイゼリヤの間違い探しを眺めていたら。

サイゼリヤに行きたくなってきましたねぇ。。。

まだ、エスカルゴを食べたことがないんです。一度、食べてみたい。

サイゼリヤ おいしいから売れるのではない 売れているのがおいしい料理だ (日経ビジネス人文庫)

« iPad mini (第6世代)を使って一週間 | トップページ | Windows 11の悪さ? »

数値解析系」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

« iPad mini (第6世代)を使って一週間 | トップページ | Windows 11の悪さ? »

当ブログ内検索

スポンサード リンク

ブログ村

無料ブログはココログ