使用OpenCV进行水平线检测

时间:2011-08-29 07:01:44

标签: opencv image-processing hough-transform straight-line-detection

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

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

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

所以现在我想知道是否还有其他类型的变换可以让我可靠地找到水平和垂直线(最好是虚线)。

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

谢谢! 帕特里克。

5 个答案:

答案 0 :(得分:29)

您是否看过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)

结果如下:

答案 1 :(得分:9)

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

答案 2 :(得分:6)

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

答案 3 :(得分:5)

您可能会考虑离开Hough线检测,因为此方法会查找“全局”线,而不一定是线段。我最近实施了一个识别“平行四边形”的应用程序 - 基本上可以旋转的正方形和由于视角而缩短的透视图。你可能会考虑类似的东西。我的管道是:

  1. 从RGB转换为灰度(cvCvtColor)
  2. 平滑(cvSmooth)
  3. 阈值(cvThreshold)
  4. 检测边缘(cvCanny)
  5. 查找轮廓(cvFindContours)
  6. 具有线性特征的近似轮廓(cvApproxPoly)
  7. 在您的应用程序中,生成的轮廓列表可能很大(取决于平滑的“侵略性”和Canny边缘检测器的特征增强。您可以通过各种参数修剪此列表:返回的点数轮廓查找器,轮廓区域(cvContourArea)等。根据我的经验,我希望应用程序中的“有效”线条具有明确定义的区域和顶点计数属性。此外,您可以根据距离过滤掉轮廓在终点之间,由连接端点的线定义的角度等

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

答案 4 :(得分:5)

这是使用形态学操作的完整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()