拆分图像中的数字

Splitting up digits in images

我接触过很多手写的报告。报告中的其中一列包含时间戳,我想尝试在不手动查看每个报告的情况下进行识别。

我正在玩分裂时间的想法,例如00:30,分成四位数字,然后 运行通过在 MNIST 上训练的分类器对这些数字进行分类,以识别实际时间戳。

当我在 Photoshop 中手动提取四位数字并通过 MNIST 分类器 运行 这些时,它工作得很好。但到目前为止,我还没有弄清楚如何以编程方式将数字序列拆分为单个数字。我尝试在 OpenCV 中使用不同类型的计数结果,但它的工作不是很可靠。

有什么建议吗?

我已经 added a screenshot 报告中的一些相关栏目。

将文本分解成单个字符并不像一开始听起来那么容易。您可以尝试找到一些规则并以此来操纵图像,但是会有太多例外。例如,您可以尝试找到不相交的标记,但图像中的第四个 0715 将“5”分成三部分,第 9th17.00 有两个零重叠。

你很幸运有水平线 - 至少很容易分隔不同的条目。但是你必须想出很多与半固定字符宽度、"soft" 不相交规则等相关的想法

两年前我做了一个这样的项目,我们最终使用了一个名为 Tesseract. Here's this article of Roman numerals recognition with it, up to about 90% accuracy. You might also want to look into the Lipi Toolkit 的外部开源库,但我没有这方面的经验。

您可能还想考虑只训练网络一次识别四位数字。所以输入将是带有四个手写数字的整个字段,输出将是四个数字。并让网络整理出角色所在的位置。如果您有足够的训练数据,那可能是最简单的方法。

编辑: 受@Link的回答启发,萌生了这个想法,大家可以试试看。一旦你提取了两条线之间的区域,trim 图像就摆脱了周围的白色 space。然后对字符有多大进行有根据的猜测。使用该区域的高度?然后在图片上创建滑动window,一路运行识别。很可能会有四个峰值对应于四个数字。

我会这样做(没有代码,只要它只是一个想法,你可以测试它看看是否有效):

  1. 按照 Rick M. 上面的建议,为每组数字提取每个区域。所以你会在图像形式下有很多Kl [hour]个矩形。

  2. 对于这些矩形中的每一个,提取(使用 OpenCV 轮廓特征)每个 ROI。不需要的Kl删掉(你知道这个ROI的维度(你可以用img.shape来计算),它们的维度差不多)

  3. 使用上面使用的相同脚本提取所有数字。您可以查看我的 questions/answers 以找到一些执行此操作的代码。 在某些情况下,您会遇到下划线问题。在SO上搜索这个,很少有完整的代码解决方案。

  4. 现在,关于分手。我们知道 ROI 的格式是小时,因此 hh:mm(或 4 位数字)。一个简单的(也是非常基本的)拆分字符的解决方案是将你得到的 ROI 分成一半,里面有 2 个数字。这是一个原始解决方案,但在您的情况下应该表现良好,因为附加的数字仅为 2.

  5. 部分数字会输出"missing pieces"。这可以通过使用一些 .

  6. 来避免

这里没有字母,只有数字,所以 MNIST 应该能很好地工作(不完美,请记住这一点)。

在少数情况下,提取数据并不是一件难事,但识别数字会让你有点汗。

我希望我能尽快提供一些代码来显示上述步骤。

编辑 - 代码

这是我编写的一些代码。最终输出是这样的:

该代码 100% 与此图像一起工作,因此,如果某些东西不适合您,check folders/paths/modules installation

希望对您有所帮助。

import cv2
import numpy as np

# 1 - remove the vertical line on the left

img = cv2.imread('image.jpg', 0)
# gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(img, 100, 150, apertureSize=5)

lines = cv2.HoughLines(edges, 1, np.pi / 50, 50)
for rho, theta in lines[0]:
    a = np.cos(theta)
    b = np.sin(theta)
    x0 = a * rho
    y0 = b * rho
    x1 = int(x0 + 1000 * (-b))
    y1 = int(y0 + 1000 * (a))
    x2 = int(x0 - 1000 * (-b))
    y2 = int(y0 - 1000 * (a))

    cv2.line(img, (x1, y1), (x2, y2), (255, 255, 255), 10)

cv2.imshow('marked', img)
cv2.waitKey(0)
cv2.imwrite('image.png', img)


# 2 - remove horizontal lines

img = cv2.imread("image.png")
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img_orig = cv2.imread("image.png")

img = cv2.bitwise_not(img)
th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 15, -2)
cv2.imshow("th2", th2)
cv2.waitKey(0)
cv2.destroyAllWindows()

horizontal = th2
rows, cols = horizontal.shape

# inverse the image, so that lines are black for masking
horizontal_inv = cv2.bitwise_not(horizontal)
# perform bitwise_and to mask the lines with provided mask
masked_img = cv2.bitwise_and(img, img, mask=horizontal_inv)
# reverse the image back to normal
masked_img_inv = cv2.bitwise_not(masked_img)
cv2.imshow("masked img", masked_img_inv)
cv2.waitKey(0)
cv2.destroyAllWindows()

horizontalsize = int(cols / 30)
horizontalStructure = cv2.getStructuringElement(cv2.MORPH_RECT, (horizontalsize, 1))
horizontal = cv2.erode(horizontal, horizontalStructure, (-1, -1))
horizontal = cv2.dilate(horizontal, horizontalStructure, (-1, -1))
cv2.imshow("horizontal", horizontal)
cv2.waitKey(0)
cv2.destroyAllWindows()

# step1
edges = cv2.adaptiveThreshold(horizontal, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 3, -2)
cv2.imshow("edges", edges)
cv2.waitKey(0)
cv2.destroyAllWindows()

# step2
kernel = np.ones((1, 2), dtype="uint8")
dilated = cv2.dilate(edges, kernel)
cv2.imshow("dilated", dilated)
cv2.waitKey(0)
cv2.destroyAllWindows()

im2, ctrs, hier = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# sort contours
sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0])

for i, ctr in enumerate(sorted_ctrs):
    # Get bounding box
    x, y, w, h = cv2.boundingRect(ctr)

    # Getting ROI
    roi = img[y:y + h, x:x + w]

    # show ROI
    rect = cv2.rectangle(img_orig, (x, y), (x + w, y + h), (255, 255, 255), -1)

cv2.imshow('areas', rect)
cv2.waitKey(0)

cv2.imwrite('no_lines.png', rect)


# 3 - detect and extract ROI's

image = cv2.imread('no_lines.png')
cv2.imshow('i', image)
cv2.waitKey(0)

# grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
cv2.imshow('gray', gray)
cv2.waitKey(0)

# binary
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
cv2.imshow('thresh', thresh)
cv2.waitKey(0)

# dilation
kernel = np.ones((8, 45), np.uint8)  # values set for this image only - need to change for different images
img_dilation = cv2.dilate(thresh, kernel, iterations=1)
cv2.imshow('dilated', img_dilation)
cv2.waitKey(0)

# find contours
im2, ctrs, hier = cv2.findContours(img_dilation.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# sort contours
sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0])

for i, ctr in enumerate(sorted_ctrs):
    # Get bounding box
    x, y, w, h = cv2.boundingRect(ctr)

    # Getting ROI
    roi = image[y:y + h, x:x + w]

    # show ROI
    # cv2.imshow('segment no:'+str(i),roi)
    cv2.rectangle(image, (x, y), (x + w, y + h), (255, 255, 255), 1)
    # cv2.waitKey(0)

    # save only the ROI's which contain a valid information
    if h > 20 and w > 75:
        cv2.imwrite('roi\{}.png'.format(i), roi)

cv2.imshow('marked areas', image)
cv2.waitKey(0)

这些是后续步骤:

  1. 看懂我写的;)。这是最重要的一步。

  2. 使用上面的代码片段(尤其是 step 3),您可以删除提取图像中剩余的 Kl

  3. 为每个图像创建文件夹并提取数字。

  4. 使用 MNIST,识别每个数字。