Python + OpenCV: OCR 图像分割

20

我正在尝试从这个小票的玩具示例中进行OCR。使用Python 2.7和OpenCV 3.1。

enter image description here

灰度+模糊+外部边缘检测+对小票每个区域进行分割(例如“分类”,以后可以查看哪一个被标记了-在这种情况下是现金)。

当图像“倾斜”时,我发现能够正确变换并“自动”分割小票的每个部分非常复杂。

例如:

enter image description here

有什么建议吗?

以下代码是获取边缘检测的示例,但当小票像第一张图片那样时,我的问题不是将图像转换为文本,而是图像的预处理。

超出任何帮助都将不胜感激! :)

import os;
os.chdir() # Put your own directory

import cv2 
import numpy as np

image = cv2.imread("Rent-Receipt.jpg", cv2.IMREAD_GRAYSCALE)

blurred = cv2.GaussianBlur(image, (5, 5), 0)

#blurred  = cv2.bilateralFilter(gray,9,75,75)

# apply Canny Edge Detection
edged = cv2.Canny(blurred, 0, 20)

#Find external contour

(_,contours, _) = cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
4个回答

21

关于您描述的第一步,一个很棒的教程可以在pyimagesearch上找到(他们总体上都有很棒的教程)。

简而言之,正如Ella所描述的那样,您需要使用cv2.CHAIN_APPROX_SIMPLE。稍微更为稳健的方法是使用cv2.RETR_LIST而不是cv2.RETR_EXTERNAL,然后对区域进行排序,这应该可以在白色背景或页面在背景中绘制更大形状时有效地工作等。

进入您问题的第二部分,一个很好的字符分割方法是使用OpenCV中提供的最大稳定极值区域提取器。最近我帮助的一个项目中提供了完整的CPP实现,您可以在这里找到。Python实现将按照以下方式进行(以下代码适用于OpenCV 3.0+。有关OpenCV 2.x语法,请在网上查找)。

import cv2

img = cv2.imread('test.jpg')
mser = cv2.MSER_create()

#Resize the image so that MSER can work better
img = cv2.resize(img, (img.shape[1]*2, img.shape[0]*2))

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
vis = img.copy()

regions = mser.detectRegions(gray)
hulls = [cv2.convexHull(p.reshape(-1, 1, 2)) for p in regions[0]]
cv2.polylines(vis, hulls, 1, (0,255,0)) 

cv2.namedWindow('img', 0)
cv2.imshow('img', vis)
while(cv2.waitKey()!=ord('q')):
    continue
cv2.destroyAllWindows()

这将生成以下输出

enter image description here

现在,为了消除假阳性,你可以简单地遍历hulls中的点,并计算周长(hulls [i]中所有相邻点之间的距离之和,其中hulls [i]是一个凸壳中所有点的列表)。如果周长太大,则将其分类为非字符。

图像上的对角线是因为图像边框是黑色而导致的。只需在读取图像后添加以下行即可去除它(在第7行下方)

img = img[5:-5,5:-5,:]

提供输出结果

在此输入图像描述


2
感谢@R. S. Nikhil Krishna!! 如果我使用您的代码来处理收据图像(非倾斜),我无法得到良好的分割结果。问题是,我应该调整哪些参数?凸包?提前致谢! - donpresente
@donpresente 我已经做出了修改。之前无法检测字符的原因是图片尺寸太小。MSER 需要字符之间有明显的间距。只需通过调整图片大小即可实现这一点。 - R. S. Nikhil Krishna
1
Nikhil Krishna。我想我们找到了赢家!:) 对于分割,您有其他建议吗?因为“手工制作”的模型可能需要单独划分每个字符,所以我应该在文本上强制使用网格吗? - donpresente
你所说的手工模型是指手动调整参数吗?而且网格可能会有些问题,因为字符大小不相等。 - R. S. Nikhil Krishna

7
在处理图像时,我首先需要提取扭曲图像的4个角点,这可以通过在寻找轮廓时使用cv2.CHAIN_APPROX_SIMPLE而不是cv2.CHAIN_APPROX_NONE来完成。然后,您可以使用cv2.approxPolyDP函数,希望保留收据的四个角(如果所有图像都像这个图像一样,那么它应该有效)。
现在,使用cv2.findHomographycv2.warpPerspective根据来源点和目标点进行图像矫正。其中,来源点是从扭曲图像中提取的4个点,目标点应形成一个矩形,例如完整的图像尺寸。
在这里,您可以找到代码示例和更多信息:OpenCV-Geometric Transformations of Images
此外,这个回答可能也有用:SO - Detect and fix text skew
注:已更正第二个链式逼近为cv2.CHAIN_APPROX_NONE

谢谢!然后你如何在矫正后的图像中分割文本? - donpresente
1
@donpresente 你说:"我的问题不在于图像转文字,而在于图像的预处理。" 不管怎样,我对OCR部分的贡献有限。 - Elia
预处理对我来说将包括图像分割。如果没有其他答案,我认为系统会向您发送50个点。问题是,如果您没有轮廓,您的解决方案如何工作? - donpresente
预处理通常不包括图像分割。在这种特定情况下,图像分割是主要的处理步骤。非常好的回答@Elia! - pzp

3

通过将前景中所需文本转换为黑色,同时将不需要的背景变为白色来预处理图像可以提高OCR准确性。此外,去除水平和垂直线条也可以改善结果。以下是去除不需要噪点(例如水平/垂直线)后的预处理后的图像。请注意已删除的边框和表格线。

enter image description here

import cv2

# Load in image, convert to grayscale, and threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Find and remove horizontal lines
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (35,2))
detect_horizontal = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=2)
cnts = cv2.findContours(detect_horizontal, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(thresh, [c], -1, (0,0,0), 3)

# Find and remove vertical lines
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,35))
detect_vertical = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=2)
cnts = cv2.findContours(detect_vertical, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    cv2.drawContours(thresh, [c], -1, (0,0,0), 3)

# Mask out unwanted areas for result
result = cv2.bitwise_and(image,image,mask=thresh)
result[thresh==0] = (255,255,255)

cv2.imshow('thresh', thresh)
cv2.imshow('result', result)
cv2.waitKey()

2

尝试使用笔画宽度转换技术(Stroke Width Transform)。Python 3实现该算法的代码可以在SWTloc上找到。

编辑:从v2.0.0版本开始

安装库

pip install swtloc

转换图像

import swtloc as swt

imgpath = 'images/path_to_image.jpeg'
swtl = swt.SWTLocalizer(image_paths=imgpath)
swtImgObj = swtl.swtimages[0]
# Perform SWT Transformation with numba engine
swt_mat = swtImgObj.transformImage(text_mode='lb_df', gaussian_blurr=False, 
                                   minimum_stroke_width=3, maximum_stroke_width=12,
                                   maximum_angle_deviation=np.pi/2)

在此输入图片描述


本地化字母

localized_letters = swtImgObj.localizeLetters(minimum_pixels_per_cc=10,
                                              localize_by='min_bbox')

在这里输入图片描述


本地化单词

localized_words =  swtImgObj.localizeWords(localize_by='bbox')

enter image description here


.transformImage.localizeLetters.localizeWords 函数中有多个参数,您可以尝试调整这些参数以获得所需的结果。

完全披露:我是这个库的作者


我喜欢这个库,非常有趣的东西,谢谢分享! - Yufrend

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