检查两个轮廓是否相交?
Check if two contours intersect?
我从 cv2.findContours()
收到了 2 个轮廓(cont1
和 cont2
)。我怎么知道它们是否相交?我不需要坐标,我只需要一个布尔值 True
或 False
.
我尝试了不同的方法并且已经尝试用
进行检查
if ((cont1 & cont2).area() > 0):
...
但是得到了数组没有方法的错误 "Area()"
...
cont1array = cv2.findContours(binary1, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[0]
cont2array = cv2.findContours(binary2, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[0]
...
for cont1 in cont1array:
for cont2 in cont2array:
print("cont1")
print(cont1)
print(type(cont1))
print("cont2")
print(cont2)
print(type(cont2))
> if cont1 and cont2 intersect: #i dont know how check intersect
print("yes they intersect")
else:
print("no they do not intersect")
# cont1
# [[172 302]
# [261 301]
# [262 390]
# [173 391]]
# <class 'numpy.ndarray'>
# cont2
# [[ 0 0]
# [ 0 699]
# [499 699]
# [499 0]]
# <class 'numpy.ndarray'>
从 cv2.findContours()
获得两个轮廓后,您可以使用按位 AND
运算来检测交点。具体来说,我们可以使用np.logical_and()
。这个想法是为每个轮廓创建两个单独的图像,然后对它们使用逻辑 AND
操作。任何具有正值(1
或 True
)的点都将是交点。因此,由于您只想获取是否存在交集的布尔值,我们可以检查相交图像以查看是否存在单个正值。本质上,如果整个数组是 False
那么轮廓之间就没有交点。但是如果只有一个True
,那么等高线就相交了
def contourIntersect(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()
例子
原图
检测到轮廓
我们现在将检测到的两个轮廓传递给函数,得到这个交集数组:
[[False False False ... False False False]
[False False False ... False False False]
[False False False ... False False False]
...
[False False False ... False False False]
[False False False ... False False False]
[False False False ... False False False]]
我们检查 intersection
数组以查看 True
是否存在。我们将在轮廓相交的地方获得 True
或 1
,在不相交的地方获得 False
或 0
。
return intersection.any()
这样我们得到
False
完整代码
import cv2
import numpy as np
def contourIntersect(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 array
return intersection.any()
original_image = cv2.imread("base.png")
image = original_image.copy()
cv2.imshow("original", image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray", gray)
blurred = cv2.GaussianBlur(gray, (5,5), 0)
cv2.imshow("blur", blurred)
threshold = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY)[1]
cv2.imshow("thresh", threshold)
contours = cv2.findContours(threshold.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Depending on OpenCV version, number of arguments return by cv.findContours
# is either 2 or 3
contours = contours[1] if len(contours) == 3 else contours[0]
contour_list = []
for c in contours:
contour_list.append(c)
cv2.drawContours(image, [c], 0, (0,255,0), 2)
print(contourIntersect(original_image, contour_list[0], contour_list[1]))
cv2.imshow("contour", image)
cv2.waitKey(0)
nathancy 的回答有效,但在性能方面受到影响,例如在示例中创建 3 个图像副本来绘制轮廓,因此执行时间缓慢。
我的备选答案如下;
def contour_intersect(cnt_ref,cnt_query, edges_only = True):
intersecting_pts = []
## Loop through all points in the contour
for pt in cnt_query:
x,y = pt[0]
## find point that intersect the ref contour
## edges_only flag check if the intersection to detect is only at the edges of the contour
if edges_only and (cv2.pointPolygonTest(cnt_ref,(x,y),True) == 0):
intersecting_pts.append(pt[0])
elif not(edges_only) and (cv2.pointPolygonTest(cnt_ref,(x,y),True) >= 0):
intersecting_pts.append(pt[0])
if len(intersecting_pts) > 0:
return True
else:
return False
编辑!!
经过测试这段代码,发现当一个轮廓没有两个相似点时,这个检查失败。因此,我重写了检查两条等高线相交的算法。
def ccw(A,B,C):
return (C[1]-A[1]) * (B[0]-A[0]) > (B[1]-A[1]) * (C[0]-A[0])
def contour_intersect(cnt_ref,cnt_query):
## Contour is a list of points
## Connect each point to the following point to get a line
## If any of the lines intersect, then break
for ref_idx in range(len(cnt_ref)-1):
## Create reference line_ref with point AB
A = cnt_ref[ref_idx][0]
B = cnt_ref[ref_idx+1][0]
for query_idx in range(len(cnt_query)-1):
## Create query line_query with point CD
C = cnt_query[query_idx][0]
D = cnt_query[query_idx+1][0]
## Check if line intersect
if ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D):
## If true, break loop earlier
return True
return False
为了处理一个轮廓包含另一个轮廓的情况,我们可以替换
image1 = cv2.drawContours(blank.copy(), contours, 0, 1)
image2 = cv2.drawContours(blank.copy(), contours, 1, 1)
Nathancy 的回答
image1 = cv2.fillPoly(blank.copy(), [contour1], 1)
image2 = cv2.fillPoly(blank.copy(), [contour2], 1)
@Ivans 和@nathancys 的回答是我在这里看到的最好的回答。然而,画线仍然是计算密集型的,特别是当你的轮廓中有很多点时,而直接计算按位和会损害性能,特别是如果你的 canvas 很大。提高性能的一个简单方法是首先检查 bbox 交叉点;如果你看到 bboxes 不相交,你就知道轮廓不相交。如果你的 bboxes 确实相交,只需为两个轮廓绘制最小的填充(或轮廓)ROI 并计算一个简单的按位和。我发现与此处列出的其他技术相比,这可以提供显着的加速,并且可以防止大型 canvas 上的大型复杂轮廓出现问题。我使用 torch 计算 simplicity/legibility.
的 bbox ious
import cv2
import numpy as np
import torchvision.ops.boxes as bops
def contour_intersect(cnt_ref, cnt_query):
## Contours are both an np array of points
## Check for bbox intersection, then check pixel intersection if bboxes intersect
# first check if it is possible that any of the contours intersect
x1, y1, w1, h1 = cv2.boundingRect(cnt_ref)
x2, y2, w2, h2 = cv2.boundingRect(cnt_query)
# get contour areas
area_ref = cv2.contourArea(cnt_ref)
area_query = cv2.contourArea(cnt_query)
# get coordinates as tensors
box1 = torch.tensor([[x1, y1, x1 + w1, y1 + h1]], dtype=torch.float)
box2 = torch.tensor([[x2, y2, x2 + w2, y2 + h2]], dtype=torch.float)
# get bbox iou
iou = bops.box_iou(box1, box2)
if iou == 0:
# bboxes dont intersect, so contours dont either
return False
else:
# bboxes intersect, now check pixels
# get the height, width, x, and y of the smaller contour
if area_ref >= area_query:
h = h2
w = w2
x = x2
y = y2
else:
h = h1
w = w1
x = x1
y = y1
# get a canvas to draw the small contour and subspace of the large contour
contour_canvas_ref = np.zeros((h, w), dtype='uint8')
contour_canvas_query = np.zeros((h, w), dtype='uint8')
# draw the pixels areas, filled (can also be outline)
cv2.drawContours(contour_canvas_ref, [cnt_ref], -1, 255, thickness=cv2.FILLED,
offset=(-x, -y))
cv2.drawContours(contour_canvas_query, [cnt_query], -1, 255, thickness=cv2.FILLED,
offset=(-x, -y))
# check for any pixel overlap
return np.any(np.bitwise_and(contour_canvas_ref, contour_canvas_query))
我从 cv2.findContours()
收到了 2 个轮廓(cont1
和 cont2
)。我怎么知道它们是否相交?我不需要坐标,我只需要一个布尔值 True
或 False
.
我尝试了不同的方法并且已经尝试用
进行检查if ((cont1 & cont2).area() > 0):
... 但是得到了数组没有方法的错误 "Area()"
...
cont1array = cv2.findContours(binary1, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[0]
cont2array = cv2.findContours(binary2, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[0]
...
for cont1 in cont1array:
for cont2 in cont2array:
print("cont1")
print(cont1)
print(type(cont1))
print("cont2")
print(cont2)
print(type(cont2))
> if cont1 and cont2 intersect: #i dont know how check intersect
print("yes they intersect")
else:
print("no they do not intersect")
# cont1
# [[172 302]
# [261 301]
# [262 390]
# [173 391]]
# <class 'numpy.ndarray'>
# cont2
# [[ 0 0]
# [ 0 699]
# [499 699]
# [499 0]]
# <class 'numpy.ndarray'>
从 cv2.findContours()
获得两个轮廓后,您可以使用按位 AND
运算来检测交点。具体来说,我们可以使用np.logical_and()
。这个想法是为每个轮廓创建两个单独的图像,然后对它们使用逻辑 AND
操作。任何具有正值(1
或 True
)的点都将是交点。因此,由于您只想获取是否存在交集的布尔值,我们可以检查相交图像以查看是否存在单个正值。本质上,如果整个数组是 False
那么轮廓之间就没有交点。但是如果只有一个True
,那么等高线就相交了
def contourIntersect(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()
例子
原图
检测到轮廓
我们现在将检测到的两个轮廓传递给函数,得到这个交集数组:
[[False False False ... False False False]
[False False False ... False False False]
[False False False ... False False False]
...
[False False False ... False False False]
[False False False ... False False False]
[False False False ... False False False]]
我们检查 intersection
数组以查看 True
是否存在。我们将在轮廓相交的地方获得 True
或 1
,在不相交的地方获得 False
或 0
。
return intersection.any()
这样我们得到
False
完整代码
import cv2
import numpy as np
def contourIntersect(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 array
return intersection.any()
original_image = cv2.imread("base.png")
image = original_image.copy()
cv2.imshow("original", image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow("gray", gray)
blurred = cv2.GaussianBlur(gray, (5,5), 0)
cv2.imshow("blur", blurred)
threshold = cv2.threshold(blurred, 60, 255, cv2.THRESH_BINARY)[1]
cv2.imshow("thresh", threshold)
contours = cv2.findContours(threshold.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# Depending on OpenCV version, number of arguments return by cv.findContours
# is either 2 or 3
contours = contours[1] if len(contours) == 3 else contours[0]
contour_list = []
for c in contours:
contour_list.append(c)
cv2.drawContours(image, [c], 0, (0,255,0), 2)
print(contourIntersect(original_image, contour_list[0], contour_list[1]))
cv2.imshow("contour", image)
cv2.waitKey(0)
nathancy 的回答有效,但在性能方面受到影响,例如在示例中创建 3 个图像副本来绘制轮廓,因此执行时间缓慢。
我的备选答案如下;
def contour_intersect(cnt_ref,cnt_query, edges_only = True):
intersecting_pts = []
## Loop through all points in the contour
for pt in cnt_query:
x,y = pt[0]
## find point that intersect the ref contour
## edges_only flag check if the intersection to detect is only at the edges of the contour
if edges_only and (cv2.pointPolygonTest(cnt_ref,(x,y),True) == 0):
intersecting_pts.append(pt[0])
elif not(edges_only) and (cv2.pointPolygonTest(cnt_ref,(x,y),True) >= 0):
intersecting_pts.append(pt[0])
if len(intersecting_pts) > 0:
return True
else:
return False
编辑!!
经过测试这段代码,发现当一个轮廓没有两个相似点时,这个检查失败。因此,我重写了检查两条等高线相交的算法。
def ccw(A,B,C):
return (C[1]-A[1]) * (B[0]-A[0]) > (B[1]-A[1]) * (C[0]-A[0])
def contour_intersect(cnt_ref,cnt_query):
## Contour is a list of points
## Connect each point to the following point to get a line
## If any of the lines intersect, then break
for ref_idx in range(len(cnt_ref)-1):
## Create reference line_ref with point AB
A = cnt_ref[ref_idx][0]
B = cnt_ref[ref_idx+1][0]
for query_idx in range(len(cnt_query)-1):
## Create query line_query with point CD
C = cnt_query[query_idx][0]
D = cnt_query[query_idx+1][0]
## Check if line intersect
if ccw(A,C,D) != ccw(B,C,D) and ccw(A,B,C) != ccw(A,B,D):
## If true, break loop earlier
return True
return False
为了处理一个轮廓包含另一个轮廓的情况,我们可以替换
image1 = cv2.drawContours(blank.copy(), contours, 0, 1)
image2 = cv2.drawContours(blank.copy(), contours, 1, 1)
Nathancy 的回答
image1 = cv2.fillPoly(blank.copy(), [contour1], 1)
image2 = cv2.fillPoly(blank.copy(), [contour2], 1)
@Ivans 和@nathancys 的回答是我在这里看到的最好的回答。然而,画线仍然是计算密集型的,特别是当你的轮廓中有很多点时,而直接计算按位和会损害性能,特别是如果你的 canvas 很大。提高性能的一个简单方法是首先检查 bbox 交叉点;如果你看到 bboxes 不相交,你就知道轮廓不相交。如果你的 bboxes 确实相交,只需为两个轮廓绘制最小的填充(或轮廓)ROI 并计算一个简单的按位和。我发现与此处列出的其他技术相比,这可以提供显着的加速,并且可以防止大型 canvas 上的大型复杂轮廓出现问题。我使用 torch 计算 simplicity/legibility.
的 bbox iousimport cv2
import numpy as np
import torchvision.ops.boxes as bops
def contour_intersect(cnt_ref, cnt_query):
## Contours are both an np array of points
## Check for bbox intersection, then check pixel intersection if bboxes intersect
# first check if it is possible that any of the contours intersect
x1, y1, w1, h1 = cv2.boundingRect(cnt_ref)
x2, y2, w2, h2 = cv2.boundingRect(cnt_query)
# get contour areas
area_ref = cv2.contourArea(cnt_ref)
area_query = cv2.contourArea(cnt_query)
# get coordinates as tensors
box1 = torch.tensor([[x1, y1, x1 + w1, y1 + h1]], dtype=torch.float)
box2 = torch.tensor([[x2, y2, x2 + w2, y2 + h2]], dtype=torch.float)
# get bbox iou
iou = bops.box_iou(box1, box2)
if iou == 0:
# bboxes dont intersect, so contours dont either
return False
else:
# bboxes intersect, now check pixels
# get the height, width, x, and y of the smaller contour
if area_ref >= area_query:
h = h2
w = w2
x = x2
y = y2
else:
h = h1
w = w1
x = x1
y = y1
# get a canvas to draw the small contour and subspace of the large contour
contour_canvas_ref = np.zeros((h, w), dtype='uint8')
contour_canvas_query = np.zeros((h, w), dtype='uint8')
# draw the pixels areas, filled (can also be outline)
cv2.drawContours(contour_canvas_ref, [cnt_ref], -1, 255, thickness=cv2.FILLED,
offset=(-x, -y))
cv2.drawContours(contour_canvas_query, [cnt_query], -1, 255, thickness=cv2.FILLED,
offset=(-x, -y))
# check for any pixel overlap
return np.any(np.bitwise_and(contour_canvas_ref, contour_canvas_query))