将文本图像分割为字符图像

6 投票
6 回答
13995 浏览
提问于 2025-04-15 17:26

我想把一张包含文字的图片分解成每个字母的单独图片。比如,像下面这个例子,我想最后得到14张图片。

因为我只会处理单行文字,所以字母的高度不太重要。我需要做的是找到每个字母的开始和结束位置,然后把它们裁剪到这些坐标。这样我就能避免处理像'i'、'j'这样的字母时出现的问题。

我对图像处理还很陌生,不太知道该怎么做。是不是需要某种边缘检测?有没有办法找出连续的同色区域?任何帮助都非常感谢。

我想提高我的Python技能,并熟悉一些可用的库,所以我正在使用Python图像库(PIL),同时也看了一下OpenCV。


示例图片:

这是一段文字

6 个回答

2

你可以从一个简单的连通区域分析(CCA)算法开始,这个算法可以用一种叫做扫描线的方式高效实现(你只需要跟踪合并的区域,最后再重新标记)。这样,你就能为每个连续的区域分配一个独立的编号,通常这对大多数字母都有效(但不是全部)。接着,你可以简单地获取每个连通区域的边界框,这样就能得到每个字母的轮廓。为了提高效率,你甚至可以在应用CCA时保持边界框的更新。

所以在你的例子中,经过CCA处理后,最左边的第一个单词会得到类似这样的结果:

1111111  2         3
   1     2
   1     2 4444    5  666
   1     22    4   5 6
   1     2     4   5  666
   1     2     4   5     6
   1     2     4   5  666

这里的等价类是4=2。

然后每个区域的边界框就能给你字母周围的区域。对于像字母i和j这样的字母,你可能会遇到一些问题,但可以单独处理。你可以寻找一个小于某个大小的区域,这个区域位于另一个宽度特定的区域上方(这是一种粗略的启发式方法)。

OpenCV中的cvBlobsLib库应该能帮你完成大部分工作。

6

我知道我来得有点晚 :-) 不过现在你可以很简单地用ImageMagick在命令行上做这种事情,不需要编译任何东西,因为它内置了连通组件分析的功能:

这里有一种方法可以做到这一点:

#!/bin/bash
image="$1"
draw=$(convert $image                              \
   -threshold 50%                                  \
   -define connected-components:verbose=true       \
   -define connected-components:area-threshold=10  \
   -connected-components 8                         \
   -auto-level objects.png | \
   awk 'BEGIN{command=""}
        /\+0\+0/||/id:/{next}
        {
          geom=$2
          gsub(/x/," ",geom)
          gsub(/+/," ",geom)
          split(geom,a," ")
          d=sprintf("-draw \x27rectangle %d,%d %d,%d\x27 ",a[3],a[4],a[3]+a[1],a[4]+a[2])
          command = command d
          #printf "%d,%d %d,%d\n",a[3],a[4],a[3]+a[1],a[4]+a[2]
        }
        END{print command}')

eval convert "$image" -fill none -strokewidth 2 -stroke red $draw result.png

结果看起来是这样的:

在这里输入图片描述

首先,我把你的图片进行50%的阈值处理,这样图片里就只有纯黑和纯白,没有任何渐变色。接着,我告诉ImageMagick输出它找到的边界框的详细信息,并且我不想要小于10个像素总面积的物体。然后,我允许像素之间是8连通的,也就是说,它们可以和对角线的邻居(东北、 southeast、西北、西南)以及左右和上下的邻居相连。最后,我用awk来解析边界框的输出,并在边界框周围画上红线。

我用awk解析的初始命令的输出看起来是这样的:

Objects (id: bounding-box centroid area mean-color):
  0: 539x53+0+0 263.7,24.3 20030 srgba(255,255,255,1)
  11: 51x38+308+14 333.1,30.2 869 srgba(0,0,0,1)
  13: 35x39+445+14 461.7,32.8 670 srgba(0,0,0,1)
  12: 35x39+365+14 381.7,32.8 670 srgba(0,0,0,1)
  2: 30x52+48+0 60.4,27.0 634 srgba(0,0,0,1)
  1: 41x52+1+0 20.9,16.6 600 srgba(0,0,0,1)
  8: 30x39+174+14 188.3,33.1 595 srgba(0,0,0,1)
  7: 30x39+102+14 116.3,33.1 595 srgba(0,0,0,1)
  9: 30x39+230+14 244.3,33.1 595 srgba(0,0,0,1)
  10: 35x39+265+14 282.2,33.0 594 srgba(0,0,0,1)
  16: 33x37+484+15 500.2,33.0 520 srgba(0,0,0,1)
  17: 22x28+272+19 282.3,32.8 503 srgba(255,255,255,1)
  5: 18x51+424+2 432.5,27.9 389 srgba(0,0,0,1)
  6: 18x51+520+2 528.5,27.9 389 srgba(0,0,0,1)
  15: 6x37+160+15 162.5,33.0 222 srgba(0,0,0,1)
  14: 6x37+88+15 90.5,33.0 222 srgba(0,0,0,1)
  18: 22x11+372+19 382.6,24.9 187 srgba(255,255,255,1)
  19: 22x11+452+19 462.6,24.9 187 srgba(255,255,255,1)
  3: 6x8+88+0 90.5,3.5 48 srgba(0,0,0,1)
  4: 6x8+160+0 162.5,3.5 48 srgba(0,0,0,1)

awk把它变成了这样:

convert http://imgur.com/AVW7A.png -fill none -strokewidth 2 -stroke red \
-draw 'rectangle 308,14 359,52'        \
-draw 'rectangle 445,14 480,53'        \
-draw 'rectangle 365,14 400,53'        \
-draw 'rectangle 48,0 78,52'           \
-draw 'rectangle 1,0 42,52'            \
-draw 'rectangle 174,14 204,53'        \
-draw 'rectangle 102,14 132,53'        \
-draw 'rectangle 230,14 260,53'        \
-draw 'rectangle 265,14 300,53'        \
-draw 'rectangle 484,15 517,52'        \
-draw 'rectangle 272,19 294,47'        \
-draw 'rectangle 424,2 442,53'         \
-draw 'rectangle 520,2 538,53'         \
-draw 'rectangle 160,15 166,52'        \
-draw 'rectangle 88,15 94,52'          \
-draw 'rectangle 372,19 394,30'        \
-draw 'rectangle 452,19 474,30'        \
-draw 'rectangle 88,0 94,8'            \
-draw 'rectangle 160,0 166,8' result.png
6

这并不是一件简单的事情,尤其是当背景不均匀的时候。如果你手上的图像已经是二值图像,比如例子中的那种,那就稍微简单一些。

如果你的图像不是二值的,可以先应用一个阈值算法(Otsu自适应阈值效果不错)。

接下来,你可以使用一个标记算法来识别每个像素的“岛”,这些“岛”就是你要找的形状(在这个例子中是每个字符)。

问题在于噪音的存在。有些形状被标记了,但并不是你想要的。在这种情况下,你可以使用一些经验法则来判断一个形状是否是字符(比如可以用标准化的面积、物体的位置,如果你的文本在一个明确的位置等等)。如果这些方法不够,你就需要处理更复杂的东西,比如形状特征提取算法和某种模式识别算法,比如多层感知器。

最后,这看起来是个简单的任务,但根据你图像的质量,可能会变得更困难。这里提到的算法可以在网上轻松找到,或者在一些库中实现,比如OpenCv。

如果还有其他问题,随时问我,如果我能帮上忙的话;)

撰写回答