【问题标题】:find intersection point of two lines drawn using houghlines opencv找到使用houghlines opencv绘制的两条线的交点
【发布时间】:2018-03-15 21:54:40
【问题描述】:

如何使用opencv霍夫线算法得到线的交点?

这是我的代码:

import cv2
import numpy as np
import imutils

im = cv2.imread('../data/test1.jpg')
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 60, 150, apertureSize=3)

img = im.copy()
lines = cv2.HoughLines(edges,1,np.pi/180,200)

for line in lines:
    for rho,theta in line:
        a = np.cos(theta)
        b = np.sin(theta)
        x0 = a*rho
        y0 = b*rho
        x1 = int(x0 + 3000*(-b))
        y1 = int(y0 + 3000*(a))
        x2 = int(x0 - 3000*(-b))
        y2 = int(y0 - 3000*(a))
        cv2.line(img,(x1,y1),(x2,y2),(0,255,0),10)

cv2.imshow('houghlines',imutils.resize(img, height=650))
cv2.waitKey(0)
cv2.destroyAllWindows()

输出:

我想得到所有的交点。

【问题讨论】:

    标签: python opencv numpy hough-transform


    【解决方案1】:

    你不想得到平行线的交点;只有垂直线与水平线的交点。此外,由于您有垂直线,计算斜率可能会导致爆炸或 inf 斜率,因此您不应使用 y = mx+b 方程。你需要做两件事:

    1. 根据角度将线条分成两类。
    2. 计算一个类中的每条线与其他类中的线的交点。

    使用HoughLines,您已经得到rho, theta 的结果,因此您可以使用theta 轻松分割成两类角度。您可以用于例如cv2.kmeans()theta 作为您要拆分的数据。

    然后,要计算交点,您可以使用calculating intersections given two points from each line 的公式。您已经从每行计算两个点:(x1, y1), (x2, y2),因此您可以简单地存储并使用它们。编辑:实际上,如下面我的代码所示,有一个公式可用于计算与rho, theta 形式的线的交点,HoughLines 给出。

    我之前回答过a similar question,提供了一些你可以查看的python代码;注意这是使用HoughLinesP,它只给你线段。


    代码示例

    你没有提供你的原始图片,所以我不能使用它。相反,我将使用 OpenCV 在其 Hough 变换和阈值处理教程中使用的标准数独图像:

    首先,我们将读取此图像并使用自适应阈值对其进行二值化,就像在this OpenCV tutorial 中使用的那样:

    import cv2
    import numpy as np
    
    img = cv2.imread('sudoku.jpg')
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    blur = cv2.medianBlur(gray, 5)
    adapt_type = cv2.ADAPTIVE_THRESH_GAUSSIAN_C
    thresh_type = cv2.THRESH_BINARY_INV
    bin_img = cv2.adaptiveThreshold(blur, 255, adapt_type, thresh_type, 11, 2)
    

    然后我们将找到带有cv2.HoughLines() 的霍夫线:

    rho, theta, thresh = 2, np.pi/180, 400
    lines = cv2.HoughLines(bin_img, rho, theta, thresh)
    

    现在,如果我们想找到交点,实际上我们只想找到垂直线的交点。我们不希望大部分平行线的交点。所以我们需要分割我们的线条。在这个特定的例子中,你可以很容易地根据一个简单的测试来检查线条是水平的还是垂直的;垂直线的theta 大约为 0 或大约 180;水平线的theta 约为 90。但是,如果您想根据任意数量的角度自动分割它们,而无需定义这些角度,我认为最好的办法是使用 cv2.kmeans()

    要做好一件棘手的事情。 HoughLines返回rho, theta形式的线(Hesse normal form),返回的theta在0到180度之间,180度和0度左右的线相似(它们都接近水平线),所以我们需要一些方法来获得kmeans 中的这种周期性。

    如果我们在单位圆上绘制角度,但将角度乘以 2,那么原本在 180 度左右的角度将变为接近 360 度,因此在对于 0 处的角度,单位圆接近相同。因此,我们可以通过在单位圆上绘制 2*angle 来获得一些不错的“接近度”。然后我们可以在这些点上运行cv2.kmeans(),并自动分割出我们想要的任意多的片段。

    所以让我们构建一个函数来进行分割:

    from collections import defaultdict
    def segment_by_angle_kmeans(lines, k=2, **kwargs):
        """Groups lines based on angle with k-means.
    
        Uses k-means on the coordinates of the angle on the unit circle 
        to segment `k` angles inside `lines`.
        """
    
        # Define criteria = (type, max_iter, epsilon)
        default_criteria_type = cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER
        criteria = kwargs.get('criteria', (default_criteria_type, 10, 1.0))
        flags = kwargs.get('flags', cv2.KMEANS_RANDOM_CENTERS)
        attempts = kwargs.get('attempts', 10)
    
        # returns angles in [0, pi] in radians
        angles = np.array([line[0][1] for line in lines])
        # multiply the angles by two and find coordinates of that angle
        pts = np.array([[np.cos(2*angle), np.sin(2*angle)]
                        for angle in angles], dtype=np.float32)
    
        # run kmeans on the coords
        labels, centers = cv2.kmeans(pts, k, None, criteria, attempts, flags)[1:]
        labels = labels.reshape(-1)  # transpose to row vec
    
        # segment lines based on their kmeans label
        segmented = defaultdict(list)
        for i, line in enumerate(lines):
            segmented[labels[i]].append(line)
        segmented = list(segmented.values())
        return segmented
    

    现在要使用它,我们可以简单地调用:

    segmented = segment_by_angle_kmeans(lines)
    

    这里的好处是我们可以通过指定可选参数k(默认为k = 2,所以我没有在这里指定)来指定任意数量的组。

    如果我们用不同颜色绘制每组的线:

    现在剩下的就是找到第一组中每条线的交点与第二组中每条线的交点。由于线是 Hesse 范式,因此有一个很好的线性代数公式可以计算这种形式的线的交点。见here。让我们在这里创建两个函数;一个只找到两条线的交点,一个函数循环遍历组中的所有线并将更简单的函数用于两条线:

    def intersection(line1, line2):
        """Finds the intersection of two lines given in Hesse normal form.
    
        Returns closest integer pixel locations.
        See https://stackoverflow.com/a/383527/5087436
        """
        rho1, theta1 = line1[0]
        rho2, theta2 = line2[0]
        A = np.array([
            [np.cos(theta1), np.sin(theta1)],
            [np.cos(theta2), np.sin(theta2)]
        ])
        b = np.array([[rho1], [rho2]])
        x0, y0 = np.linalg.solve(A, b)
        x0, y0 = int(np.round(x0)), int(np.round(y0))
        return [[x0, y0]]
    
    
    def segmented_intersections(lines):
        """Finds the intersections between groups of lines."""
    
        intersections = []
        for i, group in enumerate(lines[:-1]):
            for next_group in lines[i+1:]:
                for line1 in group:
                    for line2 in next_group:
                        intersections.append(intersection(line1, line2)) 
    
        return intersections
    

    然后使用它,很简单:

    intersections = segmented_intersections(segmented)
    

    绘制所有的交叉点,我们得到:


    如上所述,此代码也可以将线条分割成两组以上的角度。这是在手绘三角形上运行,并计算检测到的线与k=3的交点:

    【讨论】:

    • 请注意,来自 HoughLines 的 OpenCV 2.4x 返回类型似乎与上述基于的 3.2 不同。因此,使用 2.4 需要一点技巧,但确实有效。
    • @orangepips 如果您好心,我希望您能在我的 GH repo 上打开一个实现此代码的问题,并描述版本之间返回的不同形式。我可以更新该工具以及此答案以适应最终与您同舟共济的其他人:github.com/alkasm/houghtool/tree/master/houghtool(另外,您可以在问题中包含您所做的修复,我可以查看:D)
    • 这很漂亮。谢谢
    • 嗨@orangepips 什么是新代码?我正在努力让这个工作
    • @mattsmith5 什么特别不适合你?使用 Python 3.9.1 和 OpenCV 4.5.1,给定的代码按原样运行。您是否注意到,orangepips 指出,所提供的答案使用 OpenCV 3.2(!),并且在 OpenCV 2.4(!)中使用所提供的代码涉及一些“黑客行为”?
    【解决方案2】:

    如果您已经有了线段,只需将它们替换为线方程...

    x = x1 + u * (x2-x1)
    y = y1 + u * (y2-y1)
    

    你可以通过以下任何一种方式找到...

    u = ((x4-x3)*(y1-y3) - (y4-y3)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
    u = ((x2-x1)*(y1-y3) - (y2-y1)*(x1-x3)) / ((y4-y3)*(x2-x1) - (x4-x3)*(y2-y1))
    

    【讨论】:

      【解决方案3】:

      首先,您需要优化霍夫变换的输出(我通常通过基于某些标准的 k-means 聚类来做到这一点,例如斜率和/或线段的质心)。例如,在您的问题中,所有线的斜率似乎通常在 0、180、90 度附近,因此您可以在此基础上进行聚类。

      接下来,有两种不同的方法可以获得相交点(在技术上是相同的):

      1. Bhupen 答案中的方程式。
      2. 使用像ShapelySymPy 这样的几何库。使用几何库执行此操作的好处是,您可以使用稍后在开发中可能需要的各种工具(交集、插值、凸包等)

      附: Shapely 是一个强大的 C++ 几何库的包装器,但 SymPy 是纯 Python。如果您的应用程序时间紧迫,您可能需要考虑这一点。

      【讨论】:

        【解决方案4】:

        这里有一个更直接的解决方案,适配this answer。它应该比 Bhupen 的答案在数值上更稳定

        首先你应该对线进行聚类,这样你就不会像其他答案中提到的那样试图找到平行线的交点(否则,你会得到不一致的结果和/或计算错误)

        然后你可以用这个找到一对线的交点:

        def hough_inter(theta1, rho1, theta2, rho2):
            A = np.array([[cos(theta1), sin(theta1)], 
                          [cos(theta2), sin(theta2)]])
            b = np.array([rho1, rho2])
            return np.linalg.lstsq(A, b)[0] # use lstsq to solve Ax = b, not inv() which is unstable
        

        我的数据结果:

        解释:

        霍夫 (rho/theta) 空间中的线在 x-y 空间中表示如下:

        rho = x cosθ + y sinθ
        

        因此交点 (x, y) 必然解决

        x cos θ1 + y sin θ1 = r1
        x cos θ2 + y sin θ2 = r2
        

        即 AX = b,其中

        A = [cos θ1  sin θ1]   b = |r1|   X = |x|
            [cos θ2  sin θ2]       |r2|       |y|
        

        因此,如果你在 python 中有两条线,你可以像这样找到它们的交集。

        【讨论】:

          【解决方案5】:

          这是使用 OpenCV 2.4 用 python 2.7.x 编写的完整解决方案。

          它在这个线程中使用了来自alkasm 的解决方案,这是不完整的。 HoughLines() 的返回值和 kmeans() 的语法也从 OpenCV 2.x 更改为 3.x

          结果 1:桌子上的一张纸
          https://i.ibb.co/VBSY7V7/paper-on-desk-intersection-points.jpg
          这回答了最初的问题,但是使用 k = 2,3,4 的 k-means 聚类不会分割纸张。您需要一种不同的方法来找到纸的角落
          例如过滤平行线。

          结果 2:数独网格
          https://i.ibb.co/b6thfgr/sudoku-intersection-points.jpg

          代码: https://pastiebin.com/5f36425b7ae3d

          """
          Find the intersection points of lines.
          """
          
          import numpy as np
          import cv2
          from collections import defaultdict
          import sys
          
          
          img = cv2.imread("paper_on_desk.jpg")
          #img = cv2.imread("sudoku.jpg")
          
          
          def segment_by_angle_kmeans(lines, k=2, **kwargs):
              """
              Group lines by their angle using k-means clustering.
          
              Code from here:
              https://stackoverflow.com/a/46572063/1755401
              """
          
              # Define criteria = (type, max_iter, epsilon)
              default_criteria_type = cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER
              criteria = kwargs.get('criteria', (default_criteria_type, 10, 1.0))
          
              flags = kwargs.get('flags', cv2.KMEANS_RANDOM_CENTERS)
              attempts = kwargs.get('attempts', 10)
          
              # Get angles in [0, pi] radians
              angles = np.array([line[0][1] for line in lines])
          
              # Multiply the angles by two and find coordinates of that angle on the Unit Circle
              pts = np.array([[np.cos(2*angle), np.sin(2*angle)] for angle in angles], dtype=np.float32)
          
              # Run k-means
              if sys.version_info[0] == 2:
                  # python 2.x
                  ret, labels, centers = cv2.kmeans(pts, k, criteria, attempts, flags)
              else: 
                  # python 3.x, syntax has changed.
                  labels, centers = cv2.kmeans(pts, k, None, criteria, attempts, flags)[1:]
          
              labels = labels.reshape(-1) # Transpose to row vector
          
              # Segment lines based on their label of 0 or 1
              segmented = defaultdict(list)
              for i, line in zip(range(len(lines)), lines):
                  segmented[labels[i]].append(line)
          
              segmented = list(segmented.values())
              print("Segmented lines into two groups: %d, %d" % (len(segmented[0]), len(segmented[1])))
          
              return segmented
          
          
          def intersection(line1, line2):
              """
              Find the intersection of two lines 
              specified in Hesse normal form.
          
              Returns closest integer pixel locations.
          
              See here:
              https://stackoverflow.com/a/383527/5087436
              """
          
              rho1, theta1 = line1[0]
              rho2, theta2 = line2[0]
              A = np.array([[np.cos(theta1), np.sin(theta1)],
                            [np.cos(theta2), np.sin(theta2)]])
              b = np.array([[rho1], [rho2]])
              x0, y0 = np.linalg.solve(A, b)
              x0, y0 = int(np.round(x0)), int(np.round(y0))
          
              return [[x0, y0]]
          
          
          def segmented_intersections(lines):
              """
              Find the intersection between groups of lines.
              """
          
              intersections = []
              for i, group in enumerate(lines[:-1]):
                  for next_group in lines[i+1:]:
                      for line1 in group:
                          for line2 in next_group:
                              intersections.append(intersection(line1, line2)) 
          
              return intersections
          
          
          def drawLines(img, lines, color=(0,0,255)):
              """
              Draw lines on an image
              """
              for line in lines:
                  for rho,theta in line:
                      a = np.cos(theta)
                      b = np.sin(theta)
                      x0 = a*rho
                      y0 = b*rho
                      x1 = int(x0 + 1000*(-b))
                      y1 = int(y0 + 1000*(a))
                      x2 = int(x0 - 1000*(-b))
                      y2 = int(y0 - 1000*(a))
                      cv2.line(img, (x1,y1), (x2,y2), color, 1)
          
          
          gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
          
          blur = cv2.medianBlur(gray, 5)
          
          # Make binary image
          adapt_type = cv2.ADAPTIVE_THRESH_GAUSSIAN_C
          thresh_type = cv2.THRESH_BINARY_INV
          bin_img = cv2.adaptiveThreshold(blur, 255, adapt_type, thresh_type, 11, 2)
          cv2.imshow("binary", bin_img)
          cv2.waitKey()
          
          # Detect lines
          rho = 2
          theta = np.pi/180
          thresh = 350
          lines = cv2.HoughLines(bin_img, rho, theta, thresh)
          
          if sys.version_info[0] == 2:
              # python 2.x
              # Re-shape from 1xNx2 to Nx1x2
              temp_lines = []
              N = lines.shape[1]
              for i in range(N):
                  rho = lines[0,i,0]
                  theta = lines[0,i,1]
                  temp_lines.append( np.array([[rho,theta]]) )
              lines = temp_lines
          
          print("Found lines: %d" % (len(lines)))
          
          # Draw all Hough lines in red
          img_with_all_lines = np.copy(img)
          drawLines(img_with_all_lines, lines)
          cv2.imshow("Hough lines", img_with_all_lines)
          cv2.waitKey()
          cv2.imwrite("all_lines.jpg", img_with_all_lines)
          
          # Cluster line angles into 2 groups (vertical and horizontal)
          segmented = segment_by_angle_kmeans(lines, 2)
          
          # Find the intersections of each vertical line with each horizontal line
          intersections = segmented_intersections(segmented)
          
          img_with_segmented_lines = np.copy(img)
          
          # Draw vertical lines in green
          vertical_lines = segmented[1]
          img_with_vertical_lines = np.copy(img)
          drawLines(img_with_segmented_lines, vertical_lines, (0,255,0))
          
          # Draw horizontal lines in yellow
          horizontal_lines = segmented[0]
          img_with_horizontal_lines = np.copy(img)
          drawLines(img_with_segmented_lines, horizontal_lines, (0,255,255))
          
          # Draw intersection points in magenta
          for point in intersections:
              pt = (point[0][0], point[0][1])
              length = 5
              cv2.line(img_with_segmented_lines, (pt[0], pt[1]-length), (pt[0], pt[1]+length), (255, 0, 255), 1) # vertical line
              cv2.line(img_with_segmented_lines, (pt[0]-length, pt[1]), (pt[0]+length, pt[1]), (255, 0, 255), 1)
          
          cv2.imshow("Segmented lines", img_with_segmented_lines)
          cv2.waitKey()
          cv2.imwrite("intersection_points.jpg", img_with_segmented_lines)
          

          【讨论】:

            【解决方案6】:

            这里我用一些方法处理了我的图像;

            1.灰度

            2.是按位转换还是边缘检测,这取决于我猜的图像,这里我进行了按位转换。 首先将所有检测到的行放入一个列表中。

            listOflines = cv2.HoughLines(mask_inv,1,np.pi/180,200)
            

            我们将获得 'rho' 和 'theta' 的值, 我在这里做的是创建两个空列表,一个用于垂直线,一个用于水平线,并将两条线的值附加到各自的列表中。

            rowsValue = []
            columnValue = []
            

            这是垂直和水平线的逻辑。

            for line in listOflines:
            if line[0][1] == 0:
                columnValue.append(line[0][0])
            else:
                rowsValue.append(line[0][0])
            

            现在重要的部分在这里, 当每条线穿过并彼此相交时,它就在特定像素值上与该线相交。 我们有以“rho”表示的像素值。

            现在让我们创建元组以作为坐标传递给 'cv2' 函数,即以 (x,y) 的形式。

            tupsList = [(r,c) for r in rowsValue for c in columnValue]
            for tups in tupsList:
                 cv2.circle(image, tups, 1,(0,0,255), 2)
            cv2.imshow('image',image)
            cv2.waitKey(0)
            cv2.destroyAllWindows()
            

            就是这样!! 现在这里是之前和之后的图像。

            Original Image

            Intersection Image

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-03-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多