如何使用不同颜色和字体的文本改进图像的 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?的主要内容,如果未能解决你的问题,请参考以下文章