33

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

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

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

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

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

谢谢!帕特里克。

4

6 回答 6

36

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

结果如下:

于 2011-08-29T10:19:13.003 回答
15

这是一个使用形态学运算的完整 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()
于 2020-01-30T01:18:02.123 回答
10

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

于 2012-07-27T12:46:59.363 回答
7

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

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

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

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

于 2011-08-29T18:30:40.080 回答
6

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

于 2013-02-25T15:24:36.483 回答
0

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

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

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)

输出

于 2022-02-01T15:21:13.540 回答