【问题标题】:Masking horizontal and vertical lines with Open CV使用 Opencv 屏蔽水平线和垂直线
【发布时间】:2018-07-17 11:52:01
【问题描述】:

我正在尝试删除此图像中的水平线和垂直线,以便拥有更多不同的文本区域。

我正在使用下面的代码,它遵循guide

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)

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

有人有什么建议吗?

【问题讨论】:

  • 是否要同时使用垂直线和水平线进行遮罩?
  • 是垂直和水平线

标签: python python-3.x opencv computer-vision


【解决方案1】:

Jeru's answer 已经给了你你想要的。但我想添加一个可能比您目前所拥有的更通用的替代方案。

您正在将彩色图像转换为灰度值,然后应用自适应阈值以尝试查找线条。您对其进行过滤以仅获得较长的水平和垂直线,然后使用该蒙版在这些位置将原始图像涂成白色。

在这里,我们查找所有线条,然后将它们从图像中移除,然后用周围的颜色绘制它们。这个过程完全不涉及阈值,所有形态学操作都应用于彩色图像的通道。

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

因此,我们将以下过程分别应用于三个颜色通道中的每一个。这应该会产生对这个应用程序来说足够好的结果:

  1. 提取红色通道:获取input RGB 图像,并提取第一个通道。这是一张灰度图像。我们将此图像称为channel

  2. 应用顶帽过滤器来检测细结构:应用到 channelchannel(a关闭是膨胀,然后是具有相同 SE 的腐蚀,您也可以使用它来查找线)。我们将此输出称为thinthin = closing(channel)-channel。此步骤类似于您的本地阈值,但未应用实际阈值。由此产生的强度表明线条的深浅程度。背景。如果您将thin 添加到channel,您将填充这些薄结构。这里 SE 的大小决定了什么是“瘦”。

  3. 过滤掉短线,只保留长线: 将具有长水平 SE 的开口应用于 thin,将具有长垂直 SE 的开口应用于 @987654334 @,取两个结果中的最大值。我们称之为lines。请注意,这与您用于生成horizontalvertical 的过程相同。我们没有按照 Jeru 的建议将它们加在一起,而是取最大值。这使得输出强度仍然与channel 中的对比度相匹配。 (用数学形态学的说法,开口的上界是一个开口)。这里的 SE 长度决定了什么长度足以成为一条线。

  4. 填写原始图像通道中的行:现在只需将lines 添加到channel。将结果写入输出图像的第一个通道。

  5. 对其他两个通道重复相同的过程。

使用PyDIP 这是一个非常简单的脚本:

import PyDIP 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

编辑:

当增加第一个 SE 的大小时,上面设置为 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=reconstruct(closing(channel))-channel 代替thin=closing(channel)-channel。重建只是扩展选定的(不是薄的)结构,以便选择结构的一部分,现在选择完整的结构。现在thin 中唯一的内容是未连接到较厚结构的线。

我还添加了"add max" 作为边界条件 - 这会导致关闭以白色扩大图像外部的区域,因此将图像边缘的线条视为线条。

【讨论】:

  • 深入的细节给我留下了深刻的印象!有没有办法使用pip 命令安装这个包? BTW +1 用于更通用的方法!
  • @Jeru,不,它还不能(还?)通过pip 安装。我们即将正式发布(这是 DIPlib 迭代的第一个版本,也是 Python 绑定的第一个版本),也许我应该努力弄清楚如何通过 pip 发布。如果你想帮忙,我会张开双臂欢迎你! :)
  • 感谢您的建议。我也检查了 github 上的项目。如果我能提供一些帮助,我会与您联系。
【解决方案2】:

详细说明这里是做什么:

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

res = vertical + horizontal

  • 最后,用原始的 3 通道图像对上面得到的结果图像进行遮罩。这可以使用cv2.bitwise_and 来完成:

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

【讨论】:

  • 谢谢,杰鲁。我尝试听从您的建议,但是如果我尝试保存“fin”,我得到的结果图像是 ibb.co/kDzm3d - 知道为什么吗?非常感谢
  • @Dan 对不起,我的错!检查编辑,它必须是 cv2.bitwise_not(res) 作为掩码。
  • 谢谢,我尝试了您的编辑,但结果不是我所期望的:ibb.co/mkOh9J
【解决方案3】:

删除水平线的示例。

示例图片:

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)

=> 水平线提取.png:

=> inverse_extracted.png

=> masked_img.png(屏蔽后的结果)

【讨论】:

    【解决方案4】:

    你想要这样的东西吗?

    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)
    

    【讨论】:

    【解决方案5】:

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

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

    res = vertical + horizontal
    

    然后,从原始输入图像tresh 中减去res,用于查找线条。这将删除白线,然后可以用于应用其他一些形态学变换。

    fin = thresh - res
    

    【讨论】: