项目实战—答题卡识别判卷
本次我们将进行另一个实战项目,从本质上来讲,其与之前的卡片的数字识别基本上原理是一样的。
本次我们将使用的实验图片:
实现答题卡识别的7步
Step #1: 检测到图片中的答题卡
Step #2: 应用****变换来提取图中的答题卡(以自上向下的鸟瞰视图)
Step #3: 从****变换后的答题卡中提取 the set of 气泡/圆点 (答案选项)
Step #4: 将题目/气泡排序成行
Step #5: 判断每行中被标记/涂的答案
Step #6: 在我们的答案字典中查找正确的答案来判断答题是否正确
Step #7: 为其它题目重复上述操作
算法实现
让我们新建一个Python文件,然后添加以下内容:
# 引入必要的库
from imutils.perspective import four_point_transform from imutils import contours import numpy as np import argparse import imutils import cv2 # 构建命令行参数解析并分析参数 # 对应使用方式 python test_grader.py --image images/test_01.png ap = argparse.ArgumentParser() ap.add_argument("-i", "--image", required=True, help="path to the input image") args = vars(ap.parse_args()) # 构建答案字典,键为题目号,值为正确答案 ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1} 补充:vars()接受一个对象返回它的内建字典: 当然我们需要有OpenCV和Numpy的包,一个便于基本图像处理操作的库。使用下面的命令来安装和升级该库: pip install --upgrade imutils 补充:在Windows10_64位+ Python 3.7测试,对于OpenCV的安装有两种方法,推荐第一种方法。(由于是python3,使用的是OpenCV 4.2.0) 我们在命令行中只解析了一个参数,那就是要分析的图片的路径。然后定义了答案字典,在这里,题目对应的值是正确答案在行中的索引位置,跟python中列表的索引方式相同。 # 加载图片,将它转换为灰阶,轻度模糊,然后边缘检测。 image = cv2.imread(args["image"]) gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5, 5), 0) edged = cv2.Canny(blurred, 75, 200)
先来看滤波之后的图像:
我们先从磁盘中加载了图片文件,然后将它转换为灰阶,再进行模糊处理来消除高频噪声。最后,使用Canny边缘检测器来获取答题卡的边缘。结果如下 :
注意,答题卡长方形的四个顶点都要在图中出现,这是我们事先约定的答题卡的边缘。
获取轮廓非常重要,因为下一步我们将它作为应用****变换的标记(锚点),来获得一个答题卡的自上而下的鸟瞰视图。
而类似的情况,我们在之前的文档OCR扫描也用过,就是采用****变换进行图片的校正,它对于之后的图像处理十分的重要。
# 从边缘图中寻找轮廓,然后初始化答题卡对应的轮廓 cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = cnts[0] if imutils.is_cv2() else cnts[1] docCnt = None # 确保至少有一个轮廓被找到 if len(cnts) > 0: # 将轮廓按大小降序排序 cnts = sorted(cnts, key=cv2.contourArea, reverse=True) # 对排序后的轮廓循环处理 for c in cnts: # 获取近似的轮廓 peri = cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, 0.02 * peri, True) # 如果我们的近似轮廓有四个顶点,那么就认为找到了答题卡 if len(approx) == 4: docCnt = approx break
首先我们通过cv2.findContours从边缘检测的结果更进一步得到轮廓值。然后我们对轮廓的区域大小进行排序,在这里我们假设答题卡就是我们图像的焦点,它会比图中其它对象大,所以从大到小对轮廓进行检测,符合长方形特征的就是我们的答题卡了。
此外,对于每个轮廓,我们进行了近似,这在本质上意味着我们简化了轮廓点的数量,使其成为一个“更基本的”几何形状。
现在,如果边缘轮廓在原始图像中画出来它将是这样的:
下次我们将进行****变换,继而进行下一步的处理。