【问题标题】:How to remove horizontal and vertical lines without degrading the image quality in python如何在不降低python图像质量的情况下去除水平线和垂直线
【发布时间】:2020-11-27 15:20:05
【问题描述】:

我正在尝试从图像中删除水平线和垂直线。此图像是使用 pdf2jpg 库从 pdf 生成的。在移除水平线和垂直线后,该图像将被馈送到 pytesseract 以提取单词及其各自的坐标。这里我只是为了测试目的而提取全文。 我是 OpenCV 的新手。我通过累积来自不同网站的代码 sn-ps 编写了这段代码,包括堆栈溢出。除了偶尔有一些垂直线残留外,该代码几乎可以完美运行。这些残余物混淆了正方体,有时被视为 I、1 或 |。此外,对于处理后的图像,tesseract 的误读次数(如 s 被读取为 5,I 被读取为 1 或 | 反之亦然)似乎高于原始图像。我认为原因是字体清晰度低于我们开始使用的原始图像。可以对此代码进行哪些更改,以删除那些垂直线的残余而不影响字体清晰度。任何正确方向的建议或指导将不胜感激。提前致谢

from importlib import invalidate_caches
from pytesseract import image_to_string
#from pdf2image import convert_from_path
from pdf2jpg.pdf2jpg import convert_pdf2jpg
from PIL import Image
import sys
import cv2
import numpy

def pre_process(image):
    if isinstance(image, str):
        image = cv2.imread(image, cv2.IMREAD_GRAYSCALE)
    else:
        # image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        pass
    #Convert the image to true black n white from grayscale
    threshold, image_bin = cv2.threshold(image, 128, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)
    #Invert the image to change white to black and vice versa
    image_inv = 255-image_bin
    
    #Define kernels for horizontal and vertical lines
    kernel_len = numpy.array(image).shape[1]//100
    vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1, kernel_len))
    horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernel_len, 1))
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2, 2))

    #Remove anything that is not a vertical line
    image_inv1 = cv2.erode(image_inv, vertical_kernel, iterations=3)
    vertical_lines = cv2.dilate(image_inv1, vertical_kernel, iterations=3)

    #Remove anything that is not a horizontal line
    image_inv2 = cv2.erode(image_inv, horizontal_kernel, iterations=3)
    horizontal_lines = cv2.dilate(image_inv2, horizontal_kernel, iterations=3)

    #Add horizontal and vertical lines to get all lines
    image_vh = cv2.addWeighted(vertical_lines, 0.5, horizontal_lines, 0.5, 0.0)
    image_vh = cv2.erode(~image_vh, kernel, iterations=2)
    threshold, image_vh = cv2.threshold(image_vh, 128, 255, cv2.THRESH_BINARY|cv2.THRESH_OTSU)

    # Make a inverted copy of original grayscale image
    org_img_inv = cv2.bitwise_not(image)
    #Apply mask of all lines
    final_image_inv = cv2.bitwise_and(org_img_inv, org_img_inv, mask=image_vh)
    #Invert again to get clean image without lines
    image = cv2.bitwise_not(final_image_inv)
    cv2.imshow("final", image)
    cv2.waitKey(0)
    return image

if __name__ =="__main__":
    pdf_path = sys.argv[1]
    images = convert_pdf2jpg(pdf_path, "temp", dpi=100, pages="ALL")
    result = ""
    for image_path in images[0]["output_jpgfiles"]:
        # with Image.open(image_path) as image:
            # text = image_to_string(image)
            # result = "\n".join((result, text))
        image = pre_process(image_path)
        #image = pre_process(image)
        text = image_to_string(image)
        result = "\n".join((result, text))
    # print(result)
    with open("text.txt", "w") as out:
        out.write(result)
        # pre_process(image_path)
        # break

请找到随附的 pdf,我将其用作代码的输入 pdf 和处理图像的片段以供参考。代码可以使用命令提示符触发

python .\read_pdf_ocr.py path_to_pdf_file

环境详情:

  1. Python:3.7.9
  2. 库:
    • opencv-python:4.4.0.46
    • pdf2jpg:1.0
    • pytesseract:0.3.6
  3. Tesseract-OCR - 开源 OCR 引擎:v5.0.0-alpha.20200328

Snip of processed image

Test PDF with table

【问题讨论】:

  • 我赞成你的问题,但请确保代码是minimal reproducible example。另外,请考虑在代码中添加一些 cmets 以帮助其他人。
  • 要去除水平和垂直线条,您的图像需要先旋转,使线条更加水平和垂直。
  • 作为中间解决方案,您可以指明文本所在的区域。这将消除对更多处理的需要。
  • @Suthiro - 非常感谢。这是我在 Stack Overflow 上的第一篇文章。我会在一段时间内添加 cmets。此代码将成为我正在构建的更大代码库的一部分,以从 pdf 创建数据提取框架。我已经在git中上传了代码,并计划在它稳定时将其公开。我已经提供了我认为重新创建场景所需的最少部分。
  • @fmw42- 是的,这是真的。如果您检查随附的 PDF,您会发现它们是完全水平和垂直的,但它们仍然会留下我无法摆脱的微弱线条标记。我尝试使用内核大小但没有运气。水平线似乎消失了,但垂直线仍然存在。可以在处理图像的片段中看到输出图像。对于带有倾斜扫描图像的 PDF,我们将不得不合并这种旋转。感谢您的想法

标签: python python-3.x opencv python-tesseract


【解决方案1】:

您可以使用line-detector 来检测给定图像中的线条。

使用convert_pdf2jpg转换图像后

找到图像的边缘。你可以使用Canny

import cv2
import pytesseract

img = cv2.imread("ex.png")
img_gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_cny = cv2.Canny(img_gry, 50, 200)

Canny-applied-image

部分精明图片:

现在我们可以使用line-detector 来查找图像的坐标。

lns = cv2.ximgproc.createFastLineDetector().detect(img_cny)

img_cpy = img.copy()

for ln in lns:
    x1 = int(ln[0][0])
    y1 = int(ln[0][1])
    x2 = int(ln[0][2])
    y2 = int(ln[0][3])

    cv2.line(img_cpy, pt1=(x1, y1), pt2=(x2, y2),
             color=(0, 255, 0), thickness=5)

    print("Coords: ({}, {})->({}, {})".format(x1, y1, x2, y2))

当我们运行代码时:

输出:

Coords: (8, 6)->(586, 6)

所以表格的宽度是580 pixel。 (586 - 6)

输出:

Coords: (589, 28)->(589, 6)

所以,两条连续线之间的距离接近22 pixel (28 - 6)

一共有 37 行。

我们画出与背景颜色相同的每一行怎么样?

我们知道两条连续线之间的距离,即水平线的起点和终点。

for _ in range(0, 37):
    cv2.line(img, pt1=(6, y1), pt2=(590, y1),
             color=(255, 255, 255), thickness=5)
    print("Coords: ({}, {})->({}, {})".format(6, y1, 590, y1))
    y1 += 20

结果:Horizontal lines removed

示例:

如果你看输出的最后一句话:

Coords: (6, 726)->(590, 726)

所以结束坐标是726。因为我们已经知道结束坐标,所以删除我们需要找到起始坐标的垂直线。

输出:

Coords: (8, 6)->(586, 6)
Coords: (589, 28)->(589, 6)
Coords: (69, 8)->(69, 24)
Coords: (337, 8)->(337, 24)

第一个坐标给出起点,589, 69, 3376

第一条竖线坐标为:(6, 6)->(6, 726) 第二条竖线坐标为:(69, 6)->(69, 726) 第三条竖线坐标为:(337, 6)->(337, 726) 第四条竖线坐标为:(589, 6)->(589, 726)

cv2.line(img, pt1=(6, 6), pt2=(6, 726),
         color=(255, 255, 255), thickness=5)

cv2.line(img, pt1=(72, 6), pt2=(72, 726),
         color=(255, 255, 255), thickness=5)

cv2.line(img, pt1=(337, 6), pt2=(337, 726),
         color=(255, 255, 255), thickness=5)

cv2.line(img, pt1=(589, 6), pt2=(589, 726),
         color=(255, 255, 255), thickness=5)

结果:Vertical-lines-removed

示例:

现在,当您从输出图像中读取文本时:

LINE 1
LINE 2
LINE 3
.
.
.
SOME RANDOM TEXT FOR LINE 1 CELL 1.
SOME RANDOM TEXT FOR LINE 2 CELL 1.
SOME RANDOM TEXT FOR LINE 3 CELL 1.
SOME RANDOM TEXT FOR LINE 4 CELL 1.
.
.
.
1AM OTHER TEXT FOR LINE 1 CELL 2
1AM OTHER TEXT FOR LINE 2 CELL 2
1AM OTHER TEXT FOR LINE 3 CELL 2
1AM OTHER TEXT FOR LINE 4 CELL 2

代码:

import cv2
import pytesseract

img = cv2.imread("ex.png")
img_gry = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_cny = cv2.Canny(img_gry, 50, 200)

# lns = cv2.ximgproc.createFastLineDetector().detect(img_cny)
#
# img_cpy = img.copy()
#
# for ln in lns:
#     x1 = int(ln[0][0])
#     y1 = int(ln[0][1])
#     x2 = int(ln[0][2])
#     y2 = int(ln[0][3])
#
#     cv2.line(img_cpy, pt1=(x1, y1), pt2=(x2, y2),
#              color=(0, 255, 0), thickness=5)
#
#     print("Coords: ({}, {})->({}, {})".format(x1, y1, x2, y2))
#
#     cv2.imshow("img_cpy", img_cpy)
#     cv2.waitKey(0)

y1 = 6

for _ in range(0, 37):
    cv2.line(img, pt1=(6, y1), pt2=(590, y1),
             color=(255, 255, 255), thickness=5)
    print("Coords: ({}, {})->({}, {})".format(6, y1, 590, y1))
    y1 += 20

cv2.line(img, pt1=(6, 6), pt2=(6, 726),
         color=(255, 255, 255), thickness=5)

cv2.line(img, pt1=(72, 6), pt2=(72, 726),
         color=(255, 255, 255), thickness=5)

cv2.line(img, pt1=(337, 6), pt2=(337, 726),
         color=(255, 255, 255), thickness=5)

cv2.line(img, pt1=(589, 6), pt2=(589, 726),
         color=(255, 255, 255), thickness=5)

cv2.imshow("lns", img)
cv2.waitKey(0)

txt = pytesseract.image_to_string(img)
print(txt)

【讨论】:

  • 看起来很完美。非常感谢。我今天将尝试自己实现它。实施后,我会将其标记为答案。如果遇到困难,我可能会带着更多问题回来。请多多包涵。 :)
  • 我好像遗漏了什么。我必须更改这行代码“cv2.ximgproc.createFastLineDetector().detect(img_cny)”的第一件事是我必须卸载 opencv-python 并安装 opencv-contrib-python 4.4.0.46。但是我没有得到类似的结果。水平线被部分识别第一条水平线被识别为“坐标:(442, 98)->(101, 97)”。许多字符也被识别为较小的行。将 pdf 页面中的图像保存为 jpg 或 png 有什么不同吗?
  • 我已经使用 opencv 和 opencv-contrib-4.0.0 实现了代码,但是获得类似的结果并不重要。 “坐标:(442, 98)->(101, 97)”。那么你的表格宽度是341 pixel。现在您需要找到两条连续水平线之间的距离。很正常line-detector 发现很多字符。
  • 明白。我假设 cv2.ximgproc.createFastLineDetector().detect 只检测到大行。但这是不正确的。在分离垂直线和水平线后,我已经应用了这个发现线并使其成为白色逻辑,并且它工作得很好。我将此标记为答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-07-16
  • 2014-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-26
相关资源
最近更新 更多