OCR的清洁图像


9

我一直在尝试清除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)

编辑:此外,如果字体更改,则使用常数将不起作用。有通用的方法可以做到这一点吗?


2
这些行中的某些行或其中的一部分具有与合法文本相同的特征,并且在不破坏有效文本的情况下很难摆脱它们。在这种情况下,您可能会关注以下事实:它们比字符长,并且有些孤立。因此,第一步可能是估计字符的大小和紧密度。
伊夫·戴乌斯特

@YvesDaoust如何找到人物的亲密关系?(由于很多时候纯粹是根据大小进行过滤,所以很多时候都与字符混在一起)
K41F4r

1
您可以找到每个斑点到其最近邻居的距离。然后,通过距离的直方图分析,您将在“接近”和“分开”(类似于分布的模式)之间或“周围”和“孤立”之间找到一个阈值。
伊夫·戴乌斯特

如果有多条小线彼此靠近,那么他们最近的邻居不是另一条小线吗?计算到所有其他斑点的平均距离会不会太昂贵?
K41F4r

“他们最亲近的邻居难道不是其他小人物吗?”:很好的反对,阁下。实际上,一堆接近的短片段与合法文本没有区别,尽管排列完全不可能。您可能必须重新组合虚线的片段。我不确定所有人的平均距离是否能救出您。
伊夫·戴乌斯特

Answers:


14

这是个主意。我们将此问题分为几个步骤:

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

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

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

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

  5. 按位-并重建图像。由于我们只有想要的轮廓可以保留在蒙版上,因此可以按位排列并保留文本并得到结果。


这是该过程的可视化:

我们用Otsu的阈值获得二值图像,然后找到轮廓以确定平均矩形轮廓区域。在这里,我们通过填充轮廓来删除以绿色突出显示的较大的离群轮廓

在此处输入图片说明 在此处输入图片说明

接下来,我们构造一个垂直内核并进行扩张以连接角色。此步骤将保留所有所需的文本,并将噪声隔离为单个斑点。

在此处输入图片说明

现在我们找到轮廓并使用轮廓区域进行滤波以消除小噪声

在此处输入图片说明

这是以绿色突出显示的所有已删除的噪声粒子

在此处输入图片说明

结果

在此处输入图片说明

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()

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


1
如果使用更大的字体,这也不会删除文本吗?
K41F4r

是的,可能如此,因此您必须调整阈值区域值。对于更动态的方法,一个想法是确定平均字符区域,然后将其用作阈值
nathancy

似乎对于示例而言过于具体,使用平均面积仍会在很多时间删除文本,这会使OCR的结果更糟
K41F4r

您是否还有其他示例输入图像可以添加到帖子中?
nathancy

1
寻找使用传统图像处理技术在所有情况下均适用的解决方案是非常困难的。您可能需要考虑使用深度学习来训练自己的分类器。祝好运!
nathancy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.