使用 OpenCV Python 检测和可视化两个图像之间的差异

Posted

技术标签:

【中文标题】使用 OpenCV Python 检测和可视化两个图像之间的差异【英文标题】:Detect and visualize differences between two images with OpenCV Python 【发布时间】:2019-10-04 14:18:53 【问题描述】:

我有两张图片,想清楚地说明不同之处。我想为这两个图像添加颜色,以便用户可以在一两秒内清楚地发现所有差异。

例如,这里有两张图片有一些不同:

leftImage.jpg:

rightImage.jpg:

我目前使差异明显的方法是创建一个蒙版(两个图像之间的差异),将其着色为红色,然后将其添加到图像中。目标是用强烈的红色清楚地标记所有差异。这是我当前的代码:

import cv2

# load images
image1 = cv2.imread("leftImage.jpg")
image2 = cv2.imread("rightImage.jpg")

# compute difference
difference = cv2.subtract(image1, image2)

# color the mask red
Conv_hsv_Gray = cv2.cvtColor(difference, cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(Conv_hsv_Gray, 0, 255,cv2.THRESH_BINARY_INV |cv2.THRESH_OTSU)
difference[mask != 255] = [0, 0, 255]

# add the red mask to the images to make the differences obvious
image1[mask != 255] = [0, 0, 255]
image2[mask != 255] = [0, 0, 255]

# store images
cv2.imwrite('diffOverImage1.png', image1)
cv2.imwrite('diffOverImage2.png', image1)
cv2.imwrite('diff.png', difference)

diff.png:

diffOverImage1.png

diffOverImage2.png

当前代码有问题: 计算出的掩码显示了一些差异,但不是全部(参见例如右上角的小块,或蓝色包上的绳索)。这些差异在计算掩码中仅显示非常轻微,但它们应该像其他差异一样清晰地显示为红色。

输入: 2 张有一些差异的图片。

预期输出: 3 张图像:两张输入图像,但突出显示了差异(以可配置的颜色清楚地突出显示),以及仅包含差异的第三张图像(掩码)。

【问题讨论】:

除非您使用无损格式,否则每个图像都会有很多非常细微的差异(导致噪点)。我对此了解不多,所以这只是一个猜测,但也许一些频率滤波器可以帮助消除这些位。 使用 absdiff 而不是减法,你会得到所有的差异,但噪音可能是个问题。有关使用 absdiff 的总体思路,请参阅 ***.com/questions/27035672/… @coffeewin:现有答案中缺少什么?你在找什么? @CrisLuengo 我正在寻找一种方法,它可以识别两个图像之间的差异,其中图像处于不同的角度或略微旋转,并且除了精确的差异外,还可以输出百分比差异。我已经使用 SIFT 和 ORB 研究了特征匹配,但这些方法没有给出定量结果。具体来说,我希望有一些解决方案结合了特征匹配(识别不同角度的差异)和 SSIM(提供图像相似度百分比的能力) @coffeewin:您必须使用这些方法首先对齐两个图像(旋转/扭曲一个以匹配另一个),然后在这个问题中应用差分技术。参见这里:***.com/questions/1819124/image-comparison-algorithm; ***.com/questions/23931/… ; ***.com/questions/1927660/… ; ***.com/questions/47705011/comparing-rotated-images . 【参考方案1】:

为了可视化两幅图像之间的差异,我们可以使用@987654321 中引入的结构相似性指数 (SSIM) 采用定量方法来确定图像之间的确切差异@。此方法已在 scikit-image 库中实现,用于图像处理。您可以使用pip install scikit-image 安装scikit-image

使用 scikit-image 中的 structural_similarity() 函数,它返回一个 score 和一个差异图像 diffscore 表示两个输入图像之间的结构相似性指数,可以落在范围 [-1,1] 之间,值越接近 1 表示相似性越高。但由于您只对这两个图像的不同之处感兴趣,因此我们将重点关注 diff 图像。具体来说,diff 图像包含实际图像差异,较暗区域具有更大的差异。较大的差异区域以黑色突出显示,而较小的差异区域以灰色突出显示。

灰色嘈杂区域可能是由于 .jpg 有损压缩。如果我们使用无损压缩图像格式,我们将获得更清晰的结果。比较两张图片后的 SSIM 分数表明它们非常相似。

图像相似度 0.9198863419190031

现在我们过滤diff 图像,因为我们只想找到图像之间的巨大差异。我们遍历每个轮廓,使用最小阈值区域进行过滤以去除灰色噪声,并使用边界框突出显示差异。结果如下。

为了可视化确切的差异,我们将轮廓填充到蒙版和原始图像上。

from skimage.metrics import structural_similarity
import cv2
import numpy as np

before = cv2.imread('left.jpg')
after = cv2.imread('right.jpg')

# Convert images to grayscale
before_gray = cv2.cvtColor(before, cv2.COLOR_BGR2GRAY)
after_gray = cv2.cvtColor(after, cv2.COLOR_BGR2GRAY)

# Compute SSIM between two images
(score, diff) = structural_similarity(before_gray, after_gray, full=True)
print("Image similarity", score)

# The diff image contains the actual image differences between the two images
# and is represented as a floating point data type in the range [0,1] 
# so we must convert the array to 8-bit unsigned integers in the range
# [0,255] before we can use it with OpenCV
diff = (diff * 255).astype("uint8")

# Threshold the difference image, followed by finding contours to
# obtain the regions of the two input images that differ
thresh = cv2.threshold(diff, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
contours = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = contours[0] if len(contours) == 2 else contours[1]

mask = np.zeros(before.shape, dtype='uint8')
filled_after = after.copy()

for c in contours:
    area = cv2.contourArea(c)
    if area > 40:
        x,y,w,h = cv2.boundingRect(c)
        cv2.rectangle(before, (x, y), (x + w, y + h), (36,255,12), 2)
        cv2.rectangle(after, (x, y), (x + w, y + h), (36,255,12), 2)
        cv2.drawContours(mask, [c], 0, (0,255,0), -1)
        cv2.drawContours(filled_after, [c], 0, (0,255,0), -1)

cv2.imshow('before', before)
cv2.imshow('after', after)
cv2.imshow('diff',diff)
cv2.imshow('mask',mask)
cv2.imshow('filled after',filled_after)
cv2.waitKey(0)

注意: 使用的 scikit-image 版本是 0.18.1。在以前的版本中,该功能为skimage.measure.compare_ssim,但在0.18.1 中已被贬值并删除。根据docs,该功能仍然存在,但现在位于不同名称的新skimage.metrics 子模块下。新更新的函数是skimage.metrics.structural_similarity

【讨论】:

不错的答案。一个查询,我尝试在两张图片之间找到不同的位置,其中一张包含文本(即你好,世界)。两个图像看起来都一样,除了这个文本。为了找到它的位置,我遇到了两种情况:(a)。它使 bbox 围绕 HelloWorld - 3 个 bbox 而不是 2。(b)。如果文本是旋转形状(不是纯文本),则无法正确创建 bbox。 - 有什么收获吗? 对于1)您需要结合文本轮廓。一种简单的方法是将cv2.dilate 与水平内核一起使用。请参阅How to detect paragraphs in a text document image for a non-consistent text structure in Python,尽管您将不得不对其进行一些修改。 对于 2) 参见 draw rotated rect 和 rotated bounding box。我过去回答过更多示例,但我现在没有指向它们的直接链接,您必须查看我过去的答案 在最近版本的 skimage 中,函数 compare_ssim 似乎已被重命名。在 0.18 版中有 skimage.metrics.structural_similarity,但在 skimage.measure 下不再有 compare_ssim。不确定功能是否相同.. see here @dermen 谢谢,已更新为v0.18.1。功能好像是一样的,之前的功能已经贬值,现在叫skimage.metrics.structural_similarity【参考方案2】:

快速识别两个图像之间差异的一个好方法是使用这样的动画 GIF:

描述了该过程并且代码可用here。它可以很容易地适应 Python。照原样,它使用 ImageMagick,它安装在大多数 Linux 发行版上,可用于 macOS 和 Windows。

仅供参考,我在终端中使用了这个命令:

flicker_cmp -o result.gif -r x400 a.jpg b.jpg

【讨论】:

这是我想做的,但闪烁应该在原始图像和蒙版之间。我发现红色闪烁的部分更有效地突出差异。 我觉得 JPG 压缩差异导致左上角蓝色闪烁。那不应该显示。如果在比较图像时可以避免使用 JPG 图像,请不要使用它们。 如果您不想使用 ImageMagick,可以使用 SSIM 来检测两个图像之间的定量差异。这样,您就可以检测轮廓差异并展示蒙版和原始图像之间的闪烁。看我的回答 @Mark,.gif 展示了前/后图像之间的差异,但是您如何使用这种方法获得蒙版? @coffeewin 我会使用 ImageMagick,如 Fred 的回答 (fmw42) 所示【参考方案3】:

如果你愿意使用 Imagemagick,那么你可以使用它的比较工具。由于您的图像是 JPG,因此它们会因每个图像的压缩而显示出差异。因此,我添加了 -fuzz 15% 以允许 15% 的差异容差而不显示这一点。结果将在图像不同的地方显示为红色(默认情况下)。但是颜色是可以改变的。

Linux 带有 Imagemagick。还提供适用于 Mac OSX 和 Windows 的版本。

还有 Python Wand,它使用 Imagemagick。

compare -metric rmse -fuzz 25% left.jpg right.jpg diff.png

另一种方法是使用较低的模糊值并使用形态处理来去除噪声并稍微填充。

使用转换并首先复制左侧图像并将其变白。然后再次复制左侧图像并用红色填充。然后复制左侧图像并使用较低的模糊值 10% 与右侧进行差异操作。这将在图像中留下更多噪声,但可以更好地表示真实区域。所以我使用形态平滑来去除噪音。最后,我使用最后一张图像作为蒙版,在变白的左侧图像上合成红色。

convert left.jpg \
\( -clone 0 -fill white -colorize 50% \) \
\( -clone 0 -fill red -colorize 100 \) \
\( -clone 0 right.jpg -compose difference -composite -threshold 10% -morphology smooth diamond:1 \) \
-delete 0 \
-compose over -composite \
result.png

【讨论】:

实际差异突出显示得更清楚,但现在有很多噪音。小点不应该存在,因为这些不是我们感兴趣的差异。有没有办法消除噪音并使实际差异更加坚实? 增加模糊值以去除更多的 JPG 噪点或使用 PNG 图像而不是 JPG。 JPG 是一种有损压缩,也有伪像。我已编辑我的帖子以使用 -fuzz 25%。 我还编辑了我的答案以包含另一种方法。【参考方案4】:

假设在 image1 中的点 image1[x,y] = [10,10,200]。在不同的矩阵中,不同的[x,y] = [0,0,255]。 “+”计算后,新的值是[10,10,455],因为R值超过255,这将不起作用。

建议你试试

image1[mask != 255] = [0, 0, 255]
image2[mask != 255] = [0, 0, 255]

【讨论】:

很好,现在掩码已正确添加到图像中。问题仍然是我计算的蒙版没有用红色标记所有差异(例如右上角的小东西应该是明显的红色但只有一个小红点) 这是因为物体和表格的颜色非常相似,导致不同矩阵的灰度较低。并且 otsu 功能在这种情况下效果不佳。我认为您应该使用手动更高的阈值(捕获小的不同灰度值),然后按照此链接去除不同的噪声 [链接] (***.com/questions/42272384/…) 我的意思是,为了捕捉微小的差异,您可以手动设置阈值(或使用其他自动阈值功能),阈值低(抱歉误解,我的意思是低,不是高)。例如:threshold = 20。如果不同灰度>20,则改为255,否则改为0。因此,如果差异很小,我们仍然保持它。 如果图像是无损的,这种方法可能会起作用,但由于它们是压缩/有损 .jpg 图像,手动阈值处理不会消除每张图像上不同数量的噪声吗?

以上是关于使用 OpenCV Python 检测和可视化两个图像之间的差异的主要内容,如果未能解决你的问题,请参考以下文章

python使用openCV图像加载(转化为灰度图像)Harris角点检测器算法(Harris Corner Detector)进行角点检测在图像上标记每个角点可视化标记了角点的图像数据

python使用openCV图像加载(转化为灰度图像)Harris角点检测器算法(Harris Corner Detector)进行角点检测在图像上标记每个角点可视化标记了角点的图像数据

python使用openCV图像加载(转化为灰度图像)Shi-Tomasi算法(Shi-Tomasi Corner Detector)进行角点检测在图像上标记每个角点可视化标记了角点的图像数据

在OpenCV Python中检测/提取图像之间的最大差异

opencv&mediapipe 人脸检测+摄像头实时

python中使用opencv检测程序效率