如何在OpenCV Python中检测图像之间的最大差异?

4
我正在从事一个射击模拟器项目,需要从图像中检测子弹打孔。我试图区分两幅图像,以便能够检测到新的孔洞,但结果并不如预期。由于相机帧之间的轻微移动,导致前面子弹孔的位置也有细微变化。
第一张图片在这里:
before.png 第二张图片在这里:
after.png 我尝试了以下代码来检查差异:
import cv2 
import numpy as np

before = cv2.imread("before.png") after = cv2.imread("after.png")
result = after - before
cv2.imwrite("result.png", result)

我在 result.png 中得到的结果如下所示:
图片链接:https://istack.dev59.com/ZWsEp.webp 但这不是我期望的,我只想检测新的孔洞,而它显示了与先前图像中一些像素的差异。 我期望的结果是:
图片链接:https://istack.dev59.com/W7Wzb.webp 请帮我解决这个问题,使其仅检测大的差异。
提前感谢。
欢迎提出任何新的想法。
3个回答

5
为了找出两个图像之间的差异,您可以利用在图像质量评估:从误差可见性到结构相似度中介绍的结构相似性指数(SSIM)。该方法已经在用于图像处理的scikit-image库中实现。您可以使用pip install scikit-image安装scikit-image
使用scikit-image中的skimage.metrics.structural_similarity()函数,它返回一个score和一个差异图像diffscore表示两个输入图像之间的结构相似性指数,可以在范围[-1,1]内取值,值越接近1表示相似度越高。但由于您只关心两个图像的差异,因此diff图像是您要寻找的。 diff图像包含两个图像之间的实际图像差异。
接下来,我们使用cv2.findContours()查找所有轮廓,并过滤出最大轮廓。最大轮廓应该代表新检测到的差异,因为轻微差异应该比添加的子弹小。
这是两个图像之间检测到的最大差异。

enter image description here

这里是两个图像之间的实际差异。请注意,尽管所有差异都被捕获了,但由于新子弹很可能是最大的轮廓,我们可以过滤掉相机帧之间的所有其他微小运动。

enter image description here

注意:如果我们假设新的子弹将在diff图像中具有最大的轮廓,则此方法效果非常好。如果最新的孔较小,则可能需要屏蔽现有区域和新图像中的任何新轮廓将是新孔(假设图像将是带有白色孔的均匀黑色背景)。
from skimage.metrics import structural_similarity
import cv2

# Load images
image1 = cv2.imread('1.png')
image2 = cv2.imread('2.png')

# Convert to grayscale
image1_gray = cv2.cvtColor(image1, cv2.COLOR_BGR2GRAY)
image2_gray = cv2.cvtColor(image2, cv2.COLOR_BGR2GRAY)

# Compute SSIM between the two images
(score, diff) = structural_similarity(image1_gray, image2_gray, full=True)

# 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] image1 we can use it with OpenCV
diff = (diff * 255).astype("uint8")
print("Image Similarity: {:.4f}%".format(score * 100))

# 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]

contour_sizes = [(cv2.contourArea(contour), contour) for contour in contours]
result = image2.copy()
# The largest contour should be the new detected difference
if len(contour_sizes) > 0:
    largest_contour = max(contour_sizes, key=lambda x: x[0])[1]
    x,y,w,h = cv2.boundingRect(largest_contour)
    cv2.rectangle(result, (x, y), (x + w, y + h), (36,255,12), 2)

cv2.imshow('result', result)
cv2.imshow('diff', diff)
cv2.waitKey()

这是另一个使用不同输入图像的示例。SSIM在检测图像之间的差异方面表现得相当好。

enter image description here enter image description here

enter image description here enter image description here


你好,这个方法的功能是正确的,但对于视频来说速度太慢了,你能告诉我如何让它更快吗? - WatchMyApps Lab
在尝试提高性能时有许多因素需要考虑。由于您正在处理实时视频,一种方法是使用多线程来减少I/O延迟。另一种方法是减小图像大小。但是,这种SSIM方法对CPU的负荷很重,因此不太适合实时处理。 - nathancy
对我来说,(score, diff) = 这一行代码返回了以下错误:(score, diff) = compare_ssim(before, after, multichannel=True) # full=True) TypeError: cannot unpack non-iterable numpy.float64 object。可能的原因是什么? - Marie. P.
我相信在更新的版本中,他们已经改变了这个函数。我正在使用 opencv-contrib-python==4.2.0.32scikit-image==0.15.0scikit-learn==0.21.3 - nathancy

1
这是我的方法:我们相减后仍有一些噪点,所以我尝试去除它们。我将图像分成大小百分位数的小块,并针对每个小块在之前和之后进行比较,只保留重要的白色像素块。当新拍摄的图像与现有图像重叠时,该算法缺乏精度。
import cv2 
import numpy as np

# This is the percentage of the width/height we're gonna cut
# 0.99 < percent < 0.1
percent = 0.01 

before = cv2.imread("before.png")
after = cv2.imread("after.png")

result =  after - before # Here, we eliminate the biggest differences between before and after

h, w, _ = result.shape

hPercent = percent * h
wPercent = percent * w

def isBlack(crop): # Function that tells if the crop is black
    mask = np.zeros(crop.shape, dtype = int)
    return not (np.bitwise_or(crop, mask)).any()

for wFrom in range(0, w, int(wPercent)): # Here we are gonna remove that noise
    for hFrom in range(0, h, int(hPercent)):
        wTo = int(wFrom+wPercent)
        hTo = int(hFrom+hPercent)
        crop = result[wFrom:wTo,hFrom:hTo] # Crop the image

        if isBlack(crop): # If it is black, there is no shot in it
            continue    # We dont need to continue with the algorithm

        beforeCrop = before[wFrom:wTo,hFrom:hTo] # Crop the image before

        if  not isBlack(beforeCrop): # If the image before is not black, it means there was a hot already there
            result[wFrom:wTo,hFrom:hTo] = [0, 0, 0] # So, we erase it from the result

cv2.imshow("result",result )
cv2.imshow("before", before)
cv2.imshow("after", after)
cv2.waitKey(0)

Before After Result 正如您所看到的,它对您提供的用例起作用。一个很好的下一步是保持射击位置的数组,这样您就可以


0

我的代码:

from skimage.measure import compare_ssim
import argparse
import imutils
import cv2
import numpy as np

# load the two input images
imageA = cv2.imread('./Input_1.png')
cv2.imwrite("./org.jpg", imageA)
# imageA = cv2.medianBlur(imageA,29)
imageB = cv2.imread('./Input_2.png')
cv2.imwrite("./test.jpg", imageB)
# imageB = cv2.medianBlur(imageB,29)

# convert the images to grayscale
grayA = cv2.cvtColor(imageA, cv2.COLOR_BGR2GRAY)
grayB = cv2.cvtColor(imageB, cv2.COLOR_BGR2GRAY)

##########################################################################################################

difference = cv2.subtract(grayA,grayB)    
result = not np.any(difference)
if result is True:
    print ("Pictures are the same")
else:
    cv2.imwrite("./open_cv_subtract.jpg", difference )
    print ("Pictures are different, the difference is stored.")

##########################################################################################################

diff = cv2.absdiff(grayA, grayB)
cv2.imwrite("./tabsdiff.png", diff)

##########################################################################################################

grayB=cv2.resize(grayB,(grayA.shape[1],grayA.shape[0]))
(score, diff) = compare_ssim(grayA, grayB, full=True)
diff = (diff * 255).astype("uint8")
print("SSIM: {}".format(score))

#########################################################################################################

thresh = cv2.threshold(diff, 25, 255,cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)
#s = imutils.grab_contours(cnts)
count = 0
# loop over the contours
for c in cnts:
    # images differ
    count=count+1
    (x, y, w, h) = cv2.boundingRect(c)
    cv2.rectangle(imageA, (x, y), (x + w, y + h), (0, 0, 255), 2)
    cv2.rectangle(imageB, (x, y), (x + w, y + h), (0, 0, 255), 2)

##########################################################################################################

print (count)
cv2.imwrite("./original.jpg", imageA)
# cv2.imshow("Modified", imageB)
cv2.imwrite("./test_image.jpg", imageB)
cv2.imwrite("./compare_ssim.jpg", diff)
cv2.imwrite("./thresh.jpg", thresh)
cv2.waitKey(0)

另一段代码:

import subprocess

# -fuzz 5% # ignore minor difference between two images
# -density 300
# miff:- | display
# -metric phash
# -highlight-color White # by default its RED
# -lowlight-color Black
# -compose difference # src
# -threshold 0
# -separate -evaluate-sequence Add

cmd = 'compare -highlight-color black -fuzz 5% -metric AE Input_1.png ./Input_2.png -compose src ./result.png x: '

a = subprocess.call(cmd, shell=True)

以上代码是使用OpenCV、ImageMagic、NumPy、Scikit-image等进行图像差异比较的各种算法。
希望这对您有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接