Pythonで画像を徹底比較!OpenCV, NumPyで完全一致判定・差分検出


画像処理やコンピュータビジョンの分野では、2つの画像が同じであるか(完全一致)、あるいはどこが異なるか(差分)を比較するニーズが頻繁に発生します。例えば、製造ラインでの欠陥検出、改ざんされた画像の特定、あるいは動画での動き検出などです。Pythonでは、OpenCVNumPyという強力なライブラリを組み合わせることで、これらの画像比較タスクを効率的に行うことができます。この記事では、画像を完全一致判定する方法と、画像間の差分を取得する方法について、具体的なサンプルコードを交えながら詳しく解説します。

画像比較の基本

デジタル画像はNumPy配列として表現されるため、画像比較は実質的にNumPy配列の比較演算に帰着します。画素ごとの値が一致するか、あるいは値の差がどのくらいあるか、といった観点で比較を行います。

重要: 画像を正確に比較するためには、画像のサイズ(幅、高さ)とチャンネル数、そしてデータ型が完全に一致している必要があります。異なる場合は、比較前に適切な前処理(リサイズ、型変換など)を行う必要があります。


1. 画像が「完全一致」するか判定する

2つの画像がピクセルレベルで完全に同じであるかを判定するには、NumPyの比較演算とnp.array_equal()が便利です。

np.array_equal():配列の完全一致判定

np.array_equal()は、2つのNumPy配列の形状、データ型、およびすべての要素が完全に一致するかどうかを真偽値で返します。

サンプルコード

Python
 
import numpy as np
import cv2
from PIL import Image

# ダミー画像を生成(テスト用)
img_a = np.zeros((100, 100, 3), dtype=np.uint8) # 黒い画像A
img_b = np.zeros((100, 100, 3), dtype=np.uint8) # 黒い画像B (Aと全く同じ)
img_c = np.zeros((100, 100, 3), dtype=np.uint8) # 黒い画像C
img_c[10:20, 10:20] = [255, 0, 0] # 一部を赤にする(Aとは異なる)

# 完全一致の判定
is_a_equal_b = np.array_equal(img_a, img_b)
is_a_equal_c = np.array_equal(img_a, img_c)

print(f"画像Aと画像Bは完全一致しますか?: {is_a_equal_b}")
print(f"画像Aと画像Cは完全一致しますか?: {is_a_equal_c}")

出力例

画像Aと画像Bは完全一致しますか?: True
画像Aと画像Cは完全一致しますか?: False

なぜ == ではないのか?

NumPy配列でimg_a == img_bのように比較演算子を使うと、結果はブール値の配列(各要素がTrueFalse)になります。これをall()any()と組み合わせることも可能ですが、np.array_equal()の方が、形状やデータ型も含めてシンプルに完全一致を判定できるため推奨されます。


2. 画像間の「差分」を取得する

画像間の差分を取得する方法はいくつかあり、目的によって使い分けます。最も一般的なのは、画素値の単純な減算や絶対差分、あるいはビット単位の差分です。

2-1. 単純な画素値の差分(np.subtract または -

2つの画像の対応する画素値から一方を減算することで差分を計算します。結果には負の値が含まれる可能性があるため、np.uint8型で扱う場合は注意が必要です。

サンプルコード

Python
 
import numpy as np

# ダミー画像を生成
img1 = np.zeros((50, 50), dtype=np.uint8) # 真っ黒な画像
img2 = np.zeros((50, 50), dtype=np.uint8)
img2[10:30, 10:30] = 100 # 中央を灰色にする

# 単純な差分
# (0 - 100 = -100) となるが、uint8ではオーバーフローして予期せぬ結果に
# 通常は浮動小数点型に変換するか、後述のcv2.absdiffを使う
diff_simple = img1 - img2
print("単純な差分 (負の値が含まれる可能性):\n", diff_simple[10,10]) # -100 (実際はuint8なので255-100+1=156などになる)

注意点: np.uint8のような符号なし整数型で負の計算をすると、オーバーフローが発生し、意図しない結果(例: -1255になる)になるため、通常は以下の絶対差分を使用します。

2-2. 絶対差分 (cv2.absdiff または np.abs())

画素値の絶対差分を取ることで、2つの画像間でどれだけ値が異なるかを正の値で表現できます。これは欠陥検出などで非常に有効です。

cv2.absdiff():OpenCVで効率的な絶対差分

cv2.absdiff()は、2つの画像間の要素ごとの絶対差分を計算します。結果は元の画像と同じデータ型で返されます。

書式

Python
 
cv2.absdiff(src1, src2[, dst])
  • src1, src2: 比較したい入力画像。同じサイズ、同じデータ型である必要があります。

サンプルコード

Python
 
import cv2
import numpy as np

# ダミー画像を生成
img_original = np.zeros((100, 100, 3), dtype=np.uint8) # 黒
img_changed = img_original.copy()
img_changed[20:50, 20:50] = [0, 0, 255] # 青い四角を追加

# 絶対差分を計算
diff_abs = cv2.absdiff(img_original, img_changed)
print(f"絶対差分画像の形状: {diff_abs.shape}")

# 差分が強調された画像になる
# cv2.imwrite('diff_abs.jpg', diff_abs)

NumPyでの絶対差分 (np.abs())

OpenCVを使わず、NumPyだけで絶対差分を計算することも可能です。この場合、まずデータをより広い範囲を扱える型(例: int16)に変換してから計算し、その後np.uint8に戻すという手順を踏むのが安全です。

サンプルコード

Python
 
import numpy as np

# 上記の img_original, img_changed を使用

# NumPyで絶対差分
# 計算のためにint16に変換
diff_np_abs = np.abs(img_original.astype(np.int16) - img_changed.astype(np.int16)).astype(np.uint8)
print(f"NumPyでの絶対差分画像の形状: {diff_np_abs.shape}")

# 結果はPillowと連携して保存
from PIL import Image
# img_pil_diff = Image.fromarray(diff_np_abs)
# img_pil_diff.save('diff_np_abs.jpg')

2-3. ビットごとの差分(cv2.bitwise_xorなど)

2つの画像が完全に同じかどうかをビット単位で比較し、異なる部分を検出したい場合は、ビットごとのXOR演算が有効です。異なるビットがあれば1(白)、同じであれば0(黒)になります。

サンプルコード

Python
 
import cv2
import numpy as np

# ダミー画像を生成
img_base = np.zeros((50, 50), dtype=np.uint8)
img_overlay = np.zeros((50, 50), dtype=np.uint8)
img_overlay[10:30, 10:30] = 255 # 中央に白い四角

# ビットごとのXOR演算
# 異なる部分が白くなる
xor_diff = cv2.bitwise_xor(img_base, img_overlay)
print(f"XOR差分画像の形状: {xor_diff.shape}")

# cv2.imwrite('diff_xor.jpg', xor_diff)

まとめ

Pythonで画像を比較する際、OpenCVNumPyは非常に強力なツールを提供します。

  • 完全一致の判定:

    • np.array_equal(arr1, arr2): 最もシンプルで推奨される方法です。形状、データ型、すべての画素値の一致を判定します。

  • 差分の取得:

    • cv2.absdiff(img1, img2): 2つの画像間の画素値の絶対差分を効率的に計算します。画像の「変化量」を視覚化するのに最適です。

    • np.abs(arr1.astype(int16) - arr2.astype(int16)).astype(uint8): NumPyのみで絶対差分を計算する際の安全な方法です。

    • cv2.bitwise_xor(img1, img2): 2つの画像のビットごとの違いを強調し、異なる部分を白として二値化します。

これらの手法を使いこなすことで、画像検査、動体検知、画像処理アルゴリズムのデバッグなど、様々な画像比較タスクをPythonで効率的に実現できるでしょう。ぜひ、あなたの画像処理プロジェクトで活用してください!🔍

「らくらくPython塾」が切り開く「呪文コーディング」とは?

■初心者歓迎「AI駆動開発/生成AIエンジニアコース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

格安のプログラミングスクールといえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
対面型でより早くスキル獲得、月額2万円のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<オンライン無料>ゼロから始めるPython爆速講座