从图像中识别数字

35

我想编写一个应用程序,以查找图像中的数字并将它们相加。

如何识别图像中的手写数字?

enter image description here

图像中有许多框,我需要获取左侧的数字并将它们加起来得出总和。我该如何实现这个功能?

编辑:我在图像上使用了Java Tesseract OCR,但没有获得任何正确的结果。我该如何进行训练?

另外,我进行了边缘检测,得到了以下结果:

enter image description here


包含两个数字的矩形总是在彼此下方吗? - npinti
你需要的是OCR。你有没有在谷歌上搜索现有的OCR工具?事实证明,谷歌有一个名为tesseract的OCR工具,并带有一个名为tessjeract的JNI实现。去看看吧。 - Chetan Kinger
@npinti 矩形在纸的右侧,距离会因为中间有问题而有所不同,但是它们会成一条直线。 - Hash
@bot 我尝试在那张图片上使用Tesseract,但它只给了我“S878”,我正在尝试用Java实现。 - Hash
@ChetanKinger 看起来 tessjeract 项目现在已经从网络上消失了 :( - ᴠɪɴᴄᴇɴᴛ
6个回答

20

您很可能需要执行以下操作:

  1. 对整个页面应用Hough变换算法,这应该会产生一系列页面部分。

  2. 对于每个得到的部分,再次应用相同的过程。如果当前部分产生了2个元素,则应该处理类似上述的矩形。

  3. 完成后,可以使用OCR提取数字值。

在这种情况下,我建议您查看JavaCV(OpenCV Java Wrapper),它应该允许您解决Hough Transform问题。然后,您需要类似于Tess4j(Tesseract Java Wrapper)的东西,它应该允许您提取您想要的数字。

额外注意事项,为了减少误报,您可能需要执行以下操作:

  1. 如果您确定某些坐标永远不包含您需要的数据,请裁剪图像。这应该给您一个更小的图片来处理。

  2. 如果您正在使用彩色图像,请将其转换为灰度。颜色可能会对OCR解析图像的能力产生负面影响。

编辑:根据您的评论,假设有这样的情况:

+------------------------------+
|                   +---+---+  |
|                   |   |   |  |
|                   +---+---+  |
|                   +---+---+  |
|                   |   |   |  |
|                   +---+---+  |
|                   +---+---+  |
|                   |   |   |  |
|                   +---+---+  |
|                   +---+---+  |
|                   |   |   |  |
|                   +---+---+  |
+------------------------------+

您需要裁剪图像以删除没有相关数据的区域(左侧部分)。通过裁剪图像,您将得到类似以下内容:

+-------------+
|+---+---+    |
||   |   |    | 
|+---+---+    |
|+---+---+    |
||   |   |    |
|+---+---+    |
|+---+---+    |
||   |   |    |
|+---+---+    |
|+---+---+    |
||   |   |    |
|+---+---+    |
+-------------+

这个想法是运行Hough变换,以便您可以获取页面的段落,其中包含如下所示的矩形:

+---+---+    
|   |   |     
+---+---+ 

然后再次应用霍夫变换,得到两个线段,选择左侧的线段。

得到左侧线段后,进行OCR识别。

可以尝试在此之前进行OCR识别,但至少OCR将会同时识别出两个数字(手写和打印),而这恰恰不是你想要的。

此外,描绘矩形的额外线条可能会使OCR失去方向,导致结果不佳。


这是关于编程的内容,请将其翻译成中文。仅返回翻译后的文本:这将是一份黑白纸张,是的,方框会在右边,没有文字会在上面或下面。只有带有距离变化的方框,因此最好先裁剪它,然后运行OCR并分离值? - Hash
1
@哈希:我已经尝试完善我的回答。如果您有任何疑问,请随时让我知道。简而言之,在进行尽可能多的预处理后(这就是霍夫变换部分所在的位置),您应该将OCR部分留到最后。 - npinti
1
@哈希: 这个链接似乎很有帮助。 - npinti
@pinti: 在Java中实现霍夫变换提取矩形的问题已经解决。 - Hash
让我们在聊天中继续这个讨论。点击此处进入聊天室 - Hash
显示剩余5条评论

10
我建议将两个基本的神经网络组件结合起来:
  • 感知器(Perceptron)
  • 自组织映射(Self Organized Map,SOM)
感知器是一个非常简单的神经网络组件。它接受多个输入并产生一个输出。你需要通过提供输入和输出来对其进行训练。它是一种自学习的组件。
在内部,它有一系列权重因子,用于计算输出。这些权重因子在训练过程中得到完善。感知器的美妙之处在于,(经过适当的训练)它可以处理它从未见过的数据。
您可以通过将感知器排列成多层网络来使其更加强大,这意味着一个感知器的输出作为另一个感知器的输入。
在您的情况下,您应该使用10个感知器网络,每个数字值(0-9)使用一个。

但是,为了使用感知器,您需要一组数字输入。因此,首先需要将视觉图像转换为数字值的工具。自组织映射(SOM)使用互相连接的点阵。这些点应该被吸引到您的图像像素(见下文)。

Self Organized Map

这两个组件很好地配合在一起。SOM有固定数量的网格节点,而你的感知器需要固定数量的输入。
这两个组件非常受欢迎,并且可以在教育软件包(如MATLAB)中使用。
更新:06/01/2018 - Tensor Flow 此视频教程演示了如何使用Google的TensorFlow框架在Python中完成此操作。(单击此处获取书面教程)。

神经网络非常适合分类。如果有多个感知器,您会采取什么方法将结果组合在一起?投票? 此外,如果您需要进行培训,为什么不训练OCR引擎呢?我认为OCR引擎可以利用它是字符的知识,而不仅仅是基于像素。否则,为什么不所有OCR引擎都使用直接的神经网络呢? - Noremac
@Noremac 我并不认为这些字符的绘图是文本。对我来说,它们更像是绘画。另一方面,OCR 对于解析印刷文本非常有用,这些文本通常使用1种特定字体来编写整本书。如果字体始终相同,则不需要神经网络。但对于绘画和图片,我期望神经网络能够提供更好的结果。我听说有时 OCR 使用基于矢量的识别而不是字形,您所指的 OCR 是否属于这种类型? - bvdb
一个OCR引擎可以针对这里的"手写字体"进行训练。我很想看看它能允许多少变化。我认为它在有限的词汇量(仅数字)和经过训练的情况下表现不错。感知器可以更好地处理变化,所以我想这是作者的一致性以及OCR引擎知道它是一个书写字符是否具有任何优势的问题。关于我的另一个问题,您如何建议将感知器结果合并以获得最终分类? - Noremac
@Noremac 你可以简单地让每个网络产生一个单一的输出节点,带有浮点值 [0.0 - 1.0](即 0.0 = 完全不匹配,1.0 = 完美匹配)。然后只需检查哪个网络得分最高即可。 - bvdb

8
神经网络是解决这类问题的典型方法。在这种情况下,您可以将每个手写数字视为像素矩阵。如果您使用与要识别的图像相同大小的图像训练神经网络,则可能会获得更好的结果。您可以使用不同的手写数字图像来训练神经网络。训练完成后,如果您传递要识别的手写数字图像,则它将返回最相似的数字。当然,训练图像的质量是获得良好结果的关键因素。

我同意使用像素矩阵来传递给神经网络(例如感知器网络)是足够的。但是,如果矩阵大小是可变的(我猜这里不是),或者如果您想限制感知器的输入数量,则最好使用SOM(如我的答案所解释的那样)。 - bvdb

5
在大多数图像处理问题中,您希望尽可能利用您拥有的信息。给定图像,我们可以做出以下假设(可能还有更多):
  1. 数字周围的框是一致的。
  2. 右边的数字始终为8(或提前已知)
  3. 左侧的数字始终为数字
  4. 左侧的数字始终为手写,并由同一人编写
然后,我们可以使用这些假设来简化问题:
  1. 您可以使用更简单的方法来查找数字(模板匹配)。当您具有匹配的坐标时,可以创建子图像并减去模板,只留下您想要提供给OCR引擎的数字。 http://docs.opencv.org/doc/tutorials/imgproc/histograms/template_matching/template_matching.html
  2. 如果您知道要期望哪些数字,则可以从另一个源获取它们,而不会冒OCR错误的风险。您甚至可以将8包含在模板中。
  3. 基于此,您可以大大减少词汇量(可能的OCR结果),从而提高OCR引擎的准确性。 TesseractOCR有一个白名单设置可以实现这一点(请参见https://code.google.com/p/tesseract-ocr/wiki/FAQ#How_do_I_recognize_only_digits?)。
  4. 手写对于OCR引擎来说更难识别(它们是针对印刷字体设计的)。但是,您可以训练OCR引擎以识别作者的“字体”。 (请参见http://michaeljaylissner.com/posts/2012/02/11/adding-new-fonts-to-tesseract-3-ocr-engine/
总之,使用任何假设都可以将问题缩小为更小、更简单的子问题。然后查看可用的工具以单独解决每个子问题。
如果您必须开始担心现实世界,例如这些问题将被扫描,则需要考虑“模板”或数字的倾斜或旋转。

8会改变。假设我将行和8隔离开来,只剩下左边的数字(手写文本),那么如果我训练OCR,那就可以工作了吗?是的,同一个人会写这些标记。 - Hash
它的工作效果取决于作者的一致性。我认为如果作者一致,这是你最好的选择。手写识别是一个完全独立的领域,需要考虑可能的变化。OCR更加严格,不允许太多变化。 - Noremac
那么如何推荐识别数字呢? - Hash
你是在问手写数字吗?如果是的话,针对这个问题没有太多特定的可用资源:https://dev59.com/A2LVa4cB1Zd3GeqP1PAB - Noremac
训练识别数字的YA可能。 - Hash

1
放弃吧。真的。作为人类,我无法确定第三个字母是“1”还是“7”。人类在解密方面更擅长,所以计算机会失败。 “1”和“7”只是一个问题案例,“8”和“6”,“3”和“9”也很难辨认/区分。您的错误率将超过10%。 如果所有手写都来自同一人,您可以尝试为此训练OCR,但即使在这种情况下,仍将有约3%的错误。也许您的用例很特殊,但通常这种错误数量会禁止任何形式的自动处理。 如果我真的必须自动化处理,我会考虑使用MTurk。

@amit 人类在进行OCR方面永远比计算机更加优秀。如果人类无法阅读一段文字,那么这段文字就不存在。 - tobltobs
3
不行。几年前有一篇文章,在其中他们给数字图片加入了随机噪声。在一定程度的噪声下,人类无法正确读取数字,但计算机可以正确识别原始数字。 - amit
通过手动处理OCR系统失败的情况,@bvdb。http://img.deseretnews.com/images/article/midres/1270164/1270164.jpg - tobltobs
如果文本真的无法阅读,那并不意味着程序失败了。这就是“UserException”的作用。只需“退回寄件人”。无法阅读的文本也无法被人类排序。 - bvdb
显示剩余5条评论

1
这是一个简单的方法:

  1. 获取二值化图像。 加载图像,转换为灰度图像,然后使用Otsu阈值方法得到一个通道为1的二值图像,像素范围为[0...255]

  2. 检测水平和垂直线条。 创建水平和垂直结构元素,然后通过执行形态学操作将线条绘制到掩模上。

  3. 去除水平和垂直线条。 使用按位或操作组合水平和垂直掩模,然后使用按位与操作去除线条。

  4. 执行OCR。 应用轻微的高斯模糊,然后使用Pytesseract进行OCR。


这是每个步骤的可视化展示:
输入图像 -> 二值图像 -> 水平掩模 -> 垂直掩模

enter image description here enter image description here enter image description here enter image description here

组合遮罩 -> 结果 -> 应用轻微模糊

enter image description here enter image description here enter image description here

OCR的结果

38
18
78

我用Python实现了它,但你可以使用Java来适应类似的方法

import cv2
import pytesseract

pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

# Load image, grayscale, 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]

# Detect horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (25,1))
horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)

# Detect vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,25))
vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=1)

# Remove horizontal and vertical lines
lines = cv2.bitwise_or(horizontal, vertical)
result = cv2.bitwise_not(image, image, mask=lines)

# Perform OCR with Pytesseract
result = cv2.GaussianBlur(result, (3,3), 0)
data = pytesseract.image_to_string(result, lang='eng', config='--psm 6')
print(data)

# Display
cv2.imshow('thresh', thresh)
cv2.imshow('horizontal', horizontal)
cv2.imshow('vertical', vertical)
cv2.imshow('lines', lines)
cv2.imshow('result', result)
cv2.waitKey()

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