【问题标题】:How to clean images before OCR with Python OpenCV?OCR 的清洁图像
【发布时间】:2020-03-28 06:23:39
【问题描述】:

我一直在尝试为 OCR 清除图像:(线条)

有时我需要删除这些行以进一步处理图像,我已经非常接近了,但很多时候阈值从文本中带走了太多:

    copy = img.copy()
    blur = cv2.GaussianBlur(copy, (9,9), 0)
    thresh = cv2.adaptiveThreshold(blur,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV,11,30)

    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9,9))
    dilate = cv2.dilate(thresh, kernel, iterations=2)

    cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cnts[0] if len(cnts) == 2 else cnts[1]

    for c in cnts:
        area = cv2.contourArea(c)
        if area > 300:
            x,y,w,h = cv2.boundingRect(c)
            cv2.rectangle(copy, (x, y), (x + w, y + h), (36,255,12), 3)

编辑:此外,如果字体更改,使用常量将不起作用。 有没有通用的方法来做到这一点?

【问题讨论】:

  • 其中一些行或其中的片段具有与合法文本相同的特征,并且很难在不破坏有效文本的情况下摆脱它们。如果这适用,您可能会关注它们比字符更长并且有些孤立的事实。所以第一步可能是估计字符的大小和接近度。
  • @YvesDaoust 如何找到字符的接近性? (因为纯粹根据大小进行过滤经常会与字符混淆)
  • 您可以找到每个 blob 到其最近邻居的距离。然后通过对距离的直方图分析,您会发现“接近”和“分开”(类似于分布模式)或“包围”和“孤立”之间的阈值。
  • 如果多条小线彼此靠近,它们最近的邻居不是另一条小线吗?计算到所有其他 blob 的平均距离会不会太昂贵?
  • “他们最近的邻居不会是另一条小线吗?”:好的反对,法官大人。事实上,一堆紧密的短句段与合法文本没有区别,尽管排列方式完全不可能。您可能必须重新组合虚线的片段。我不确定到所有人的平均距离是否能救你。

标签: python opencv image-processing ocr image-segmentation


【解决方案1】:

这是一个想法。我们将这个问题分解为几个步骤:

  1. 确定平均矩形轮廓区域。我们阈值然后找到轮廓并使用轮廓的边界矩形区域进行过滤。我们这样做的原因是因为观察到任何典型字符都只会很大,而大噪声将跨越更大的矩形区域。然后我们确定平均面积。

  2. 删除大的异常轮廓。我们再次遍历轮廓,如果大轮廓5x 大于平均轮廓区域,则通过填充轮廓来删除它们。我们没有使用固定阈值区域,而是使用此动态阈值来提高鲁棒性。

  3. 使用垂直内核扩张以连接字符。这个想法是利用字符在列中对齐的观察结果。通过使用垂直内核进行膨胀,我们将文本连接在一起,因此该组合轮廓中不会包含噪声。

  4. 去除小噪音。现在要保留的文本已连接,我们找到轮廓并删除任何小于4x 平均轮廓区域的轮廓。

  5. 按位与重构图像。因为我们只有想要的轮廓来保留我们的蒙版,所以我们按位 - 并保留文本并获得我们的结果。


这是该过程的可视化:

我们Otsu's threshold 获得二值图像然后find contours 确定平均矩形轮廓区域。从这里我们删除filling contours 以绿色突出显示的大异常值轮廓

接下来我们构造一个vertical kerneldilate 来连接字符。此步骤连接所有需要保留的文本,并将噪声隔离为单个 blob。

现在我们找到轮廓并使用contour area 过滤以去除小噪声

这里是所有移除的噪声粒子,以绿色突出显示

结果

代码

import cv2

# Load image, grayscale, and Otsu's threshold
image = cv2.imread('1.png')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Determine average contour area
average_area = [] 
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = w * h
    average_area.append(area)

average = sum(average_area) / len(average_area)

# Remove large lines if contour area is 5x bigger then average contour area
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    x,y,w,h = cv2.boundingRect(c)
    area = w * h
    if area > average * 5:  
        cv2.drawContours(thresh, [c], -1, (0,0,0), -1)

# Dilate with vertical kernel to connect characters
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (2,5))
dilate = cv2.dilate(thresh, kernel, iterations=3)

# Remove small noise if contour area is smaller than 4x average
cnts = cv2.findContours(dilate, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    area = cv2.contourArea(c)
    if area < average * 4:
        cv2.drawContours(dilate, [c], -1, (0,0,0), -1)

# Bitwise mask with input image
result = cv2.bitwise_and(image, image, mask=dilate)
result[dilate==0] = (255,255,255)

cv2.imshow('result', result)
cv2.imshow('dilate', dilate)
cv2.imshow('thresh', thresh)
cv2.waitKey()

注意:传统的图像处理仅限于阈值处理、形态学操作和轮廓过滤(轮廓近似、面积、纵横比或斑点检测)。由于输入图像可能因字符文本大小而异,因此很难找到一个单一的解决方案。您可能希望考虑使用机器/深度学习训练您自己的分类器以获得动态解决方案。

【讨论】:

  • 如果字体更大,这不是也删除文本吗?
  • 是的,它可以,因此您必须调整阈值区域值。对于更动态的方法,一个想法是确定平均字符区域,然后将其用作阈值
  • 似乎对示例过于具体,使用平均区域仍然会在很多时候删除文本,这会恶化 OCR 的结果
  • 您还有其他可以添加到帖子中的示例输入图像吗?
  • 使用传统图像处理技术找到适用于所有情况的解决方案非常困难。您可能想研究使用深度学习训练自己的分类器。祝你好运!
猜你喜欢
  • 2020-11-15
  • 2013-11-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-26
  • 2018-01-10
  • 2013-11-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多