这是一种寻找具有最大表面积的边界框轮廓的潜在解决方案。我们有两个要求:
- 最大边界框不与任何其他框相交
- 最大边界框不在另一个框内
基本上我们可以将这两个要求改写为:
- 给定 C1 和 C2,确定 C1 和 C2 是否相交
- 给定 C1 和 C2,检查 C2 中是否存在来自 C1 的点
为了解决#1,我们可以创建一个contour_intersect 函数,它使用AND 的按位运算和np.logical_and() 来检测交集。这个想法是为每个轮廓创建两个单独的掩码,然后对它们使用逻辑AND 操作。任何具有正值的点(1 或 True)都将是交点。本质上,如果整个数组是False,那么轮廓之间就没有交集。但是如果有一个True,那么轮廓会在某个点接触并因此相交。
对于#2,我们可以创建一个函数contour_inside 并使用cv2.pointPolygonTest() 来确定一个点是在轮廓的内部、外部还是在轮廓的边缘。该函数返回+1、-1 或0 以分别指示一个点是在轮廓内部、外部还是在轮廓上。我们找到 C1 的质心,然后检查该点是否在 C2 内。
这是一个可视化场景的示例:
输入具有三个轮廓的图像。这里没什么特别的,预期的答案是面积最大的轮廓。
答案:
Contour #0 is the largest
接下来我们添加两个额外的轮廓。等高线#3 将代表交叉点场景,等高线#4 将代表内部等高线场景。
答案:
Contour #0 has failed test
Contour #1 has failed test
Contour #2 is the largest
为了解决这个问题,我们找到轮廓,然后使用轮廓面积从大到小排序。接下来,我们将此轮廓与所有其他轮廓进行比较,并检查这两种情况。如果任一情况失败,我们将转储当前轮廓并移动到下一个最大轮廓。通过所有其他轮廓的两个测试的第一个轮廓是我们最大的边界框轮廓。通常,轮廓 #0 将是我们最大的,但它未通过交叉测试。然后我们移动到轮廓#1,但内部测试失败。因此,通过两个测试的最后一个轮廓是轮廓 #2。
import cv2
import numpy as np
# Check if C1 and C2 intersect
def contour_intersect(original_image, contour1, contour2):
# Two separate contours trying to check intersection on
contours = [contour1, contour2]
# Create image filled with zeros the same size of original image
blank = np.zeros(original_image.shape[0:2])
# Copy each contour into its own image and fill it with '1'
image1 = cv2.drawContours(blank.copy(), contours, 0, 1)
image2 = cv2.drawContours(blank.copy(), contours, 1, 1)
# Use the logical AND operation on the two images
# Since the two images had bitwise and applied to it,
# there should be a '1' or 'True' where there was intersection
# and a '0' or 'False' where it didnt intersect
intersection = np.logical_and(image1, image2)
# Check if there was a '1' in the intersection
return intersection.any()
# Check if C1 is in C2
def contour_inside(contour1, contour2):
# Find centroid of C1
M = cv2.moments(contour1)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
inside = cv2.pointPolygonTest(contour2, (cx, cy), False)
if inside == 0 or inside == -1:
return False
elif inside == 1:
return True
# Load image, convert to grayscale, Otsu's threshold
image = cv2.imread('1.png')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Find contours, sort by contour area from largest to smallest
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
sorted_cnts = sorted(cnts, key=lambda x: cv2.contourArea(x), reverse=True)
# "Intersection" and "inside" contours
# Add both contours to test
# --------------------------------
intersect_contour = np.array([[[230, 93]], [[230, 187]], [[326, 187]], [[326, 93]]])
sorted_cnts.append(intersect_contour)
cv2.drawContours(original, [intersect_contour], -1, (36,255,12), 3)
inside_contour = np.array([[[380, 32]], [[380, 229]], [[740, 229]], [[740, 32]]])
sorted_cnts.append(inside_contour)
cv2.drawContours(original, [inside_contour], -1, (36,255,12), 3)
# --------------------------------
# Find centroid for each contour and label contour number
for count, c in enumerate(sorted_cnts):
M = cv2.moments(c)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
cv2.putText(original, str(count), (cx-5, cy+5), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (246,255,12), 3)
# Find largest bounding box contour
largest_contour_name = ""
largest_contour = ""
contours_length = len(sorted_cnts)
for i1 in range(contours_length):
found = True
for i2 in range(i1 + 1, contours_length):
c1 = sorted_cnts[i1]
c2 = sorted_cnts[i2]
# Test intersection and "inside" contour
if contour_intersect(original, c1, c2) or contour_inside(c1, c2):
print('Contour #{} has failed test'.format(i1))
found = False
continue
if found:
largest_contour_name = i1
largest_contour = sorted_cnts[i1]
break
print('Contour #{} is the largest'.format(largest_contour_name))
print(largest_contour)
# Display
cv2.imshow('thresh', thresh)
cv2.imshow('image', image)
cv2.imshow('original', original)
cv2.waitKey()
注意:假设您有一个来自cv2.findContours() 的等高线数组,格式如下例:
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
sorted_cnts = sorted(cnts, key=lambda x: cv2.contourArea(x), reverse=True)
for c in sorted_cnts:
print(c)
print(type(c))
x,y,w,h = cv2.boundingRect(c)
print((x,y,w,h))
输出
[[[230 93]]
[[230 187]]
[[326 187]]
[[326 93]]]
<class 'numpy.ndarray'>
(230, 93, 97, 95)
性能说明:交点检查功能在性能方面受到影响,因为它会创建输入图像的三个副本来绘制轮廓,并且在执行时间较长时可能会更慢轮廓或更大的输入图像尺寸。我把这个优化步骤留给你!