使用OpenCV去除水平和垂直线条

3

我正在尝试去除这个图像中的水平和垂直线条,以便拥有更加清晰的文本区域。

enter image description here

我正在使用以下代码,该代码遵循这个指南

image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(
                    blurred, 255,
                    cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV,
                    25,
                    15
                )
# Create the images that will use to extract the horizontal and vertical lines
horizontal = np.copy(thresh)
vertical = np.copy(thresh)

# Specify size on horizontal axis
cols = horizontal.shape[1]
horizontal_size = math.ceil(cols / 20)

# Create structure element for extracting horizontal lines through morphology operations
horizontalStructure = cv2.getStructuringElement(cv2.MORPH_RECT, (horizontal_size, 1))

# Apply morphology operations
horizontal = cv2.erode(horizontal, horizontalStructure)
horizontal = cv2.dilate(horizontal, horizontalStructure)

# Show extracted horizontal lines
cv2.imwrite("horizontal.jpg", horizontal)

# Specify size on vertical axis
rows = vertical.shape[0]
verticalsize = math.ceil(rows / 20)

# Create structure element for extracting vertical lines through morphology operations
verticalStructure = cv2.getStructuringElement(cv2.MORPH_RECT, (1, verticalsize))

# Apply morphology operations
vertical = cv2.erode(vertical, verticalStructure)
vertical = cv2.dilate(vertical, verticalStructure)

在此之后,我知道我需要隔离这些线条并用白色线条遮盖原始图像,但是我不太确定该如何继续。

有人有什么建议吗?


您想同时使用垂直和水平线进行掩蔽吗? - Jeru Luke
是的,包括垂直和水平线。 - Dan
5个回答

3

Jeru的回答已经给出了你想要的内容。但是我想添加一个替代方案,可能比你现有的更通用。

您将彩色图像转换为灰度值,然后尝试应用自适应阈值以查找线条。您过滤此以仅获取长水平和垂直线,然后使用该掩码在这些位置上将原始图像涂成白色。

在这里,我们寻找所有线条,并从图像中删除它们,在周围颜色的帮助下对其进行绘制。这个过程根本不涉及阈值处理,所有形态学操作都应用于彩色图像的通道。

理想情况下,我们会使用颜色形态学,但实现很少见。数学形态学基于最大和最小操作,而颜色三元组(即向量)的最大或最小值并没有被明确定义。

因此,我们独立地对每个三色通道应用以下程序。这应该能够产生足够好的结果:

  1. 提取红色通道:从RGB图像输入中提取第一个通道,生成灰度图像,并称其为“channel”。
  2. 应用顶帽滤波器以检测细线条结构: 对“channel”应用小型结构元素(SE)的闭运算结果与“channel”的差异,即“thin=closing(channel)-channel”。此步骤类似于局部阈值处理,但没有实际阈值应用。输出的强度指示了线条相对于背景的深浅程度。如果将“thin”添加到“channel”,则可以填充这些细线条。SE的大小决定了什么是“细”的定义。
  3. 过滤掉短线,只保留长线: 分别对“thin”应用具有水平和垂直方向的长SE的开运算,并取两者结果的最大值,得到“lines”。请注意,这与生成“horizontal”和“vertical”的过程相同。不同之处在于,我们取最大值。这样可以使输出强度仍然匹配“channel”中的对比度。(在数学形态学术语中,最大值开操作是一种开操作)。 SE的长度决定了哪些线条足够长。
  4. 在原始图像通道中填充线条:现在,只需将“lines”添加到“channel”中即可。将结果写入输出图像的第一个通道。
  5. 重复以上步骤处理另外两个通道。

使用DIPlib,这是一个相当简单的脚本:

import diplib as dip

input = dip.ImageReadTIFF('/home/cris/tmp/T4tbM.tif')
output = input.Copy()

for ii in range(0,3):
   channel = output.TensorElement(ii)
   thin = dip.Closing(channel, dip.SE(5, 'rectangular')) - channel
   vertical = dip.Opening(thin, dip.SE([100,1], 'rectangular'))
   horizontal = dip.Opening(thin, dip.SE([1,100], 'rectangular'))
   lines = dip.Supremum(vertical,horizontal)
   channel += lines # overwrites output image

编辑:

当将第一个结构元素的大小增加到5以去除示例图像中较粗的灰色条时,也会导致包含倒置文字“POWERLIFTING”的块的一部分保留在thin中。

为了过滤掉这些部分,我们可以按以下方式更改thin的定义:

notthin = dip.Closing(channel, dip.SE(11, 'rectangular'), ["add max"]))
notthin = dip.MorphologicalReconstruction(notthin, channel, 1, "erosion")
thin = notthin - channel

即,我们不再使用thin=closing(channel)-channel,而是采用thin=reconstruct(closing(channel))-channel。重构只是扩展选择的(非细线)结构,以便在选择了结构的一部分时,现在选择整个结构。现在thin中唯一存在的是未连接到更粗的结构的线条。
我还添加了"add max"作为边界条件——这会导致关闭扩展图像外面的区域,并因此将图像边缘的线条视为线条。 output

我对这个深入的细节印象深刻!有没有一种方法可以使用“pip”命令安装这个包?顺便说一句,赞一个更通用的方法!! - Jeru Luke
谢谢提议。我也在GitHub上检查了这个项目。如果我能提供帮助,我会与您联系。 - Jeru Luke
1
DIPlib可以通过pip install diplib进行安装。 - Cris Luengo

2

以下是详细步骤:

  • 首先,将verticalhorizontal的结果图像相加。这将给您一个包含水平和垂直线的图像。由于两个图像都是uint8类型(无符号8位整数),所以它们相加不会有问题:

res = vertical + horizontal

  • 最后,使用原始3通道图像遮蔽上述获得的结果图像。这可以使用cv2.bitwise_and完成:

fin = cv2.bitwise_and(image, image, mask = cv2.bitwise_not(res))


谢谢,Jeru。我尝试按照你的建议操作,但是如果我尝试保存“fin”,得到的图像是这个 https://ibb.co/kDzm3d - 有什么想法吗?非常感谢。 - Dan
@Dan 抱歉,我的错!请检查编辑,遮罩应该是 cv2.bitwise_not(res) - Jeru Luke
谢谢,我尝试了您的编辑,但结果并不是我所期望的:https://ibb.co/mkOh9J - Dan

2
一种去除水平线的示例。
示例图像: enter image description here
import cv2
import numpy as np

img = cv2.imread("Image path", 0)

if len(img.shape) != 2:
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
else:
    gray = img

gray = cv2.bitwise_not(gray)
bw = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, 
cv2.THRESH_BINARY, 15, -2)

horizontal = np.copy(bw)

cols = horizontal.shape[1]
horizontal_size = cols // 30

horizontalStructure = cv2.getStructuringElement(cv2.MORPH_RECT, (horizontal_size, 1))

horizontal = cv2.erode(horizontal, horizontalStructure)
horizontal = cv2.dilate(horizontal, horizontalStructure)

cv2.imwrite("horizontal_lines_extracted.png", horizontal)

horizontal_inv = cv2.bitwise_not(horizontal)
cv2.imwrite("inverse_extracted.png", horizontal_inv)

masked_img = cv2.bitwise_and(gray, gray, mask=horizontal_inv)
masked_img_inv = cv2.bitwise_not(masked_img)

cv2.imwrite("masked_img.jpg", masked_img_inv)

"最初的回答" 翻译成英文是 "Original Answer"。下面是需要翻译的内容:

=> horizontal_lines_extracted.png:

enter image description here

=> inverse_extracted.png

enter image description here

=> masked_img.png(掩膜后的结果图像)

enter image description here


1
你想要类似这样的东西吗?
image = cv2.imread('image.jpg', cv2.IMREAD_UNCHANGED);

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

ret,binary = cv2.threshold(gray, 170, 255, cv2.THRESH_BINARY)#|cv2.THRESH_OTSU)

V = cv2.Sobel(binary, cv2.CV_8U, dx=1, dy=0)
H = cv2.Sobel(binary, cv2.CV_8U, dx=0, dy=1)

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3,3))
V = cv2.morphologyEx(V, cv2.MORPH_DILATE, kernel, iterations = 2)
H = cv2.morphologyEx(H, cv2.MORPH_DILATE, kernel, iterations = 2)

rows,cols = image.shape[:2]

mask = np.zeros(image.shape[:2], dtype=np.uint8)

contours = cv2.findContours(V, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[1]
for cnt in contours:
    (x,y,w,h) = cv2.boundingRect(cnt)
    # manipulate these values to change accuracy
    if h > rows/2 and w < 10:
        cv2.drawContours(mask, [cnt], -1, 255,-1)

contours = cv2.findContours(H, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[1]
for cnt in contours:
    (x,y,w,h) = cv2.boundingRect(cnt)
    # manipulate these values to change accuracy
    if w > cols/2 and h < 10:
        cv2.drawContours(mask, [cnt], -1, 255,-1)

mask = cv2.morphologyEx(mask, cv2.MORPH_DILATE, kernel, iterations = 2)
image[mask == 255] = (255,255,255)

result


谢谢,这对于这张特定的图片有效。您能否添加一些评论来解释这个过程?我对OpenCV非常新手。此外,您有没有针对具有不同颜色和长度的背景或线条的图像的建议?我尝试在几个不同的图像上运行它,结果相当差。非常感谢您的帮助! - Dan
1
我尝试过的一个示例是这个,只有左侧竖线被识别出来了 http://nikulsinha.com/wp-content/uploads/2011/04/news-article001.jpg 或者是这个,没有任何黑线被覆盖 https://www.teachitenglish.co.uk/attachments/thumbnails/big/x14219.php.pagespeed.ic.LjLPBUNgIK.jpg - Dan

1

所以我通过使用Juke的建议的一部分找到了解决方案。最终,我需要继续使用二进制模式处理图像,因此想保持这种方式。

首先,将verticalhorizontal的结果图像相加。这将给您一个包含水平和垂直线条的图像。由于两个图像都是uint8类型(无符号8位整数),因此将它们相加不会有问题:

res = vertical + horizontal

然后,从用于查找线条的原始输入图像 tresh 中减去 res,这将删除白色线条,并可用于应用其他形态学变换。
fin = thresh - res

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接