【问题标题】:Find if two areas intersect given the polygons` edges给定多边形的边缘,查找两个区域是否相交
【发布时间】:2021-07-15 06:21:58
【问题描述】:

这个问题困扰了我一段时间。 我确实找到了一个解决方案,我可以找到每个多边形中的每个点,然后检查交叉点。然而,这在计算上是昂贵的,而且根本不实用。

下图中有四行;两条红线和两条蓝线。我想检查两条红线之间的区域是否与两条蓝线之间的区域相交。

以下变量是已知的:

  1. 每行的起点
  2. 每条线的角度
  3. 线结束的地方(总是在图像的边缘)

我正在考虑使用斜率公式来检查红线的原点相对于每条蓝线的位置。但我不确定这是否是最好的方法。

提前致谢。

【问题讨论】:

  • 您可以使用凸多边形相交算法。这在 O(N) 时间内是可行的。一个更简单的解决方案是通过分离轴定理。 O(N²)。与填充相比,这仍然是相当合理的。

标签: python opencv python-imaging-library computational-geometry intersection


【解决方案1】:

概念

检测图像中是否存在形状相交的一种简单方法,假设每个形状都必须是不同的颜色,您可以为每种颜色定义一个遮罩,并且图像的颜色全部被遮盖,除了带有其颜色的形状,检测为形状轮廓找到的轮廓数量。

如果找到多个轮廓(大于指定数量的区域以滤除噪声),这意味着另一个形状的轮廓与该形状的轮廓相交,在其轮廓中留下间隙,因此导致多个轮廓。

代码

import cv2
import numpy as np

def intersected(img, masks):
    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    for lower, upper in masks:
        mask = cv2.inRange(img_hsv, np.array(lower), np.array(upper))
        blur = cv2.GaussianBlur(mask, (5, 5), 0)
        canny = cv2.Canny(blur, 0, 0)
        contours, _ = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
        count = 0
        for cnt in contours:
            if cv2.contourArea(cnt) > 50:
                cv2.drawContours(img, [cnt], -1, (0, 255, 0), 1)
                cv2.imshow("Test", img)
                count += 1
                if count == 2:
                    return True

img = cv2.imread("shapes.png")

blue_mask = [1, 0, 0], [178, 255, 255]
red_mask = [0, 1, 0], [179, 254, 255]

if intersected(img, (blue_mask, red_mask)):
    print("Intersection detected!")
else:
    print("No intersection detected.")

输出

Intersection detected!

解释

  1. 导入必要的库:
import cv2
import numpy as np
  1. 定义一个接受 2 个参数的函数;我们将检测的图像是否存在形状交叉点,以及每个形状颜色的 HSV 掩码数组:
def intersected(img, masks):
  1. 获取 HSV 形式的图像,并遍历每个 HSV 掩码:
    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    for lower, upper in masks:
        mask = cv2.inRange(img_hsv, np.array(lower), np.array(upper))
  1. 模糊遮罩以去除噪声,使用 canny 边缘检测器检测其边缘,并找到 canny 边缘的轮廓:
        blur = cv2.GaussianBlur(mask, (5, 5), 0)
        canny = cv2.Canny(blur, 0, 0)
        contours, _ = cv2.findContours(canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
  1. 定义一个变量count,以存储面积大于50 的等高线数量,目前已找到。如果count 变量达到2,我们就知道至少找到了一个交叉点,这足以确认图像中有交叉点:
        count = 0
        for cnt in contours:
            if cv2.contourArea(cnt) > 50:
                cv2.drawContours(img, [cnt], -1, (0, 255, 0), 1)
                cv2.imshow("Test", img)
                count += 1
                if count == 2:
                    return True
  1. 最后,我们可以利用图像上的功能:
img = cv2.imread("shapes.png")

blue_mask = [1, 0, 0], [178, 255, 255]
red_mask = [0, 1, 0], [179, 254, 255]

if intersected(img, (blue_mask, red_mask)):
    print("Intersection detected!")
else:
    print("No intersection detected.")

【讨论】:

    【解决方案2】:

    这是一个使用 10x10 像素的小型单通道图像的示例。

    basic_img = np.zeros([10, 10], dtype=np.uint8)
    

    一旦你有了点的坐标,让我们说:

    pts_red = np.array([(9, 0), (9, 6), (4, 2), (4, 0)], dtype=np.int32)
    pts_blue = np.array([(9, 0), (9, 1), (0, 8), (6, 0)], dtype=np.int32)
    

    您可以使用fillPoly 来绘制包含在线条之间的多边形:

    red_poly = basic_img.copy()
    cv2.fillPoly(red_poly, [pts_red], 1)
    # plt.imshow(red_poly)
    

    blue_poly = basic_img.copy()
    cv2.fillPoly(blue_poly, [pts_blue], 1)
    # plt.imshow(blue_poly)
    

    然后用Numpy logical operations得到交集:

    intersection = np.logical_and(red_poly, blue_poly)
    # plt.imshow(intersection)
    

    最后,检查任何 True 值以获得 bool 结果:

    np.any(intersection) #=> True
    

    这里是这个例子的绘制图像。

    蓝色多边形

    红色的多边形

    十字路口

    【讨论】:

      【解决方案3】:

      如果每种颜色有两条线,具有共同的起点,并且在图像边界处有不同的终点,则可以简单地创建一个蒙版,绘制这些线,计算两个终点之间的中点,并使用此中点点作为一些洪水填充的种子点。由于您有一个封闭的多边形,因此该中点保证位于多边形内。从这两个掩码中,确定交集(逻辑与),并检查至少一个像素重叠。

      这是一些使用 OpenCV 和 NumPy 的代码:

      import cv2
      import numpy as np
      
      # Set up image
      w, h = (400, 300)
      img = np.ones((h, w, 3), np.uint8) * 255
      
      # Set up colors
      colors = {
          'Red': (0, 0, 255),
          'Blue': (255, 0, 0)
      }
      
      # Set up lines per color, first element is the point in common
      lines = {
          'Red': [((200, 150), (380, 0)), ((200, 150), (200, 0))],
          'Blue': [((100, 100), (399, 100)), ((100, 100), (300, 0))]
      }
      
      # Set up masks per color
      masks = {
          'Red': np.zeros((h, w), np.uint8),
          'Blue': np.zeros((h, w), np.uint8)
      }
      
      # For each color...
      for c in ['Red', 'Blue']:
          for line in lines[c]:
      
              # ... draw colored line in image, ...
              img = cv2.line(img, line[0], line[1], colors[c], 2)
      
              # ... draw white line in mask, ...
              masks[c] = cv2.line(masks[c], line[0], line[1], 255, 1)
      
          # ... find mid point between both end points, and ...
          mid = tuple(np.int0(np.sum(np.array(lines[c])[:, 1, :], axis=0) / 2))
      
          # ... flood fill mask with the mid point as seed point
          masks[c] = cv2.floodFill(masks[c], None, mid, 255)[1]
      
      # Logical and all masks, and check for at least one pixel overlap
      inter = np.all(np.array(list(masks.values())), axis=0).astype(np.uint8) * 255
      print('Is intersection: ', inter.max() > 0)
      
      # Outputs
      cv2.imshow('Image', img)
      cv2.imshow('Red mask', masks['Red'])
      cv2.imshow('Blue mask', masks['Blue'])
      cv2.imshow('Intersection', inter)
      cv2.waitKey(0)
      cv2.destroyAllWindows()
      

      图片:

      红色面具:

      蓝色面具:

      路口:

      决定:

      Is intersection:  True
      

      代码可以很容易地泛化以添加更多颜色(或多边形)。

      ----------------------------------------
      System information
      ----------------------------------------
      Platform:      Windows-10-10.0.16299-SP0
      Python:        3.9.1
      PyCharm:       2021.1
      NumPy:         1.20.2
      OpenCV:        4.5.1
      ----------------------------------------
      

      【讨论】:

        【解决方案4】:

        这是my answer using OpenCV and NumPy 的仅枕头版本,采用了相同的想法。然而,这个版本仅限于两种颜色。添加更多颜色(或多边形)需要额外的工作(基本上,一些循环)。

        from PIL import Image, ImageChops, ImageDraw
        
        # Set up image
        w, h = (400, 300)
        img = Image.new('RGB', (w, h), (255, 255, 255))
        draw_img = ImageDraw.Draw(img)
        
        # Set up colors
        colors = {
            'Red': (0, 0, 255),
            'Blue': (255, 0, 0)
        }
        
        # Set up lines per color, first element is the point in common
        lines = {
            'Red': [((200, 150), (380, 0)), ((200, 150), (200, 0))],
            'Blue': [((100, 100), (399, 100)), ((100, 100), (300, 0))]
        }
        
        # Set up masks per color
        masks = {
            'Red': Image.new('L', (w, h), 0),
            'Blue': Image.new('L', (w, h), 0)
        }
        
        # For each color...
        for c in ['Red', 'Blue']:
            draw_mask = ImageDraw.Draw(masks[c])
            for line in lines[c]:
        
                # ... draw colored line in image, ...
                draw_img.line(line, colors[c], 2)
        
                # ... draw white line in mask, ...
                draw_mask.line(line, 255, 1)
        
            # ... find mid point between both end points, and ...
            mid = (int(sum([line[1][0] for line in lines[c]]) / len(lines[c])),
                   int(sum([line[1][1] for line in lines[c]]) / len(lines[c])))
        
            # ... flood fill mask with the mid point as seed point
            ImageDraw.floodfill(masks[c], mid, 255)
        
        # Logical and all masks, and check for at least one pixel overlap
        inter = ImageChops.multiply(masks['Red'], masks['Blue'])
        print('Is intersection: ', inter.getextrema()[1] > 0)
        
        # Outputs
        img.show()
        masks['Red'].show()
        masks['Blue'].show()
        inter.show()
        

        输出与 OpenCV 版本相同。

        ----------------------------------------
        System information
        ----------------------------------------
        Platform:      Windows-10-10.0.16299-SP0
        Python:        3.9.1
        PyCharm:       2021.1
        Pillow:        8.2.0
        ----------------------------------------
        

        【讨论】:

          【解决方案5】:

          解决这个问题主要有两种方法:

          1。线性规划

          将问题表达为线性不等式系统并将其解决为线性规划问题,如下所述:Solve a system of linear equations and linear inequalities。在您的情况下,不等式将采用(x - ox[i])*sin(a[i]) - (y - oy[i])*cos(a[i]) > 0(x - ox[i])*sin(a[i]) - (y - oy[i])*cos(a[i]) < 0 的形式,具体取决于您如何定义第i 条线的角度a[i] 以及该线的哪一侧放置多边形。 (ox[i], oy[i]) 是第 i 个顶点的坐标。如果不等式严格与否,这取决于您要如何处理多边形与顶点或边接触的边界情况。这是一个很好的易于推广的方法,但它可能会很慢。

          2。交叉口测试

          在一般情况下(没有顶点和边重合)有4种可能性:(1)一些边相交; (2) 多边形 1 在多边形 2 内; (3) 多边形 2 在多边形 1 内; (4) 多边形不相交。您需要测试前 3 个案例。

          对于案例 1,您需要按照此处How can I check if two segments intersect? 的描述实现线段相交测试,并尝试将多边形 1 的每条边与多边形 2 的每条边相交,这在您的情况下不是问题,因为最多会有2*2 = 4 测试。如果您检测到至少一个交叉路口,您就完成了。

          对于情况 2 和 3,您需要测试多边形 1 的顶点是否在多边形 2 内,反之亦然。这可以使用How can I check if two segments intersect? 中描述的相同测试IsOnLeftIsOnRight 来完成:如果一个点位于右侧线的左侧并且位于左侧线的右侧,则它在内部。

          在任何情况下,您都应该特别注意退化和边界情况:如果多边形的边重合,或者一个多边形的顶点位于另一个多边形的边上,或者不同多边形的边重合,该怎么办。您可以根据您的特定目的以不同的方式检测和处理此类情况。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2012-03-21
            • 2013-07-23
            • 2010-10-19
            • 1970-01-01
            • 2017-07-07
            • 2021-10-25
            • 2012-11-23
            • 2011-04-06
            相关资源
            最近更新 更多