如何使用不同颜色和字体的文本改进图像的 OCR?

Posted

技术标签:

【中文标题】如何使用不同颜色和字体的文本改进图像的 OCR?【英文标题】:How to Improve OCR on image with text in different colors and fonts? 【发布时间】:2019-01-19 01:50:10 【问题描述】:

我正在使用Google Vision API 从一些图片中提取文本,但是,我一直在尝试提高结果的准确性(置信度),但没有成功。

每次我从原始图像更改图像时,我都会失去检测某些字符的准确性。

我已将问题隔离为不同的单词有多种颜色,例如可以看出红色单词比其他单词更频繁地出现错误结果。

例子:

灰度或黑白图像的一些变化

我可以尝试哪些想法来使这项工作更好,特别是将文本颜色更改为统一颜色或仅在白色背景上显示黑色,因为大多数算法都期望这样做?

我已经尝试过的一些想法,还有一些阈值。

dimg = ImageOps.grayscale(im)
cimg = ImageOps.invert(dimg)

contrast = ImageEnhance.Contrast(dimg)
eimg = contrast.enhance(1)

sharp = ImageEnhance.Sharpness(dimg)
eimg = sharp.enhance(1)

【问题讨论】:

为什么与白色文本相比,红色文本以及部分绿色文本变得模糊? 好问题@WalterTross,这正是我想要弄清楚的。尽管发生这种情况是有道理的,因为玩家名称具有不同的颜色深浅,因此当您将其二值化时,其中一些变为白色,而另一些变为黑色。主要问题是如何找到一个好的阈值,使所有文本都变成黑色 源图质量真的这么差吗? 这么好的问题 - 非常热门! 【参考方案1】:

我只能提供屠夫的解决方案,可能是一场噩梦。

在我自己非常有限的场景中,它就像一个魅力,其他几个 OCR 引擎要么失败,要么运行时间不可接受。

我的先决条件:

我清楚地知道文本将出现在屏幕的哪个区域。 我确切地知道要使用哪些字体和颜色。 文本是半透明的,因此底层图像受到干扰,它是要启动的可变图像。 我无法可靠地检测到平均帧的文本更改并减少干扰。

我做了什么: - 我测量了每个字符的字距宽度。我只需要担心 A-Za-z0-9 和一堆标点符号。 - 程序将从位置 (0,0) 开始,测量平均颜色以确定颜色,然后访问从该颜色的所有可用字体中的字符生成的整个位图集。然后它会判断哪个矩形最接近屏幕上对应的矩形,并前进到下一个。

(几个月后,需要更多性能,我添加了一个可变概率矩阵来首先测试最可能的字符)。

最终,生成的 C 程序能够以 100% 的准确率实时读取视频流中的字幕。

【讨论】:

【参考方案2】:

您几乎尝试了每一个标准步骤。我建议您尝试一些 PIL 内置过滤器,例如锐度过滤器。在 RGB 图像上应用锐度和对比度,然后对其进行二值化。也许使用 Image.split() 和 Image.merge() 分别对每种颜色进行二值化,然后将它们重新组合在一起。 或者将您的图像转换为 YUV,然后仅使用 Y 通道进行进一步处理。 此外,如果您没有单色背景,请考虑执行一些背景减法。

tesseract 在检测扫描文本时喜欢删除帧,因此您可以尝试从图像中破坏尽可能多的非字符空间。 (你可能需要保持图片大小,所以你应该用白色替换它)。 Tesseract 也喜欢直线。因此,如果您的文本是以一定角度录制的,则可能需要进行一些校正。如果将图像大小调整为原始大小的两倍,Tesseract 有时也会提供更好的结果。

我怀疑 Google Vision 使用了 tesseract 或其中的一部分,但我不知道它为您做了哪些其他预处理。所以我在这里的一些建议实际上可能已经实施了,这样做是不必要的和重复的。

【讨论】:

【参考方案3】:

您需要对图像进行多次预处理,并使用bitwise_or 操作来组合结果。要提取颜色,您可以使用

import cv2
boundaries = [      #BGR colorspace for opencv, *not* RGB
    ([15, 15, 100], [50, 60, 200]),    #red
    ([85, 30, 2], [220, 90, 50]),      #blue
    ([25, 145, 190], [65, 175, 250]),  #yellow
]

for (low, high) in boundaries:
    low = np.array(low, dtype = "uint8")
    high = np.array(high, dtype = "uint8")

    # find the colors within the specified boundaries and apply
    # the mask
    mask = cv2.inRange(image, low, high)
    bitWise = cv2.bitwise_and(image, image, mask=mask)
    #now here is the image masked with the specific color boundary...

获得蒙版图像后,您可以对您的“最终”图像执行另一个bitwise_or 操作,本质上是添加此蒙版。

但是这个具体的实现需要opencv,不过同样的原理也适用于其他的镜像包。

【讨论】:

【参考方案4】:

我需要更多关于此的背景信息。

    您要对 Google Vision API 进行多少次调用?如果您在整个直播中都这样做,您可能需要付费订阅。 您打算如何处理这些数据? OCR 需要多准确? 假设您从其他人的 twitch 流中获取此快照,处理流媒体的视频压缩和网络连接,您将获得相当模糊的快照,因此 OCR 将非常困难。

由于视频压缩,图像过于模糊,因此即使对图像进行预处理以提高质量,也可能无法获得足够高的图像质量以实现准确的 OCR。如果您设置了 OCR,您可以尝试一种方法:

    对图像进行二值化以获得白色和黑色背景的非红色文本,就像在二值化图像中一样:

    from PIL import Image
    
    def binarize_image(im, threshold):
    """Binarize an image."""
        image = im.convert('L')  # convert image to monochrome
        bin_im = image.point(lambda p: p > threshold and 255)
        return bin_im
    
    im = Image.open("game_text.JPG")
    binarized = binarize_image(im, 100)
    

    使用过滤器仅提取红色文本值,然后对其进行二值化:

    import cv2
    from matplotlib import pyplot as plt
    
    lower = [15, 15, 100]
    upper = [50, 60, 200]
    
    lower = np.array(lower, dtype = "uint8")
    upper = np.array(upper, dtype = "uint8")
    
    mask = cv2.inRange(im, lower, upper)
    red_binarized = cv2.bitwise_and(im, im, mask = mask)
    
    plt.imshow(cv2.cvtColor(red_binarized, cv2.COLOR_BGR2RGB))
    plt.show()
    

但是,即使进行了这种过滤,它仍然不能很好地提取红色。

    添加在 (1.) 和 (2.) 中获得的图像。

    combined_image = binarized + red_binarized
    

    在 (3.) 上执行 OCR

【讨论】:

【参考方案5】:

这不是一个完整的解决方案,但它可能会带来更好的结果。

通过将数据从 BGR(或 RGB)转换为 CIE-Lab,您可以将灰度图像处理为颜色通道 a* 和 b* 的加权和。 此灰度图像将增强文本的颜色区域。 但是调整阈值,您可以从此灰度图像中分割原始图像中的彩色单词,并从 L 通道阈值中获取其他单词。 按位和运算符应该足以合并到两个分割图像。

如果您可以获得对比度更好的图像,最后一步可能是基于轮廓的填充。 为此,请查看函数“cv2.findContours”的RETR_FLOODFILL。 其他软件包中的任何其他漏洞归档功能也可能适用于此目的。

这是显示我想法的第一部分的代码。

import cv2
import numpy as np 
from matplotlib import pyplot as plt

I = cv2.UMat(cv2.imread('/home/smile/QSKN.png',cv2.IMREAD_ANYCOLOR))

Lab = cv2.cvtColor(I,cv2.COLOR_BGR2Lab)

L,a,b = cv2.split(Lab)

Ig = cv2.addWeighted(cv2.UMat(a),0.5,cv2.UMat(b),0.5,0,dtype=cv2.CV_32F)

Ig = cv2.normalize(Ig,None,0.,255.,cv2.NORM_MINMAX,cv2.CV_8U)


#k = np.ones((3,3),np.float32)
#k[2,2] = 0
#k*=-1
#
#Ig = cv2.filter2D(Ig,cv2.CV_32F,k)
#Ig = cv2.absdiff(Ig,0)
#Ig = cv2.normalize(Ig,None,0.,255.,cv2.NORM_MINMAX,cv2.CV_8U)



_, Ib = cv2.threshold(Ig,0.,255.,cv2.THRESH_OTSU)
_, Lb = cv2.threshold(cv2.UMat(L),0.,255.,cv2.THRESH_OTSU)

_, ax = plt.subplots(2,2)

ax[0,0].imshow(Ig.get(),cmap='gray')
ax[0,1].imshow(L,cmap='gray')
ax[1,0].imshow(Ib.get(),cmap='gray')
ax[1,1].imshow(Lb.get(),cmap='gray')

【讨论】:

【参考方案6】:
import numpy as np
from skimage.morphology import selem
from skimage.filters import rank, threshold_otsu
from skimage.util import img_as_float
from PIL import ImageGrab
import matplotlib.pyplot as plt

def preprocessing(image, strelem, s0=30, s1=30, p0=.3, p1=1.):
    image = rank.mean_bilateral(image, strelem, s0=s0, s1=s1)
    condition = (lambda x: x>threshold_otsu(x))(rank.maximum(image, strelem))
    normalize_image = rank.autolevel_percentile(image, strelem, p0=p0, p1=p1)
    return np.where(condition, normalize_image, 0)

#Grab image from clipboard
image = np.array(ImageGrab.grabclipboard())
sel = selem.disk(4)
a = sum([img_as_float(preprocessing(image[:, :, x], sel, p0=0.3)) for x in range(3)])/3

fig, ax = plt.subplots(1, 2, sharey=True, sharex=True)
ax[0].imshow(image)
ax[1].imshow(rank.autolevel_percentile(a, sel, p0=.4))

这是我的代码,用于从噪音中清除文本并为字符创建均匀的亮度。 稍作修改,我就用它来解决你的问题。

【讨论】:

以上是关于如何使用不同颜色和字体的文本改进图像的 OCR?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Pytesseract 文本识别改进 OCR?

从文本图像生成字体

pygame - 如何用字体和颜色显示文本?

如何从背景和文本颜色相似的轮胎等图像中检测文本?

如何为垂直文本赋予 2 种不同颜色的字体,以便文本 1 和文本 2 在一个“div 类”中?

改进 OCR/图像识别的预处理