繁体   English   中英

如何从 opencv python 中的框架 forms 中检测和删除边框?

[英]How to detect and remove borders from framed forms in opencv python?

资料说明

  • 数据集包含forms作为图片(所以数据质量参差不齐)
  • 所有 forms 遵循同一个模板

这是带有边框的表单元素的示例:

图片示例

目标

检测并因此移除(大约)图像周围的矩形边框或框架。

挑战

由于阴影效果等原因,边框的颜色可能不统一,并且可能包含符号或被符号部分中断。 并非所有图像实际上都会首先具有边框(在这种情况下,不需要删除任何东西)。

参考

其他人之前已在此链接上描述过问题,并在 C++ 中提供了答案。 由于我的语言不流利,我需要在 Python 中进行操作。

引用的答案描述了以下步骤(由于我刚刚开始使用计算机视觉,我不确定它们的含义):

  1. 计算图像的拉普拉斯算子
  2. 计算水平和垂直投影
  3. 评估两个方向的变化
  4. 找到最大峰值,在梯度图像的一侧找到。

1 - 您必须对存在的边界做出一些假设 - 假设它们不应超过 20 像素或图像高度/宽度的 10%。 看到您的数据,您将能够做出这个假设

现在我们将从图像中分离出这个 20 像素的边界区域并仅在其中工作。

2 - 将图像转换为灰度,因为您的边框颜色会有所不同。 在灰度上工作将使生活变得轻松。 如果你能做到阈值,那就更好了。

import cv2 
import numpy as np 
img = cv2.imread('input.png', 0) 
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

3 - 由于您的图像边框可能被符号部分中断 - 使用膨胀操作。 如果有统一的边界或没有边界存在 - 什么都不会发生。 如果边界存在且中断 - 扩张操作将使其统一。

将大小为 5 的矩阵作为 kernel

kernel = np.ones((5,5), np.uint8)    
img_dilated = cv2.dilate(gray, kernel, iterations=1)

您将需要尝试

  • kernel 尺寸
  • 迭代次数
  • 膨胀后是否需要腐蚀操作。 腐蚀与膨胀相反

4 - 现在让我们使用拉普拉斯算子找出是否有任何边界。 拉普拉斯算子是图像二阶空间导数的二维各向同性度量。 图像的拉普拉斯算子突出显示强度快速变化的区域,因此通常用于边缘检测。

laplacian = cv2.Laplacian(img_dilated,cv2.CV_64F)

在图像的拉普拉斯算子中,您会看到两条线代替边框。 注意 - 您不需要使用单独的水平和垂直 sobel 运算符。 拉普拉斯算子兼顾水平和垂直。 拉普拉斯算子是二阶导数,而 sobel 是一阶导数。

5 - 现在您希望算法检测是否有双线。 为此,我们使用霍夫变换。

# This returns an array of r and theta values 
lines = cv2.HoughLines(edges,1,np.pi/180, 200) 
  
# The below for loop runs till r and theta values  
# are in the range of the 2d array 
for r,theta in lines[0]: 
      
    # Stores the value of cos(theta) in a 
    a = np.cos(theta) 
  
    # Stores the value of sin(theta) in b 
    b = np.sin(theta) 

6 - 如果霍夫变换检测到线条(检查上述角度 theta 与具有一定公差的预期) - 这意味着您的边界存在。 从图像中删除 20 像素的边框。

注意- 这只是一个让您入门的伪代码。 现实世界的问题需要大量的定制工作和实验。

我设法找到了一种对我有用的方法,尽管如果图像中有其他水平和垂直形状,它就不起作用。

我使用的想法是简单地从边界是水平和垂直形状的假设和 go 的假设开始,假设它们只存在于边界中(这意味着图像本身没有垂直或水平线,这是一个拉伸,但我的用例有这个假设)。

这是我使用的代码:

# extract horizontal and vertical lines
only_box = extract_all_squares(box, kernel_length=7)
# build up a mask of the same size as the image
mask = np.zeros(box.shape, dtype='uint8')
# get contours of horizontal and vetical lines
contours, hierarchy = cv2.findContours(only_box, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# draw contours on mask
mask = cv2.drawContours(mask, contours, -1, (255, 255, 255), thickness=cv2.FILLED)
# threhold mask and image
ret, mask = cv2.threshold(mask, 20, 255, cv2.THRESH_BINARY)
ret, box = cv2.threshold(box, 20, 255, cv2.THRESH_BINARY)
# remove the bits we don't want
box[mask == 0] = 255

使用以下辅助函数

def extract_all_squares(image, kernel_length):
    """
    Binarizes image, keeping only vertical and horizontal lines
    hopefully, it'll help us detect squares
    Args:
        image: image (cropped around circonstances)
        kernel_length: length of kernel to use. Too long and you will catch everything,
            too short and you catch nothing
    Returns:
        image binarized and keeping only vertical and horizozntal lines
    """
    # thresholds image : anything beneath a certain value is set to zero
    (thresh, img_bin) = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)

    # A vertical kernel of (1 X kernel_length), which will detect all the verticle lines from the image.
    vertical_ksize = (1, kernel_length)
    # Morphological operation to detect vertical lines from an image
    verticle_lines_img = extract_lines(img_bin, vertical_ksize)

    # A horizontal kernel of (kernel_length X 1), which will help to detect all the horizontal line from the image.
    horizontal_ksize = (kernel_length, 1)
    # Morphological operation to detect horizontal lines from an image
    horizontal_lines_img = extract_lines(img_bin, horizontal_ksize)
    img_final_bin = add_lines_together(verticle_lines_img, horizontal_lines_img)

    return img_final_bin


def extract_lines(image, ksize):
    """
    extract lines (horizontal or vertical, depending on ksize)
    Args:
        image: binarized image
        ksize: size of kernel to use. Possible values :
            horizontal_ksize = (kernel_length, 1)
            vertical_ksize = (1, kernel_length)
    Returns:
        lines from image (vertical or horizontal, depending on ksize)
    """
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, ksize)
    img_temp = cv2.erode(image, kernel, iterations=3)
    lines_img = cv2.dilate(img_temp, kernel, iterations=3)
    return lines_img


def add_lines_together(verticle_lines_img, horizontal_lines_img, alpha=0.5, beta=0.5):
    """
    extract lines (horizontal or vertical, depending on ksize)
    Args:
        verticle_lines_img: image with vertical lines
        horizontal_lines_img: image with horizontal lines
        alpha : weight of first image. Keep at 0.5 for balance
        beta : weight of second image. Keep at 0.5 for balance
            alpha and beta are weighting parameters, this will
            decide the quantity of an image to be added to make a new image
    Returns:
        image with an addition of both vertical and horizontal lines
    """

    # This function helps to add two image with specific weight parameter to get a third image as summation of two image.
    img_final_bin = cv2.addWeighted(verticle_lines_img, alpha, horizontal_lines_img, beta, 0.0)
    # A kernel of (3 X 3) nes.
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
    # erodes boundaries of features, gets rid of some noise
    img_final_bin = cv2.erode(~img_final_bin, kernel, iterations=2)
    # further kill noise by thresholding
    (thresh, img_final_bin) = cv2.threshold(img_final_bin, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
    return img_final_bin

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM