如何提高对不同颜色和字体的文本图像的OCR识别能力?

12

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

每次我改变原始图像后,检测一些字符的准确性就会下降。

我已经确定问题出在了不同单词的颜色上,例如红色的单词比其他单词更容易出现错误结果。

例如:

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

Original Image

enter image description here

enter image description here

enter image description here

enter image description here

有哪些想法可以尝试以获得更好的效果,特别是将文本颜色更改为统一的颜色或仅使用黑色在白色背景上,因为大多数算法都期望如此?

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

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

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

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

为什么与白色文本相比,红色文本和部分绿色文本模糊不清? - Walter Tross
很好的问题@WalterTross,这正是我试图弄清楚的。当你将玩家名称二值化时,它有不同的颜色阴影,所以一些变成了白色,一些变成了黑色,这种情况是有道理的。而主要的问题是如何找到一个好的阈值,使所有文本都变成黑色。 - RaedMarji
源图像的质量真的这么差吗? - Blender
这是一个非常好的问题 - 非常时髦! - jtlz2
6个回答

1
我只能提供一个屠夫的解决方案,可能难以维护。
在我的非常有限的场景中,它像魔法一样运行良好,而其他几个OCR引擎要么失败,要么运行时间不可接受。
我的先决条件: - 我确切地知道文本将要放置在屏幕的哪个区域。 - 我确切地知道将要使用哪些字体和颜色。 - 文本是半透明的,所以底层图像会干扰,而且它是一个可变的图像。 - 我无法可靠地检测到文本更改以平均帧数并减少干扰。
我的做法: - 我测量了每个字符的字距宽度。我只需要关注A-Za-z0-9和一堆标点符号字符。 - 程序将从位置(0,0)开始,测量平均颜色以确定颜色,然后访问所有可用字体中生成的字符位图集。然后它将确定哪个矩形最接近屏幕上对应的矩形,并前进到下一个矩形。
(几个月后,需要更多性能,我添加了一个变化的概率矩阵来首先测试最可能的字符)。
最终,生成的C程序能够实时以100%的准确率读取视频流中的字幕。

1
你已经尝试了几乎所有标准步骤。我建议您尝试一些 PIL 内置的滤镜,例如锐度滤镜。在 RGB 图像上应用锐度和对比度,然后将其二值化。也许使用 Image.split() 和 Image.merge() 分别对每种颜色进行二值化,然后将它们重新组合在一起。 或者将图像转换为 YUV,然后仅使用 Y 通道进行进一步处理。 此外,如果您没有单色背景,请考虑执行一些背景减法。
当检测扫描文本时,tesseract 喜欢去除框架,因此您可以尝试从图像中消除尽可能多的非字符空间。(您可能需要保留图片大小,因此应该用白色替换它)。Tesseract 还喜欢直线。因此,如果您的文本呈角度记录,则可能需要进行一些校正。如果将图像的大小调整为原始大小的两倍,则 Tesseract 有时会给出更好的结果。
我怀疑 Google Vision 使用 tesseract 或其中的部分,但是它为您做了哪些其他预处理我不知道。因此,这里的一些建议可能已经实现,并且执行它们将是不必要和重复的。

0

我需要更多的背景信息。

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

由于视频压缩,图像过于模糊,即使对图像进行预处理以提高质量,也可能无法获得足够高的图像质量以进行准确的OCR。如果您决定使用OCR,则可以尝试以下方法:

  1. 将图像二值化,以获得白色的非红色文本和黑色背景,如您的二值化图像所示:

    from PIL import Image
    
    def binarize_image(im, threshold):
    """二值化图像。"""
        image = im.convert('L')  # 将图像转换为单色
        bin_im = image.point(lambda p: p > threshold and 255)
        return bin_im
    
    im = Image.open("game_text.JPG")
    binarized = binarize_image(im, 100)
    

enter image description here

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

    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()
    
  • 然而,即使有这种过滤,它仍然无法很好地提取红色。

    enter image description here

    enter image description here

    1. 添加从(1.)和(2.)获取的图像。

      combined_image = binarized + red_binarized
      

    enter image description here

    1. 对(3.)进行OCR识别

    0
    你需要对图像进行多次预处理,并使用按位或操作来合并结果。要提取颜色,可以使用
    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...
    

    一旦您获得了掩码图像,您可以在要成为“最终”图像上执行另一个按位或操作,从而将此掩码添加到其中。
    但是,这种特定的实现需要使用OpenCV,然而相同原理适用于其他图像包。

    0

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

    通过将您的数据从BGR(或RGB)转换为CIE-Lab,您可以将灰度图像处理为颜色通道a*和b*的加权和。 这个灰度图像将增强文本的颜色区域。 但是通过调整阈值,您可以从这个灰度图像中分割出原始图像中的彩色单词,并从a 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')
    

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

    这是我用来清除噪声并为字符创建统一亮度的代码。经过小修改,我用它来解决了你的问题。

    enter image description here


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接