【问题标题】:Python + OpenCV: OCR Image SegmentationPython + OpenCV:OCR 图像分割
【发布时间】:2017-03-19 13:05:47
【问题描述】:

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

灰度 + 模糊 + 外部边缘检测 + 收据中每个区域的分割(例如“类别”以便稍后查看标记了哪一个 - 在本例中为现金 -)。

当图像被“倾斜”以能够正确转换然后“自动”分割收据的每个部分时,我发现这很复杂。

例子:

有什么建议吗?

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

任何帮助都非常感谢! :)

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)

【问题讨论】:

    标签: python opencv image-processing computer-vision


    【解决方案1】:

    pyimagesearch 上提供了有关您描述的第一步的精彩教程(他们通常都有很棒的教程)

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

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

    这给出了输出

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

    图像上的诊断线即将出现,因为图像的边框是黑色的。可以通过在读取图像后立即添加以下行来简单地删除(第 7 行以下)

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

    给出输出

    【讨论】:

    • 谢谢@R。 S.尼基尔克里希纳!!如果我将您的代码用于收据的图像(未倾斜)(请参见问题上方)。我没有得到很好的分割。题。我应该调整哪些参数?凸包?提前致谢!
    • @donpresente 我已经进行了更改。它没有检测到字符的原因是图像尺寸太小。 MSER 要求字符之间有很大的间距。这可以通过简单地调整图像大小来实现
    • 尼基尔克里希纳。我认为我们有一个赢家! :) 关于细分的任何其他建议?因为“手工”模型可能需要单独划分每个字符,对吗?我应该在文本上强制使用网格吗?
    • 手工模型是指手动调参吗?网格可能会有点问题,因为字符大小不相等。
    【解决方案2】:

    我头上的选项需要提取倾斜图像的 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

    【讨论】:

    • 谢谢!然后如何分割 rectify 图像中的文本? (这是问题的一部分)
    • @donpresente 您写道“我的问题不是图像到文本。是图像的预处理。”无论如何,我无法在 OCR 部分做出太多贡献。
    • 对我来说预处理将包括图像分割。如果没有其他答案,我认为系统会向您发送 50 分。问题,如果你没有轮廓,你的解决方案是如何工作的?
    • 预处理一般不包括图像分割。在这种特定情况下,图像分割是主要的处理步骤。很好的答案@Elia!
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      尝试使用笔画宽度变换。该算法的 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 函数中有多个参数,您可以使用它们来获得所需的结果。

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

      【讨论】:

      • 我喜欢这个图书馆,有趣的东西,谢谢分享!