【问题标题】:Horizontal Line detection with OpenCV使用 OpenCV 进行水平线检测
【发布时间】:2011-11-05 19:48:05
【问题描述】:

我正在尝试从来自“文档”的图像中找到水平线和垂直线。这些文档是合同中的扫描页面,因此这些行看起来就像您在表格或合同块中看到的那样。

我一直在尝试使用 OpenCV 来完成这项工作。 OpenCV 中的霍夫变换实现似乎对这项工作很有用,但我找不到任何参数组合可以让它干净地找到垂直和水平线。我尝试了有无边缘检测。没有运气。如果有人做过类似的事情,我有兴趣知道如何做。

在此处查看我在 OpenCV 中使用 HoughP 进行实验前后的图像。这是我能做的最好的了,http://dl.dropbox.com/u/3787481/Untitled%201.png

所以现在我想知道是否可以使用另一种变换来可靠地找到水平线和垂直线(最好也找到虚线)。

我知道这个问题是可以解决的,因为我有 Nuance 和 ABBYY OCR 工具,它们都可以可靠地提取水平和垂直线并将线条的边界框返回给我。

谢谢! 帕特里克。

【问题讨论】:

    标签: image opencv image-processing computer-vision hough-transform


    【解决方案1】:

    您是否看过 HoughLinesP 函数文档中的代码示例?

    我认为您可以将其用作算法的起点。要选择水平线和垂直线,您只需按线角过滤掉其他线。

    更新:

    正如我所见,您需要在页面上找到水平和垂直边缘而不是线条。对于此任务,您需要结合多个处理步骤以获得良好的结果。

    对于您的图像,我可以通过将 Canny 边缘检测与 HoughLinesP 相结合来获得良好的结果。这是我的代码(我用过python,但我想你明白了):

    img = cv2.imread("C:/temp/1.png")
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 80, 120)
    lines = cv2.HoughLinesP(edges, 1, math.pi/2, 2, None, 30, 1);
    for line in lines[0]:
        pt1 = (line[0],line[1])
        pt2 = (line[2],line[3])
        cv2.line(img, pt1, pt2, (0,0,255), 3)
    cv2.imwrite("C:/temp/2.png", img)
    

    结果如下:

    【讨论】:

    • 嗨,安德烈,谢谢。是的,我用许多不同的变量尝试了 HoughLinesP。我已经调整了我最初的问题,并包含了一个指向我可以从 HoughLinesP 中获得的最佳图像的链接。是的,我确实尝试过只限制近水平线。
    • 太棒了。显然我错过了 Canny 探测器。这是一个很好的结果。我还发现了一种称为 Orthogonal Zig-Zag 的算法,它可以获取提供的线条,然后扩展它们以确定线条的宽度(这是 HoughP 无法做到的)。
    【解决方案2】:

    这是一个使用形态学运算的完整 OpenCV 解决方案。

    • 获取二值图像
    • 创建水平内核并检测水平线
    • 创建垂直内核并检测垂直线

    这是该过程的可视化。使用此输入图像:

    二值图像

    import cv2
    
    # Load image, convert to grayscale, Otsu's threshold
    image = cv2.imread('1.png')
    result = image.copy()
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
    

    检测到的以绿色突出显示的水平线

    # Detect horizontal lines
    horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,1))
    detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
    cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    for c in cnts:
        cv2.drawContours(result, [c], -1, (36,255,12), 2)
    

    检测到的以绿色突出显示的垂直线

    # Detect vertical lines
    vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,10))
    detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
    cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    for c in cnts:
        cv2.drawContours(result, [c], -1, (36,255,12), 2)
    

    结果

    这是使用另一个输入图像的输出

    输入->二进制->检测水平->检测垂直->结果


    注意:根据图像,您可能需要修改内核大小。例如,要捕获更长的水平线,可能需要将水平内核从(40, 1) 增加到(80, 1)。如果你想检测更粗的水平线,那么你可以增加内核的宽度,比如(80, 2)。此外,您可以在执行cv2.morphologyEx() 时增加迭代次数。同样,您可以修改垂直内核以检测更多或更少的垂直线。增加或减少内核大小时需要权衡取舍,因为您可能会捕获更多或更少的行。同样,这一切都取决于输入图像

    完整的代码

    import cv2
    
    # Load image, convert to grayscale, Otsu's threshold
    image = cv2.imread('1.png')
    result = image.copy()
    gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
    
    # Detect horizontal lines
    horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40,1))
    detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
    cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    for c in cnts:
        cv2.drawContours(result, [c], -1, (36,255,12), 2)
    
    # Detect vertical lines
    vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,10))
    detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
    cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]
    for c in cnts:
        cv2.drawContours(result, [c], -1, (36,255,12), 2)
    
    cv2.imshow('result', result)
    cv2.waitKey()
    

    【讨论】:

      【解决方案3】:

      如果您只想要“线”而不是“线段”,我会避免使用 Canny、Hough、FindContours 或任何其他此类函数,以防您希望代码速度更快。如果您的图像没有旋转并且您想要找到的始终是垂直或水平的,我将只使用 cv::Sobel(一个用于垂直,另一个用于水平)并为列和行创建累积数组。然后您可以在此类累积或轮廓中搜索最大值,例如通过设置阈值,您将知道存在垂直或水平边缘线的行或列。

      【讨论】:

      • 你知道cv::sobel中横竖线要设置的参数吗?
      【解决方案4】:

      您可能会考虑离开霍夫线检测,因为此方法查找“全局”线,而不一定是线段。我最近实现了一个识别“平行四边形”的应用程序——本质上是可以旋转的正方形,并且由于视角而透视缩短。你可能会考虑类似的事情。我的管道是:

      1. 从 RGB 转换为灰度 (cvCvtColor)
      2. 平滑 (cvSmooth)
      3. 阈值 (cvThreshold)
      4. 检测边缘 (cvCanny)
      5. 查找轮廓 (cvFindContours)
      6. 具有线性特征的近似轮廓 (cvApproxPoly)

      在您的应用程序中,生成的轮廓列表可能会很大(取决于平滑的“积极性”和 Canny 边缘检测器的功能增强。您可以通过各种参数修剪此列表:从返回的点数轮廓查找器,轮廓区域(cvContourArea)等。根据我的经验,我希望您的应用程序中的“有效”线具有明确定义的区域和顶点计数属性。此外,您可以根据距离过滤掉轮廓端点之间,由连接端点的线定义的角度等。

      根据您拥有多少 CPU“时间”,您始终可以将 Hough 算法与上述算法配对,以可靠地识别水平线和垂直线。

      【讨论】:

        【解决方案5】:

        不要将 RGB 转换为灰度。有时,RGB 中的不同颜色可以合并为相同的灰度值,因此可能会丢失一些轮廓。您应该分别分析每个 RGB 通道。

        【讨论】:

          【解决方案6】:

          这是一种为列和行累积数组的方法。然后可以在这些累积中搜索最大值(超过某个阈值)并推断在哪一行或哪一列中存在垂直或水平线。

          如果您想快速测试代码,请使用以下 Google Colab Notebook。
          Google Colab Notebook

          import numpy as np
          import cv2
          import scipy
          from scipy.signal import find_peaks
          from matplotlib import pyplot as plt
          
          url = "https://i.stack.imgur.com/S00ap.png"
          !wget $url -q -O input.jpg
          fileName = 'input.jpg'
          img = cv2.imread(fileName)
          img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
          
          tmp = img.copy()
          gray = cv2.cvtColor(tmp, cv2.COLOR_BGR2GRAY)
          blurred = cv2.bilateralFilter(gray, 11, 61, 39)
          edges = cv2.Canny(blurred, 0, 255)
          
          v_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,3))
          h_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7,1))
          
          v_morphed = cv2.morphologyEx(edges, cv2.MORPH_OPEN, v_kernel, iterations=2)
          v_morphed = cv2.dilate(v_morphed, None)
          h_morphed = cv2.morphologyEx(edges, cv2.MORPH_OPEN, h_kernel, iterations=2)
          h_morphed = cv2.dilate(h_morphed, None)
          
          v_acc = cv2.reduce(v_morphed, 0, cv2.REDUCE_SUM, dtype=cv2.CV_32S)
          h_acc = cv2.reduce(h_morphed, 1, cv2.REDUCE_SUM, dtype=cv2.CV_32S)
          
          def smooth(y, box_pts):
              box = np.ones(box_pts)/box_pts
              y_smooth = np.convolve(y, box, mode='same')
              return y_smooth
          
          s_v_acc = smooth(v_acc[0,:],9) 
          s_h_acc = smooth(h_acc[:,0],9) 
          
          v_peaks, v_props = find_peaks(s_v_acc, 0.70*np.max(np.max(s_v_acc)))
          h_peaks, h_props = find_peaks(s_h_acc, 0.70*np.max(np.max(s_h_acc)))
          
          for peak_index in v_peaks:
              cv2.line(tmp, (peak_index, 0), (peak_index, img.shape[0]), (255, 0, 0),2)
          for peak_index in h_peaks:
              cv2.line(tmp, (0, peak_index), (img.shape[1], peak_index), (0, 0, 255),2)
          v_height = v_props['peak_heights'] #list of the heights of the peaks
          h_height = h_props['peak_heights'] #list of the heights of the peaks
          
          def align_axis_x(ax, ax_target):
              """Make x-axis of `ax` aligned with `ax_target` in figure"""
              posn_old, posn_target = ax.get_position(), ax_target.get_position()
              ax.set_position([posn_target.x0, posn_old.y0, posn_target.width, posn_old.height])
          
          def align_axis_y(ax, ax_target):
              """Make y-axis of `ax` aligned with `ax_target` in figure"""
              posn_old, posn_target = ax.get_position(), ax_target.get_position()
              ax.set_position([posn_old.x0, posn_target.y0, posn_old.width, posn_target.height])
          
          fig = plt.figure(constrained_layout=False, figsize=(24,16))
          spec = fig.add_gridspec(ncols=4, nrows=2, height_ratios=[1, 1])
          ax1 = fig.add_subplot(spec[0,0])
          ax1.imshow(tmp)
          ax2 = fig.add_subplot(spec[0, 1])
          ax2.imshow(v_morphed)
          ax3 = fig.add_subplot(spec[0, 2])
          ax3.imshow(h_morphed)
          ax4 = fig.add_subplot(spec[0, 3], sharey=ax3)
          ax4.plot(h_acc[:,0], np.arange(len(h_acc[:,0])), 'y', marker="o", ms=1, mfc="k", mec="k")
          ax4.plot(s_h_acc, np.arange(len(s_h_acc)), 'r', lw=1)
          ax4.plot(h_height, h_peaks, "x", lw="5")
          ax5 = fig.add_subplot(spec[1, 1], sharex=ax2)
          ax5.plot(np.arange(len(v_acc[0,:])), v_acc[0,:], 'y', marker="o", ms=1, mfc="k", mec="k")
          ax5.plot(np.arange(len(s_v_acc)), s_v_acc, 'r', lw=2)
          ax5.plot(v_peaks, v_height, "x", lw="5")
          plt.tight_layout()
          align_axis_y(ax4,ax3)
          align_axis_x(ax5,ax2)
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2018-07-18
            • 2013-03-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-12-02
            相关资源
            最近更新 更多