从图像中分离单个数字以进行 OCR

Isolating individual numbers from image for OCR

单个数字的字符识别方法非常简单。但这是图像只包含一个数字的时候。

当图像包含多个数字时,我们不能使用相同的算法,因为整个位图是不同的。我们如何处理图像以将其拆分,以便我们可以 "modularise" 对每个数字进行 OCR 操作?

按照以下步骤操作:

  1. 加载图像。
  2. Select 数字(通过轮廓查找并对字母的面积和高度应用约束以避免错误检测)。这将拆分图像,从而模块化您要执行的 OCR 操作。
  3. 用于执行识别和分类的简单 K - 最近邻算法。

但是你要执行的是图像分割问题,而不是数字分类问题。就像@VitaliPro 说的。两者都是 OCR 问题,但是(在一个巨大的简化中)第一个问题是 "what character is this",第二个是 "how many characters I have here"。你已经知道如何解决第一个问题,让我们看看第二个通常是如何解决的。

您想将图像分割成字符(在分割中称为"regions"),然后将数字分类应用于每个区域。一种方法是执行 Watershed Segmentation,它使用颜色渐变来区分边缘和区域。

一个简单的分水岭可以用Python的numpy/scipy/skimage来完成,例如:

#!/usr/bin/env python

from PIL import Image
import numpy as np
from scipy import ndimage
from skimage import morphology as morph
from skimage.filter import rank

def big_regions(lb, tot):
    l = []
    for i in range(1, tot+1):
        l.append(((i == lb).sum(), i))
    l.sort()
    l.reverse()
    return l

def segment(img, outimg):
    img = np.array(Image.open(img))
    den = rank.median(img, morph.disk(3))
    # continuous regions (low gradient)
    markers  = rank.gradient(den, morph.disk(5)) < 10
    mrk, tot = ndimage.label(markers)
    grad     = rank.gradient(den, morph.disk(2))
    labels   = morph.watershed(grad, mrk)
    print 'Total regions:', tot
    regs = big_regions(labels, tot)

我正在使用 skimagemorph 模块的分水岭分割。

大多数情况下,对于分水岭,您应该将区域放在图像的顶部以获得该区域的实际内容,我在上面的代码中没有这样做。然而,数字或大多数文本不需要它,因为它应该是黑白的。

Watershed 使用颜色渐变来识别边缘,但也可以使用 Canny 或 Sobel 过滤器等过滤器。请注意,我正在执行图像的去噪(轻微模糊)以防止发现非常小的区域,因为这些区域很可能是伪影或噪声。使用 Canny 或 Sobel 过滤器可能需要更多的去噪步骤,因为过滤器会产生清晰的边缘。

分割不仅仅用于字符分割,它通常用于图像区分重要区域(即外观非常相似的大区域)。例如,如果我在上面添加一些 matplotlib 并更改段函数,比如:

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.cm as cm

def plot_seg(spr, spc, sps, img, cmap, alpha, xlabel):
    plt.subplot(spr, spc, sps)
    plt.imshow(img, cmap=cmap, interpolation='nearest', alpha=alpha)
    plt.yticks([])
    plt.xticks([])
    plt.xlabel(xlabel)

def plot_mask(spr, spc, sps, reg, lb, regs, cmap, xlabel):
    masked = np.ma.masked_array(lb, ~(lb == regs[reg][1]))
    plot_seg(spr, spc, sps, masked, cmap, 1, xlabel)

def plot_crop(spr, spc, sps, reg, img, lb, regs, cmap):
    masked = np.ma.masked_array(img, ~(lb == regs[reg][1]))
    crop   = masked[~np.all(masked == 0, axis=1), :]
    crop   = crop[:, ~np.all(crop == 0, axis=0)]
    plot_seg(spr, spc, sps, crop, cmap, 1, '%i px' % regs[reg][0])

def segment(img, outimg):
    img = np.array(Image.open(img))
    den = rank.median(img, morph.disk(3))
    # continuous regions (low gradient)
    markers  = rank.gradient(den, morph.disk(5)) < 10
    mrk, tot = ndimage.label(markers)
    grad     = rank.gradient(den, morph.disk(2))
    labels   = morph.watershed(grad, mrk)
    print 'Total regions:', tot
    regs = big_regions(labels, tot)

    spr = 3
    spc = 6
    plot_seg(spr, spc, 1, img,    cm.gray,     1,   'image')
    plot_seg(spr, spc, 2, den,    cm.gray,     1,   'denoised')
    plot_seg(spr, spc, 3, grad,   cm.spectral, 1,   'gradient')
    plot_seg(spr, spc, 4, mrk,    cm.spectral, 1,   'markers')
    plot_seg(spr, spc, 5, labels, cm.spectral, 1,   'regions\n%i' % tot)
    plot_seg(spr, spc, 6, img,    cm.gray,     1,   'composite')
    plot_seg(spr, spc, 6, labels, cm.spectral, 0.7, 'composite')

    plot_mask(spr, spc, 7,  0, labels, regs, cm.spectral, 'main region')
    plot_mask(spr, spc, 8,  1, labels, regs, cm.spectral, '2nd region')
    plot_mask(spr, spc, 9,  2, labels, regs, cm.spectral, '3rd region')
    plot_mask(spr, spc, 10, 3, labels, regs, cm.spectral, '4th region')
    plot_mask(spr, spc, 11, 4, labels, regs, cm.spectral, '5th region')
    plot_mask(spr, spc, 12, 5, labels, regs, cm.spectral, '6th region')

    plot_crop(spr, spc, 13, 0, img, labels, regs, cm.gray)
    plot_crop(spr, spc, 14, 1, img, labels, regs, cm.gray)
    plot_crop(spr, spc, 15, 2, img, labels, regs, cm.gray)
    plot_crop(spr, spc, 16, 3, img, labels, regs, cm.gray)
    plot_crop(spr, spc, 17, 4, img, labels, regs, cm.gray)
    plot_crop(spr, spc, 18, 5, img, labels, regs, cm.gray)

    plt.show()

(此示例本身没有 运行,您需要将上面的其他代码示例添加到它的顶部。)

我可以很好地分割任何图像,例如以上结果:

第一行是 segmentation 函数的步骤,第二行是区域,第三行是用作图像顶部遮罩的区域。

(P.S。是的,剧情代码挺难看的,但是好理解改改)