识别 Python 中的相似项目以减少/澄清 CV 结果的数量

Identifying similar items in Python to reduce / clarify number of CV results

在 运行 对图像使用两种不同的 CV 算法(以查找细节的多次出现)之后,它们都提供如下列表的结果列表。

第一个算法交付(左、上、宽、高的元组):

[(816, 409, 35, 39), (817, 409, 35, 39), (818, 409, 35, 39), (815, 410, 35, 39), (816, 410, 35, 39), (817, 410, 35, 39), (818, 410, 35, 39), (819, 410, 35, 39), (816, 411, 35, 39), (817, 411, 35, 39), (818, 411, 35, 39), (816, 447, 35, 39), (817, 447, 35, 39), (818, 447, 35, 39), (815, 448, 35, 39), (816, 448, 35, 39), (817, 448, 35, 39), (818, 448, 35, 39), (816, 449, 35, 39), (817, 449, 35, 39), (818, 449, 35, 39), (856, 639, 35, 39), (857, 639, 35, 39), (858, 639, 35, 39), (855, 640, 35, 39), (856, 640, 35, 39), (857, 640, 35, 39), (858, 640, 35, 39), (859, 640, 35, 39), (856, 641, 35, 39), (857, 641, 35, 39), (858, 641, 35, 39)]

第二个(CV2)算法的输出为(左上角坐标):

[(816, 409), (817, 409), (818, 409), (815, 410), (816, 410), (817, 410), (818, 410), (819, 410), (816, 411), (817, 411), (818, 411), (816, 447), (817, 447), (818, 447), (815, 448), (816, 448), (817, 448), (818, 448), (819, 448), (816, 449), (817, 449), (818, 449), (856, 639), (857, 639), (858, 639), (855, 640), (856, 640), (857, 640), (858, 640), (859, 640), (856, 641), (857, 641), (858, 641)]

但是在屏幕上 只出现了三次 搜索项。仔细观察,您会发现 - 例如 - 前两个条目非常相似(左侧位置为 816 而不是 817)。

CV2 代码如下所示:

# detect image in image 
img_rgb = open_cv_image  # original image
template = cv2.imread('C:/temp/detail.png') # searching this!
w, h = template.shape[:-1]

res = cv2.matchTemplate(img_rgb, template, cv2.TM_CCOEFF_NORMED)
threshold = .8
loc = np.where(res >= threshold)
a = []
for pt in zip(*loc[::-1]):  
    cv2.rectangle(img_rgb, pt, (pt[0] + w, pt[1] + h), (0, 0, 255), 2)
    a.append(pt)
#cv2.imwrite('result.png', img_rgb)
print(a)

因此,这两种方法都不会提供准确的结果,而是类似结果的分散列表。我要解决的问题是:

1.如何找出真正找到了多少项(对结果进行分组)?

2。如何将列表减少到每组中的一个项目(哪一个并不重要,因为它们都是相似的)?

有没有一种简单的方法可以将 Python 中的相似元组/列表分组,并将其简化为基本项目?或者 Python 是否有任何简单的 CV 机制可以给出精确匹配?感谢任何帮助...

提前致谢!乌尔里希

你可以得到一个列表,其中每个元组的 euclidean_distance 大于 1(或其他),而前一个元组具有简单的 list comprehension。但是,您需要在开头插入一个零值 tuple。如果 s 是您的输入列表,那么

s.insert(0, (0,0,0,0))
t = [s[x] for x in range(1,len(s)) if euclidean_distance(s[x],s[x-1]) > 1]

>>> t
[(816, 409, 35, 39), (815, 410, 35, 39), (816, 411, 35, 39), (816, 447, 35, 39), (815, 448, 35, 39), (816, 449, 35, 39), (856, 639, 35, 39), (855, 640, 35, 39), (856, 641, 35, 39)]

Non local Maxima Supression(参见: and especially for computer vision: https://www.pyimagesearch.com/2015/02/16/faster-non-maximum-suppression-python/)似乎是解决此问题的既定方法。缺点:占用资源多,速度慢。

我采用了 重叠矩形 的想法,假设是:

  1. 重叠的矩形表示相同的结果
  2. 接触矩形或仅轻微重叠的矩形不代表相同的结果(需要阈值)
  3. 数据仅包含真实表示(不包含错误/数据中的第一项是结果之一)

有了这个,您只需要一些几何公式就可以得到一组正确的结果。这是代码的初稿(有点乱,但是可以用):


data = [(816, 409, 35, 39), (817, 409, 35, 39), (818, 409, 35, 39), 
        (815, 410, 35, 39), (816, 410, 35, 39), (817, 410, 35, 39), 
        (818, 410, 35, 39), (819, 410, 35, 39), (816, 411, 35, 39), 
        (817, 411, 35, 39), (818, 411, 35, 39), (816, 447, 35, 39), 
        (817, 447, 35, 39), (818, 447, 35, 39), (815, 448, 35, 39), 
        (816, 448, 35, 39), (817, 448, 35, 39), (818, 448, 35, 39), 
        (816, 449, 35, 39), (817, 449, 35, 39), (818, 449, 35, 39), 
        (856, 639, 35, 39), (857, 639, 35, 39), (858, 639, 35, 39), 
        (855, 640, 35, 39), (856, 640, 35, 39), (857, 640, 35, 39), 
        (858, 640, 35, 39), (859, 640, 35, 39), (856, 641, 35, 39), 
        (857, 641, 35, 39), (858, 641, 35, 39) ]

def Points(i):
    # converts (X, Y, W, H) to (X1, Y1, X2, Y2) 
    return (i[0],i[1],i[0]+i[2],i[1]+i[3])

def XYWH(i): 
    # converts (X1, Y1, X2, Y2) to (X, Y, W, H)
    return (i[0],i[1],i[2]-i[0],i[3]-i[1])

def Overlap(R1, R2):
    # check if two rectangles overlap
    R1 = Points(R1)
    R2 = Points(R2)
    if (R1[0]>=R2[2]) or (R1[2]<=R2[0]) or \
       (R1[3]<=R2[1]) or (R1[1]>=R2[3]): 
        return False
    return True

def Intersection(a,b):
    # return the intersection area
    a = Points(a)
    b = Points(b)
    x1 = max(min(a[0], a[2]), min(b[0], b[2]))
    y1 = max(min(a[1], a[3]), min(b[1], b[3]))
    x2 = min(max(a[0], a[2]), max(b[0], b[2]))
    y2 = min(max(a[1], a[3]), max(b[1], b[3]))
    if x1 < x2 and y1 < y2:
        return XYWH((x1, y1, x2, y2))

def Area(i):
    # calculate the size of a rectangle
    return i[2]*i[3]

def Covers(a, b):
    # calculates the share (0 to 1) of coverage
    if not Overlap(a, b): return None
    inters = Area(Intersection(a, b))
    original = Area(a)
    return inters/original


def Uniques(Data, Threshold = 0.8):
    ret = [Data[0]]
    for i in range(len(Data)-1):
        c = Covers(data[i],data[i+1])
        if not c or c < Threshold: 
            ret.append(data[i+1])
    return ret 

print(Uniques(data))

您需要对阈值进行一些试验才能获得好的结果。在 Uniques 函数内的 for 循环中包含一个 print(c) 以查看它的传播。上面代码的结果显示了三个正确的发现:

[(816, 409, 35, 39), (816, 447, 35, 39), (856, 639, 35, 39)]

唯一的缺点是,这是每个结果的一种可能解决方案,而不是最佳解决方案,但对于大多数用例来说,这应该足够了。