如何使用光学字符识别解析数字 4

Posted

技术标签:

【中文标题】如何使用光学字符识别解析数字 4【英文标题】:How to parse the number 4 using optical character recognition 【发布时间】:2013-09-26 00:48:25 【问题描述】:

我正在研究一种 OCR 算法,该算法给出了一个包含一些数字的图像。我希望它能够简单地检测每个图像,并将其与其他图像分开。

它适用于 0-9 的所有数字,除了数字 4,这给我带来了很多麻烦。

这是我的源图片:

以下是一些数字的解析结果:

如您所见,它们都被完美解析。唯一给我带来麻烦的是 4。这是数字 4 的外观:

我遇到的问题是检测 4 的最左角,以便包含整个数字。 这是我的算法在尝试检测每个字符的左边界时向下遍历的方式(蓝点表示算法采用的路径):

如果您在另一个选项卡中打开图像并放大,您可能会更好地看到它在做什么。

如您所见,它向下和向左移动,直到两次遇到背景。当它发生时,这意味着已经到达图像的最左侧边界。它适用于所有其他图像,除了 4,您可以看到它两次遇到背景并停止,但如果它继续向下多两个像素,那么它将遇到更多的 4 并找到它真正的左-最边缘。

我不确定如何以不会破坏其他数字的方式执行此操作。这是我的实际代码以防万一:

    int misses = 0;
    int maxMisses = 2;
    while (y < image.getHeight() && x >= 0 )
    
        markPixel(x, y);
        color = image.getRGB(x, y);
        if (! reader.isForeground(color))
            misses++;

        if (misses < maxMisses)
        
            y++;
            x--;
            continue;
        
        x++;
        break;
            
    if (x < 0)
        x = 0;
    return x;

编辑:我已经能够通过一直遍历图像而不是在遇到 2 个背景像素时停下来实现一些改进,当我遇到前景像素时存储每一步的 x 坐标,然后按升序对匹配项进行排序并返回它们的最低结果。它工作得更好一些。新的 4 图像:

它还不完美。另外,9 看起来有点小:

算法遍历的新路径:

更新代码:

    ArrayList<Integer> matches = new ArrayList<>();
    int yB = y;
    for (int i = 1; i <= 2; i++)
    
        y = yB;
        while (y < image.getHeight() && x >= 0 )
        
            markPixel(x, y);
            color = image.getRGB(x, y);
            if ( reader.isForeground(color))
                matches.add(x);

            y++;                
              
        x--;
    

    Collections.sort(matches);
    return matches.get(0);

任何人有任何想法来消除 4 中的最后一个缺陷?

【问题讨论】:

从您发布的源图片中可以看出,另一种方法是识别数字之间的“空白”空间;然后修剪每个分段数字以获得“最小”数字矩形。空格是那些不包含任何前景像素的“像素列”。这种方法适用于您的情况吗? 如果你看到最左边的边缘有前景像素,你可以向后移动几个像素 @ratchetfreak 不会让它继续向右直到找到图像的最右边而不是他需要的左边吗? 您还可以更改条件以检查向下像素是否为命中。如果两者都不是前景,这只是一个错过。向左 向下移动可确保您找不到任何角度超过 45 度的东西。如果你改变字体,那可能是一个更大的问题。 @Geobits 叮叮……这似乎是正确的答案! 【参考方案1】:

改编自我的评论,这似乎是对您发布的算法进行最小修改的方法:

检查下一个像素时不要同时向下和向左移动,而是单独检查它们。如果 lefter(?) 和 lower 像素都未命中,则只是未命中。这将有助于任何超过 45 度坡度的角度,如果您更改字体等,您可能会遇到这种情况。


但是,

如果你愿意改变你的算法,我认为其他答案最终可能会更稳健。

来自 Durandal 的回答:围绕数字绕一圈,跟踪最小 x/y 和最大 x/y。要获取左下角,它是min x, max y,假设原点在左上角。我看到你在这里遇到的唯一问题是岛屿(ij)或极其斜体的字体,字符可能在 x 方向重叠。

来自 user2399923 的 cmets: 查找空列来划分字符也很有效。它不会受到岛屿的影响,但会受到上述极端斜体情况的影响,因为在这种情况下可能没有 be 空列。

来自 blgt 的回答: Flood-fill 方法也不错,而且我认为是标准。它需要针对岛屿进行调整,但不会受到斜体的影响,除非角色实际上是感人的。数字中是否存在“漏洞”(例如8)并不重要,因为您只对淹没区域的最小/最大 x/y 值感兴趣。根据定义,这些点都不是洞。

【讨论】:

您的答案完美无缺,除了检查这张图片(放大很多):i.imgur.com/4QwhmbS.png。你会看到它停在 4 的地方,实际上左边和底部都有一个间隙.. 有什么想法吗? 啊,痕迹实际上是在图像的外部。我以为你是在前景边缘追踪。在这种情况下,更改它以检查跟踪点右侧的像素。在您的具体情况下,4 的下一步将是直接向下,因为前景像素存在于右侧但不位于下方。 这好多了,但还剩下一点点 4..i.imgur.com/q9RDdKJ.png 您不应该在最后一步中同时向下和向左移动。如果没有前景向下,直接向下移动。如果你每次都以 45 度角移动,它永远不会起作用。 老实说,我不确定。我认为这会比填充更快,只是因为您检查的像素更少。唯一确定的方法是同时运行一堆和基准测试。【参考方案2】:

按照您的描述,最简单的方法是填充图像。可用于分隔任何不相交的字符(即不能直接用于“i”、“j”等,但适应它并不难)

您所描述的内容听起来比较复杂。当已有解决方案时,您无需重新发明***。

链接 -> http://en.wikipedia.org/wiki/Flood_fill

【讨论】:

我已经考虑过了,但是对于 4、5、8 等数字本身包含间隙的数字,它似乎不起作用。还是会? 会的,只是需要为像素岛添加一些额外的逻辑 你能澄清一下什么样的逻辑吗?我不是在谈论像 i 这样的字符,我只需要解析 0.9 中的数字 我想试试这个方法但是不知道从哪里开始,你能帮帮我吗? 即,我可以使用填充来获取字符的宽度、起始 x、结束 x 位置吗?【参考方案3】:

为什么不沿着图像轮廓顺时针走,直到你一直回到(或足够接近)你遇到第一个黑点的位置?

一个简单的最小/最大跟踪变量对将在您遍历路径时自然地为数字图像提供矩形。这也可以独立于所使用的字体,只要字符不包含多个岛(想想字母 i)并且不相互重叠/连接。

编辑:TC 提到填充字形的洪水。我认为这是一个绝妙的主意,甚至比我上面的方法还要好。

只需找到第一个黑色像素,然后以该像素为起点运行泛洪填充 - 如果将像素复制到单独的图像中,它选择填充的每个像素甚至可以解决如果字体字距调整使相邻字形具有重叠的边界矩形(这可能需要两次执行,第一次找到边界矩形,第二次实际复制像素)。

【讨论】:

我在图像的左下角遇到第一个像素..(对于像 1 和 7 这样的数字,它可能在图像的中心)。顺时针阅读如何帮助我找到数字的左下角..? 您跟踪沿途遇到的最小/最大 x 值,因此您可以轻松查看图像最左侧/最右侧的位置。 所以你本质上是说在数字周围画一个圆圈并记录它在找到前景色的位置遇到的 x 值?但是我可以看到它缺少4,即它可以在它周围画一个圆圈但不会遇到4的左下角 抱歉,“min/max”中有一个错字,可能会造成一些混乱。我不会称之为画一个圆圈,但本质上就是这样。您遵循字形的轮廓并在遵循轮廓的同时跟踪 min(x) 和 max(x)(也可能是 y)。它比您最初的方法复杂一点,但也不容易被愚弄。您在 cmets 中提到的“填充”方法效果相同,基于黑色像素而不是白色像素。它甚至可能实现起来更简单(洪水填充是一个众所周知的问题,因此可能有可用的示例)

以上是关于如何使用光学字符识别解析数字 4的主要内容,如果未能解决你的问题,请参考以下文章

光学字符识别。使用tesseract识别里程表

PDF 上的光学字符识别 (python)

[译]OpenCV的基础光学字符识别(Basic OCR in OpenCV)

如何识别作为光学字符识别 (OCR) 输出的文本中的实体?

光学字符识别OCR-4

Tesseract:简单的Java光学字符识别