字符/数字的边界框检测

6

我有一些图像,看起来像这样:

enter image description here

我想要找到8个数字的边界框。我尝试使用cv2和以下代码:

import cv2
import matplotlib.pyplot as plt
import cvlib as cv
from cvlib.object_detection import draw_bbox

im = cv2.imread('31197402.png')
bbox, label, conf = cv.detect_common_objects(im)
output_image = draw_bbox(im, bbox, label, conf)
plt.imshow(output_image)
plt.show()

很遗憾,那不起作用。有人有想法吗?

1个回答

11
你的解决方案中存在的问题可能是输入图像质量非常差。字符和背景之间几乎没有对比度。来自cvlib的blob检测算法可能无法区分字符blob和背景,从而生成无用的二进制掩码。让我们尝试使用纯粹的OpenCV来解决这个问题。

我提出以下步骤:

  1. 应用自适应阈值以获得合理的二进制掩码。
  2. 使用面积过滤器清除二进制掩码中的blob噪声。
  3. 使用形态学改善二进制图像的质量。
  4. 获取每个字符的外部轮廓并将一个边界矩形适应到每个字符blob上。
  5. 使用先前计算的边界矩形裁剪每个字符。

让我们看看代码:

# importing cv2 & numpy:
import numpy as np
import cv2

# Set image path
path = "C:/opencvImages/"
fileName = "mrrm9.png"

# Read input image:
inputImage = cv2.imread(path+fileName)
inputCopy = inputImage.copy()

# Convert BGR to grayscale:
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)

从这里开始,我们只需要读取 BGR 图像并将其转换为 灰度。现在,让我们使用 高斯自适应阈值 进行处理。这是一个棘手的部分,因为参数需要根据输入的质量手动调整。该方法的工作原理是将图像分成大小为 windowSize 的单元格网格,然后应用本地阈值以找到前景和背景之间的最佳分离点。可以通过添加由 windowConstant 指示的额外常数来微调输出的阈值:

# Set the adaptive thresholding (gasussian) parameters:
windowSize = 31
windowConstant = -1
# Apply the threshold:
binaryImage = cv2.adaptiveThreshold(grayscaleImage, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, windowSize, windowConstant)

您获得了这张漂亮的二进制图像:

现在,您可以看到该图像有一些斑点噪声。让我们应用一个面积过滤器来去除噪声。噪声比感兴趣的目标斑点要小,所以我们可以根据面积轻松过滤它们,如下所示:

# Perform an area filter on the binary blobs:
componentsNumber, labeledImage, componentStats, componentCentroids = \
cv2.connectedComponentsWithStats(binaryImage, connectivity=4)

# Set the minimum pixels for the area filter:
minArea = 20

# Get the indices/labels of the remaining components based on the area stat
# (skip the background component at index 0)
remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]

# Filter the labeled pixels based on the remaining labels,
# assign pixel intensity to 255 (uint8) for the remaining pixels
filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype('uint8')

这是经过滤波处理后的图像:

我们可以通过一些形态学方法来提高这张图像的质量。一些字符似乎已经断裂了(例如第一个3,它被分成了两个不连通的区域)。我们可以应用闭运算将它们连接起来:

# Set kernel (structuring element) size:
kernelSize = 3

# Set operation iterations:
opIterations = 1

# Get the structuring element:
maxKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))

# Perform closing:
closingImage = cv2.morphologyEx(filteredImage, cv2.MORPH_CLOSE, maxKernel, None, None, opIterations, cv2.BORDER_REFLECT101)

这是“闭合”的图像:

现在,您想要获取每个字符的边界框。让我们检测每个区块的外轮廓并在其周围拟合一个漂亮的矩形:

# Get each bounding box
# Find the big contours/blobs on the filtered image:
contours, hierarchy = cv2.findContours(closingImage, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

contours_poly = [None] * len(contours)
# The Bounding Rectangles will be stored here:
boundRect = []

# Alright, just look for the outer bounding boxes:
for i, c in enumerate(contours):

    if hierarchy[0][i][3] == -1:
        contours_poly[i] = cv2.approxPolyDP(c, 3, True)
        boundRect.append(cv2.boundingRect(contours_poly[i]))


# Draw the bounding boxes on the (copied) input image:
for i in range(len(boundRect)):
    color = (0, 255, 0)
    cv2.rectangle(inputCopy, (int(boundRect[i][0]), int(boundRect[i][1])), \
              (int(boundRect[i][0] + boundRect[i][2]), int(boundRect[i][1] + boundRect[i][3])), color, 2)

最后一个for循环基本是可选的。它从列表中获取每个边界矩形并将其绘制在输入图像上,以便您可以看到每个单独的矩形,如下所示: 让我们在二进制图像上可视化它: 此外,如果您想使用刚刚得到的边界框裁剪每个字符,则可以按以下方式执行:
# Crop the characters:
for i in range(len(boundRect)):
    # Get the roi for each bounding rectangle:
    x, y, w, h = boundRect[i]

    # Crop the roi:
    croppedImg = closingImage[y:y + h, x:x + w]
    cv2.imshow("Cropped Character: "+str(i), croppedImg)
    cv2.waitKey(0)

以下是获取单个边界框的方法。现在,你可能正在尝试将这些图像传递给一个OCR。我尝试将过滤后的二值图像(经过闭操作后)传递到pyocr(那是我在使用的OCR),并得到如下输出字符串:31197402

我用于获取闭合图像OCR的代码如下:

# Set the OCR libraries:
from PIL import Image
import pyocr
import pyocr.builders

# Set pyocr tools:
tools = pyocr.get_available_tools()
# The tools are returned in the recommended order of usage
tool = tools[0]

# Set OCR language:
langs = tool.get_available_languages()
lang = langs[0]

# Get string from image:
txt = tool.image_to_string(
    Image.open(path + "closingImage.png"),
    lang=lang,
    builder=pyocr.builders.TextBuilder()
)

print("Text is:"+txt)

请注意,OCR 会接收白底黑字的图像,因此您需要先将图像反转。


非常感谢您的回复(再次):)。您的方法非常好,但问题是它需要太多手动信息。我需要一些可以自动读取数字而无需任何手动预处理的东西。我在考虑编写一个算法来至少找到正确的边界框,以便我可以训练神经网络完成此任务(类似于MNIST)。您知道这方面有什么可能性吗? - spadel
1
@spadel 很抱歉,您的问题中并没有提到要求完全自动化的解决方案。您要求数字的边界框,我已经提供了。或许可以发布一个新问题,只询问基于深度学习的答案,看看是否会有任何建议。祝您好运,我的朋友。 - stateMachine
我希望能找到一种算法,可以普遍适用且最小程度地进行调整。实际上,当使用您的方法迭代手动输入数据时,我取得了相当不错的结果。但这也是计算上昂贵的。无论如何,我仍将您的答案标记为解决方案,因为这是目前为止最好的,所以再次感谢! - spadel

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