如何使用OpenCV检测/查找复选框轮廓

shi*_*vam 7 python opencv image-processing computer-vision opencv-contour

我有几张图像需要通过使用计算机视觉检测复选框来进行OMR。

我正在使用findContours仅在扫描文档中的复选框上绘制轮廓。但是该算法提取文本的每个轮廓。

from imutils.perspective import four_point_transform
from imutils import contours
import numpy as np
import argparse, imutils, cv2, matplotlib
import matplotlib.pyplot as plt
import matplotlib.image as mpimg

image = cv2.imread("1.jpg")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 75, 200)

im_test = [blurred, cv2.GaussianBlur(gray, (7, 7), 0), cv2.GaussianBlur(gray, (5, 5), 5), cv2.GaussianBlur(gray, (11, 11), 0)]
im_thresh = [ cv2.threshold(i, 127, 255, 0)  for i in im_test ]
im_thresh_0 = [i[1] for i in im_thresh ]
im_cnt = [cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)[0] for thresh in im_thresh_0]

im_drawn = [cv2.drawContours(image.copy(), contours, -1, (0,255,0), 1) for contours in im_cnt]

plt.imshow(im_drawn[0])
plt.show()

Run Code Online (Sandbox Code Playgroud)

输入图片: 在此处输入图片说明

nat*_*ncy 9

由于我们只想检测复选框,因此想法是使用两种过滤方法将所需的框与单词隔离。在预处理并找到轮廓之后,我们可以遍历每个轮廓并应用过滤器。我们使用cv2.contourArea()最小和最大阈值级别,然后使用计算纵横比,cv2.approxPolyDP()因为正方形的纵横比接近 1。

为了检测图像中的边缘,我们可以使用cv2.Canny()然后抓取轮廓,使用cv2.findContours 它产生这个图像。请注意如何检测包括单词和复选框在内的所有轮廓。

在此处输入图片说明

接下来,我们使用阈值面积和纵横比遍历每个检测到的轮廓和过滤器。使用此方法,检测到所有 52 个复选框。

在此处输入图片说明

输出

('checkbox_contours', 52)

为了防止潜在的误报,我们可以添加第三个过滤器以确保每个轮廓有四个点(它是正方形的可能性更高)。如果输入图像来自一个角度,我们可以使用四点变换作为预处理步骤来获得图像的鸟瞰图。

另一个输入图像集

输出

('checkbox_contours', 2)

代码

import numpy as np
import imutils, cv2

original_image = cv2.imread("1.jpg")
image = original_image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 120, 255, 1)

cv2.imshow("edged", edged)

cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = imutils.grab_contours(cnts)

checkbox_contours = []

threshold_max_area = 250
threshold_min_area = 200
contour_image = edged.copy()

for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.035 * peri, True)
    (x, y, w, h) = cv2.boundingRect(approx)
    aspect_ratio = w / float(h)
    area = cv2.contourArea(c) 
    if area < threshold_max_area and area > threshold_min_area and (aspect_ratio >= 0.9 and aspect_ratio <= 1.1):
        cv2.drawContours(original_image,[c], 0, (0,255,0), 3)
        checkbox_contours.append(c)

print('checkbox_contours', len(checkbox_contours))
cv2.imshow("checkboxes", original_image)
cv2.waitKey(0)
Run Code Online (Sandbox Code Playgroud)

编辑:

回到这个问题之后,这里有一个更强大的解决方案。除了我们使用Otsu 阈值而不是 Canny 边缘检测来获得二值图像外,这个想法非常相似。Otsu 的阈值会自动计算阈值,因此应该会给出更好的结果。从这里我们找到轮廓,使用轮廓近似、纵横比和轮廓区域进行过滤。结果应该是一样的。

import cv2

# Load image, convert to grayscale, Otsu's 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 contours, filter using contour approximation, aspect ratio, and contour area
threshold_max_area = 550
threshold_min_area = 100
cnts = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.035 * peri, True)
    x,y,w,h = cv2.boundingRect(approx)
    aspect_ratio = w / float(h)
    area = cv2.contourArea(c) 
    if len(approx) == 4 and area < threshold_max_area and area > threshold_min_area and (aspect_ratio >= 0.9 and aspect_ratio <= 1.1):
        cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 2)

cv2.imshow("image", image)
cv2.imshow("thresh", thresh)
cv2.waitKey()
Run Code Online (Sandbox Code Playgroud)