Python + OpenCV:OCR 图像分割

Python + OpenCV: OCR Image Segmentation

我正在尝试根据收据的这个玩具示例进行 OCR。使用 Python 2.7 和 OpenCV 3.1.

灰度 + 模糊 + 外部边缘检测 + 收据中每个区域的分割(例如 "Category" 稍后查看哪个标记 - 在本例中为现金 -)。

当图像 "skewed" 能够正确转换然后 "automatically" 分割收据的每一段时,我觉得很复杂。

示例:

有什么建议吗?

下面的代码是在边缘检测之前获取的示例,但是当收据像第一张图像时。我的问题不是图像到文本。是图像的预处理。

非常感谢任何帮助! :)

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个角。这是通过在查找轮廓时使用 cv2.CHAIN_APPROX_SIMPLE 而不是 cv2.CHAIN_APPROX_NONE 来完成的。之后,您可以使用 cv2.approxPolyDP 并希望保留收据的 4 个角(如果您所有的图像都像这样,那么没有理由不工作)。

现在使用cv2.findHomographycv2.wardPerspective根据源点校正图像,源点是从倾斜图像中提取的4个点和应该形成矩形的目标点,例如完整图像尺寸。

您可以在此处找到代码示例和更多信息: OpenCV-Geometric Transformations of Images

这个答案也可能有用 - SO - Detect and fix text skew

编辑:将第二条链更正为 cv2.CHAIN_APPROX_NONE

有关您描述的第一步的精彩教程可在 pyimagesearch 获得(他们一般都有很棒的教程)

简而言之,正如 Ella 所描述的,您将不得不使用 cv2.CHAIN_APPROX_SIMPLE。一种稍微更稳健的方法是使用 cv2.RETR_LIST 而不是 cv2.RETR_EXTERNAL 然后对区域进行排序,因为即使在白色 backgrounds/if 页面中也应该可以正常工作 backgrounds/if 页面在背景中刻有更大的形状,等等

来到问题的第二部分,分割字符的一个好方法是使用 OpenCV 中提供的最大稳定极值区域提取器。在我最近帮助的一个项目中,可以使用 CPP 的完整实现 here。 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()

这给出了输出

现在,为了消除误报,您可以简单地循环遍历 hulls 中的点,并计算周长(hulls[i] 中所有相邻点之间的距离总和,其中 hulls[i] 是一个列表所有点都在一个凸包中)。如果周长太大,则将其归类为不是字符。

由于图像的边框是黑色的,所以出现了横跨图像的诊断线。可以通过在读取图像后立即添加以下行(第 7 行下方)

来简单地删除它
img = img[5:-5,5:-5,:]

给出输出

通过将前景中的所需文本转换为黑色同时将不需要的背景转换为白色来预处理图像有助于提高 OCR 准确性。此外,删除水平和垂直线可以改善结果。这是去除不需要的噪声(例如 horizontal/vertical 行)后的预处理图像。注意删除的边框和 table 行

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()

尝试使用笔画宽度变换。 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')


.transformImage.localizeLetters.localizeWords 函数中有多个参数,您可以随意使用这些参数以获得所需的结果。

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