Introduction
画像の類似度を測るimgsimの記事を投稿したところ、画面の差分を確認できないかとリクエストいただきました。
https://qiita.com/kagami_t/items/a1cae07c9565ce501ced
imgsim
は特徴ベクトルから画像間の距離を測るもので、差分検出とは異なります。
画像の差分はOpenCV
による検出が分かりやすいため、imgsim
の記事と同様に手早く差分を取得して比較するスクリプトを紹介します。1
本記事では基本編で、簡単な画像差分を検出して比較します。
勿論アプリや web 画面にも応用可能です。
そして応用編で、難しいと話題になっているサンリオの間違い探しを差分検出で高速クリアしてみます。
(間違い探しクリアって誰得? と思ったのですが、非エンジニアの知人にこの話をしたところ CNN よりずっと食いつきが良かったです...)
https://twitter.com/hapidanbui/status/1669223213199503360
© 2023 SANRIO CO., LTD. / Via Twitter: @hapidanbui
本記事が少しでも読者様の学びに繋がれば幸いです!
「いいね」をしていただけると今後の励みになるので、是非お願いします!
環境
Ubuntu22.04
Python3.11
実装
先に結論として出力に使用したソースコードを紹介します。
解説はコメントアウトで詳細に書きました。
OpenCV
に馴染みのない方は参考にしてください。
=======================
サンプルコード
========================
import cv2
import matplotlib.pyplot as plt
import numpy as np
def compare_images(image1_path, image2_path):
"""
2つの画像を比較し、差分を表示する関数。
Args:
image1_path (str): 1つ目の画像のパス
image2_path (str): 2つ目の画像のパス
"""
# 画像を読み込む
image1 = cv2.imread(image1_path)
image2 = cv2.imread(image2_path)
# 差分画像を計算
diff = cv2.absdiff(image1, image2)
# グレースケールに変換
gray2 = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)
gray_diff = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
# BGRからRGBに変換
image1_rgb = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)
image2_rgb = cv2.cvtColor(image2, cv2.COLOR_BGR2RGB)
# カラーマップを適用するために差分画像を正規化
norm_diff = gray_diff / np.max(gray_diff)
# 差分画像に重みをかけて2枚目の画像の色に反映
diff_img = cv2.addWeighted(gray2, 0.1, gray_diff, 2, 100)
diff_colored = np.zeros_like(image2_rgb)
diff_colored[..., 0] = image2_rgb[..., 0] * norm_diff
diff_colored[..., 1] = image2_rgb[..., 1] * norm_diff
diff_colored[..., 2] = image2_rgb[..., 2] * norm_diff
# 結果をMatplotlibで表示
fig, axes = plt.subplots(2, 2, figsize=(10, 10))
# 1枚目の画像を表示
axes[0, 0].imshow(image1_rgb)
axes[0, 0].set_title("Image 1")
axes[0, 0].axis("off")
# 2枚目の画像を表示
axes[0, 1].imshow(image2_rgb)
axes[0, 1].set_title("Image 2")
axes[0, 1].axis("off")
# 差分画像(グレースケール)を表示
axes[1, 0].imshow(diff_img, cmap="gray")
axes[1, 0].set_title("Difference (Grayscale)")
axes[1, 0].axis("off")
# 差分画像(カラー)を表示
axes[1, 1].imshow(diff_colored)
axes[1, 1].set_title("Difference (Colored)")
axes[1, 1].axis("off")
plt.tight_layout()
plt.show()
if __name__ == "__main__":
# 2つの画像を比較して違いを検出
image1_path = "./Pictures/p.png"
image2_path = "./Pictures/output.png"
compare_images(image1_path, image2_path)
実行すると、2☓2 マスに以下の画像を出力します。
- 1 行 1 列目: 変数
image1_path
で指定した 1 枚目の画像を出力。 - 1 行 2 列目: 変数
image2_path
で指定した 2 枚目の画像を出力。 - 2 行 1 列目: 1 枚目と 2 枚目の差分を白、それ以外を白黒で出力。
- 2 行 2 列目: 1 枚目と 2 枚目の差分をカラーで出力。
それでは出力内容を見ていきましょう。
基本編
実装したスクリプトで私のペンギンアイコンから画像差分を検出します。
-
線の有無を検出
1 枚目に私のアイコン、2 枚目に横線を一本引いた画像を用意します。
線はOpenCV
を使うと簡単に引けます。
横線が浮かび上がりました。
差分が視覚的で良い感じです。 -
色違いを検出
2 枚目と同じ画像を用いて、片方だけ帽子の色を赤くしました。
急にサンタさん感が増します。
色の変化した帽子が浮かび上がりました。
線だけでなく、色の差分もしっかり検出できています。
応用編
今回の間違い探しで用いる画像は以下の 2 枚です。
間違いは 10 ヶ所あります。
© 2023 SANRIO CO., LTD. / Via Twitter: @hapidanbui
10 ヶ所全部見つかりましたか?
試しに目視でやりましたが 8 ヶ所で目が疲れてきました...
実装したスクリプトで実行しちゃいましょう。
© 2023 SANRIO CO., LTD. / Via Twitter: @hapidanbui
何とか 10 ヶ所すべて出力できました!
右下の木目が若干見辛いですが、これ以上はノイズが乗ってくるので検出できただけ良しとします。
簡単な間違い探しや Web 画面の差分であればもっと分かりやすいため十分機能を果たせています。
おまけ
実装したスクリプトに至るまでの過程を記載します。
差分検出
まずは簡単に差分を検出しました。
1 枚目と 2 枚目の差分を 3 枚目で出力します。
=======================
差分検出サンプル
========================
import cv2
import matplotlib.pyplot as plt
def compare_images(image1_path, image2_path):
image1 = cv2.imread(image1_path)
image2 = cv2.imread(image2_path)
diff = cv2.absdiff(image1, image2)
gray_diff = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
image1_rgb = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)
image2_rgb = cv2.cvtColor(image2, cv2.COLOR_BGR2RGB)
fig, axes = plt.subplots(2, 2, figsize=(10, 10))
axes[0, 0].imshow(image1_rgb)
axes[0, 0].set_title("Image 1")
axes[0, 0].axis("off")
axes[0, 1].imshow(image2_rgb)
axes[0, 1].set_title("Image 2")
axes[0, 1].axis("off")
axes[1, 0].imshow(gray_diff, cmap="gray")
axes[1, 0].set_title("Difference")
axes[1, 0].axis("off")
axes[1, 1].axis("off")
plt.tight_layout()
plt.show()
image1_path = "./Pictures/1.png"
image2_path = "./Pictures/2.png"
compare_images(image1_path, image2_path)
© 2023 SANRIO CO., LTD. / Via Twitter: @hapidanbui
記事作成前の完成イメージはこれでしたが、目視で追うのが少々面倒です。
矩形出力
=======================
矩形サンプル
========================
import cv2
import matplotlib.pyplot as plt
import numpy as np
def compare_images(image1_path, image2_path):
image1 = cv2.imread(image1_path)
image2 = cv2.imread(image2_path)
diff = cv2.absdiff(image1, image2)
gray_diff = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
image1_rgb = cv2.cvtColor(image1, cv2.COLOR_BGR2RGB)
image2_rgb = cv2.cvtColor(image2, cv2.COLOR_BGR2RGB)
norm_diff = gray_diff / np.max(gray_diff)
image2_diff = image2_rgb.copy()
contours, _ = cv2.findContours(
gray_diff, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(image2_diff, (x, y), (x + w, y + h), (255, 0, 0), 2)
fig, axes = plt.subplots(2, 2, figsize=(10, 10))
axes[0, 0].imshow(image1_rgb)
axes[0, 0].set_title("Image 1")
axes[0, 0].axis("off")
axes[0, 1].imshow(image2_rgb)
axes[0, 1].set_title("Image 2")
axes[0, 1].axis("off")
axes[1, 0].imshow(gray_diff, cmap="gray")
axes[1, 0].set_title("Difference (Grayscale)")
axes[1, 0].axis("off")
axes[1, 1].imshow(image2_diff)
axes[1, 1].set_title("Difference with Rectangles")
axes[1, 1].axis("off")
plt.tight_layout()
plt.show()
image1_path = "./Pictures/1.png"
image2_path = "./Pictures/2.png"
compare_images(image1_path, image2_path)
何か思っていたのと違う...
小さすぎる差分はまとめるようにします。
矩形出力(差分のみ)
for contour in contours:
area = cv2.contourArea(contour)
# 面積が一定以上の場合にのみ矩形を描画
if area > 100:
x, y, w, h = cv2.boundingRect(contour)
cv2.rectangle(image2_diff, (x, y), (x + w, y + h), (255, 0, 0), 2)
- 出力結果
© 2023 SANRIO CO., LTD. / Via Twitter: @hapidanbui
簡単な箇所は検出できています。
基本編の差分であれば十分な気もしますが、悔しいので続けました。
とはいえ単純な手法で矩形出力は厳しことが分かり、実装したスクリプトのように差分を浮かび上がらせて完成させました。
最後に
OpenCV×Python
は良い教材が少ないので、私はUdemy
で勉強しました。
https://www.udemy.com/course/pythonopencv/
Introductionにも記載したSIFT
, AKAZE
等の特徴点抽出についても触れており、大変勉強になりました。
OpenCV
の諸機能を淡々と進められるので、Python
を抵抗なく書ける程度の知識がある方におすすめです。
最後まで閲覧頂きありがとうございました。
本記事がお役に立てば幸いです!
参考 URL
https://peaceandhilightandpython.hatenablog.com/entry/2016/01/16/002028
-
ノイズ等も考慮して正確に取得するのであれば
SIFT
,AKAZE
等の特徴点抽出や、機械学習を用いる必要があります。 ↩