使用OpenCV Python检测表单中的复选框

4

给定一份牙科表格作为输入,需要使用图像处理找到表格中所有存在的复选框。我在下面回答了我的当前方法。是否有更好的方法可以找到低质量文档中的复选框?

示例输入:

掩码输入图像

2个回答

11

这是一种解决问题的方法,

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

horizontal

寻找竖直线

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

final binary

对二进制图像应用连通组件分析以获取所需的块。

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)

最终图片


非常好的解决方案! - stateMachine
嗨@Sreekiran,感谢您的解决方案,它为我节省了一个小时,但是在检测到这些框之后,我该如何将它们构建成一行? - Steven Dang
我们如何修改这个解决方案来检查单选按钮?我尝试使用轮廓并检查周长和面积来区分,但它总是只给出复选框,希望能得到一些建议。 - undefined

1
你可以使用轮廓线来解决这个问题。
# 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标志在图像中查找轮廓,以获取具有父子关系的所有轮廓。 有关更多信息,请参见此处
Contours, Hierarchy = cv2.findContours(Thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

现在所有的框和图像中的所有字母都被检测到了。我们需要消除检测到的字母,非常小的轮廓(由于噪音),以及那些包含较小框的框。
为此,我正在运行一个for循环迭代所有检测到的轮廓,并使用此循环将每个轮廓的3个值保存在3个不同的列表中。
  • 第一个值:轮廓的面积(轮廓包围的像素数)
  • 第二个值:轮廓的边界矩形信息。
  • 第三个值:轮廓面积与其边界矩形面积的比率。
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]

最终输出图像


尝试运行我们的代码但出现以下提示:C_Image = np.zeros(Gray.shape, dtype=np.uint8) NameError: name 'Gray'未被定义 - Chau Loi
@Chau,只需要将Gray.shape更改为Thresh.shape即可。我在输入答案时犯了一个错误。抱歉。 - Rahul Kedia

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