你的解决方案中存在的问题可能是输入图像质量非常差。字符和背景之间几乎没有对比度。来自
cvlib
的blob检测算法可能无法区分字符blob和背景,从而生成无用的二进制掩码。让我们尝试使用纯粹的
OpenCV
来解决这个问题。
我提出以下步骤:
- 应用自适应阈值以获得合理的二进制掩码。
- 使用面积过滤器清除二进制掩码中的blob噪声。
- 使用形态学改善二进制图像的质量。
- 获取每个字符的外部轮廓并将一个边界矩形适应到每个字符blob上。
- 使用先前计算的边界矩形裁剪每个字符。
让我们看看代码:
import numpy as np
import cv2
path = "C:/opencvImages/"
fileName = "mrrm9.png"
inputImage = cv2.imread(path+fileName)
inputCopy = inputImage.copy()
grayscaleImage = cv2.cvtColor(inputImage, cv2.COLOR_BGR2GRAY)
从这里开始,我们只需要读取 BGR
图像并将其转换为 灰度
。现在,让我们使用 高斯自适应阈值
进行处理。这是一个棘手的部分,因为参数需要根据输入的质量手动调整。该方法的工作原理是将图像分成大小为 windowSize
的单元格网格,然后应用本地阈值以找到前景和背景之间的最佳分离点。可以通过添加由 windowConstant
指示的额外常数来微调输出的阈值:
windowSize = 31
windowConstant = -1
binaryImage = cv2.adaptiveThreshold(grayscaleImage, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, windowSize, windowConstant)
您获得了这张漂亮的二进制图像:
现在,您可以看到该图像有一些斑点噪声。让我们应用一个面积过滤器
来去除噪声。噪声比感兴趣的目标斑点要小,所以我们可以根据面积轻松过滤它们,如下所示:
componentsNumber, labeledImage, componentStats, componentCentroids = \
cv2.connectedComponentsWithStats(binaryImage, connectivity=4)
minArea = 20
remainingComponentLabels = [i for i in range(1, componentsNumber) if componentStats[i][4] >= minArea]
filteredImage = np.where(np.isin(labeledImage, remainingComponentLabels) == True, 255, 0).astype('uint8')
这是经过滤波处理后的图像:
我们可以通过一些形态学方法来提高这张图像的质量。一些字符似乎已经断裂了(例如第一个3
,它被分成了两个不连通的区域)。我们可以应用闭运算将它们连接起来:
kernelSize = 3
opIterations = 1
maxKernel = cv2.getStructuringElement(cv2.MORPH_RECT, (kernelSize, kernelSize))
closingImage = cv2.morphologyEx(filteredImage, cv2.MORPH_CLOSE, maxKernel, None, None, opIterations, cv2.BORDER_REFLECT101)
这是“闭合”的图像:
现在,您想要获取每个字符的边界框
。让我们检测每个区块的外轮廓并在其周围拟合一个漂亮的矩形:
contours, hierarchy = cv2.findContours(closingImage, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
contours_poly = [None] * len(contours)
boundRect = []
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]))
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
循环基本是可选的。它从列表中获取每个边界矩形并将其绘制在输入图像上,以便您可以看到每个单独的矩形,如下所示:
![](https://i.imgur.com/ggiPJ7T.png)
让我们在二进制图像上可视化它:
![](https://i.imgur.com/uNys034.png)
此外,如果您想使用刚刚得到的边界框裁剪每个字符,则可以按以下方式执行:
for i in range(len(boundRect)):
x, y, w, h = boundRect[i]
croppedImg = closingImage[y:y + h, x:x + w]
cv2.imshow("Cropped Character: "+str(i), croppedImg)
cv2.waitKey(0)
以下是获取单个边界框的方法。现在,你可能正在尝试将这些图像传递给一个OCR。我尝试将过滤后的二值图像(经过闭操作后)传递到pyocr(那是我在使用的OCR),并得到如下输出字符串:31197402
我用于获取闭合图像OCR的代码如下:
from PIL import Image
import pyocr
import pyocr.builders
tools = pyocr.get_available_tools()
tool = tools[0]
langs = tool.get_available_languages()
lang = langs[0]
txt = tool.image_to_string(
Image.open(path + "closingImage.png"),
lang=lang,
builder=pyocr.builders.TextBuilder()
)
print("Text is:"+txt)
请注意,OCR
会接收白底黑字的图像,因此您需要先将图像反转。