初版からアルゴリズムを大幅に変更しました。
初版のアルゴリズムではわずかに色が変化する場合(JPEGで劣化した場合など)に紫色になってしまいましたが、新しいアルゴリズムでは改善しています。また、よりシンプルな計算になりました。
この記事のゴール
画像に追加された部分を青、削除された部分を赤で表示したい。
- 入力画像A
3の差分画像だと入力画像の色の変化(赤→青とか)を扱える、変更箇所が目立つというメリットがあるが、削除された場所と追加された場所の区別がつかない。また、元画像が見えないので人間が解釈するには元画像と照らし合わせないといけない。
4の差分画像は人間に解釈しやすいというメリットがあり、例えば図面の変更箇所の比較などに使える。ただしモノクロ化して処理するため色の変化は扱えない、ごちゃごちゃした画像の微妙な変化は見落とす可能性があるというデメリットがある。
仕組み
入力画像A, Bはモノクロ画像とする。出力画像はCとする。
- 差分B-Aを計算する
- Aに差分を足し引きして着色する
ステップ1: 差分B-Aを計算する
Aの各ピクセルの明るさを$g_1$、Bの各ピクセルの明るさを$g_2$とする。最小値は0、最大値は1とする。
\begin{align}
A &= g_1 \\
B &= g_2 \\
\mathrm{diff} &= B - A = g_2 - g_1
\end{align}
ステップ2: Aに差分を足し引きして着色する
出力画像Cはカラー画像で、要素の値は順にR, G, Bを表すものとする。
Aに比べてBが明るくなっている場所については、Cは赤色に着色しつつ明るくすればよい。
\begin{align}
C = (g_1+\mathrm{diff},\; g_1,\; g_1) \;(\mathrm{if}\;\mathrm{diff} \geq 0)
\end{align}
Aに比べてBが暗くなっている場所については、Cは青色に着色しつつ暗くすればよい。
\begin{align}
C = (g_1+\mathrm{diff},\; g_1+\mathrm{diff},\; g_1) \;(\mathrm{if}\;\mathrm{diff} < 0)
\end{align}
Python&OpenCVによる実装
OpenCVではRGB
ではなくBGR
の順であることに気をつけて実装する。
import cv2
import numpy as np
# uint8配列 -> flaot32配列
def tofloat32(img):
return (img/255.).astype(np.float32)
# float32配列 -> uint8配列
def touint8(img):
return np.clip(img*255, a_min = 0, a_max = 255).astype(np.uint8)
# diff画像作成
def create_img(img1, img2):
if img1.shape != img2.shape:
raise Exception("image shape doesn't match")
img1f = tofloat32(img1)
img2f = tofloat32(img2)
diff = img2f - img1f
# img1 <= img2(明るくなった部分)のマスク
mask_img1_lesser_img2 = cv2.cvtColor(
np.where(diff >= 0, 1, 0).astype(np.float32),
cv2.COLOR_GRAY2BGR
)
# img1 > img2(暗くなった部分)のマスク
mask_img1_greater_img2 = cv2.cvtColor(
np.where(diff < 0, 1, 0).astype(np.float32),
cv2.COLOR_GRAY2BGR
)
# img1 <= img2の着色
result1 = cv2.multiply(
np.dstack((img1f, img1f, img1f+diff)),
mask_img1_lesser_img2
)
# img1 > img2の着色
result2 = cv2.multiply(
np.dstack((img1f, img1f+diff, img1f+diff)),
mask_img1_greater_img2
)
return touint8(result1 + result2)
img1 = cv2.imread("a.png", cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread("b.png", cv2.IMREAD_GRAYSCALE)
diff = create_img(img1, img2)
cv2.imshow("diff", diff)
cv2.waitKey(0)
cv2.destroyAllWindows()