使用 opencv python 从表单中检测复选框

detect checkboxes from a form using opencv python

给定一个牙科表格作为输入,需要使用图像处理找到表格中存在的所有复选框。我已经在下面回答了我目前的做法。有没有更好的方法来找到低质量文档的复选框?

示例输入:

这是我们可以解决问题的一种方法,

import cv2
import numpy as np
image=cv2.imread('path/to/image.jpg')

### binarising image
gray_scale=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
th1,img_bin = cv2.threshold(gray_scale,150,225,cv2.THRESH_BINARY)

定义垂直和水平内核

lineWidth = 7
lineMinWidth = 55
kernal1 = np.ones((lineWidth,lineWidth), np.uint8)
kernal1h = np.ones((1,lineWidth), np.uint8)
kernal1v = np.ones((lineWidth,1), np.uint8)

kernal6 = np.ones((lineMinWidth,lineMinWidth), np.uint8)
kernal6h = np.ones((1,lineMinWidth), np.uint8)
kernal6v = np.ones((lineMinWidth,1), np.uint8)

检测水平线

img_bin_h = cv2.morphologyEx(~img_bin, cv2.MORPH_CLOSE, kernal1h) # bridge small gap in horizonntal lines
img_bin_h = cv2.morphologyEx(img_bin_h, cv2.MORPH_OPEN, kernal6h) # kep ony horiz lines by eroding everything else in hor direction

寻找垂直线

## detect vert lines
img_bin_v = cv2.morphologyEx(~img_bin, cv2.MORPH_CLOSE, kernal1v)  # bridge small gap in vert lines
img_bin_v = cv2.morphologyEx(img_bin_v, cv2.MORPH_OPEN, kernal6v)# kep ony vert lines by eroding everything else in vert direction

合并垂直线和水平线以获得块。添加一层膨胀以去除小间隙

### function to fix image as binary
def fix(img):
    img[img>127]=255
    img[img<127]=0
    return img

img_bin_final = fix(fix(img_bin_h)|fix(img_bin_v))

finalKernel = np.ones((5,5), np.uint8)
img_bin_final=cv2.dilate(img_bin_final,finalKernel,iterations=1)

对二值图像应用连通分量分析以获得所需的块。

ret, labels, stats,centroids = cv2.connectedComponentsWithStats(~img_bin_final, connectivity=8, ltype=cv2.CV_32S)

### skipping first two stats as background
for x,y,w,h,area in stats[2:]:
    cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),2)

你也可以用contours来解决这个问题。

# Reading the image in grayscale and thresholding it
Image = cv2.imread("findBox.jpg", 0)
ret, Thresh = cv2.threshold(Image, 100, 255, cv2.THRESH_BINARY)

现在执行膨胀和腐蚀两次以连接方框内的虚线。

kernel = np.ones((3, 3), dtype=np.uint8)
Thresh = cv2.dilate(Thresh, kernel, iterations=2)
Thresh = cv2.erode(Thresh, kernel, iterations=2)

在带有cv2.RETR_TREE标志的图像中查找轮廓,以获取具有父子关系的所有轮廓。 For more info on this.

Contours, Hierarchy = cv2.findContours(Thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

现在检测到图像中的所有框以及所有字母。我们必须消除检测到的字母、非常小的轮廓(由于噪声),以及那些内部包含较小框的框。

为此,我 运行 一个 for 循环迭代所有检测到的轮廓,并使用这个循环我为 3 个不同列表中的每个轮廓保存 3 个值。

  • 第一个值:轮廓面积(轮廓包围的像素数)
  • 第二个值:Contour 的 bounding rectangle 信息。
  • 第三个值:等高线面积与其外接矩形面积之比。
Areas = []
Rects = []
Ratios = []
for Contour in Contours:
    # Getting bounding rectangle
    Rect = cv2.boundingRect(Contour)

    # Drawing contour on new image and finding number of white pixels for contour area
    C_Image = np.zeros(Thresh.shape, dtype=np.uint8)
    cv2.drawContours(C_Image, [Contour], -1, 255, -1)
    ContourArea = np.sum(C_Image == 255)

    # Area of the bounding rectangle
    Rect_Area = Rect[2]*Rect[3]
    
    # Calculating ratio as explained above
    Ratio = ContourArea / Rect_Area
   
    # Storing data
    Areas.append(ContourArea)
    Rects.append(Rect)
    Ratios.append(Ratio)

过滤掉不需要的轮廓:

  • 获取面积小于 3600(此图像的阈值)且比率 >= 0.99 的等高线的索引。 该比率定义轮廓与其边界矩形的重叠范围。在这种情况下,所需的轮廓是矩形的,它们的这个比率预计为“1.0”(0.99 用于保持小噪声的阈值)。
BoxesIndices = [i for i in range(len(Contours)) if Ratios[i] >= 0.99 and Areas[i] > 3600]
  • 现在最终轮廓是那些在索引“BoxesIndices”处没有子轮廓的轮廓(这将提取最里面的轮廓),如果它们有子轮廓,那么这个子轮廓不应该是轮廓之一在索引“BoxesIndices”处。
FinalBoxes = [Rects[i] for i in BoxesIndices if Hierarchy[0][i][2] == -1 or BoxesIndices.count(Hierarchy[0][i][2]) == 0]

Final output image