OpenCV实际案例(四) 答题卡识别判卷

一、目标

扫描如图所示的答题卡,找到其中选的答案,与正确答案对比,给出分数。
在这里插入图片描述

二、分步实现

1、读取数据,进行透视变换

  • 数据预处理:高斯滤波、二值化、边缘检测、识别轮廓
#对输入图像进行预处理
image = cv2.imread(args["image"])
contours_img = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray,(5,5),0)
cv_show('blurred',blurred)
edges = cv2.Canny(blurred,75,200)#边缘检测
cv_show("edges",edges)

#轮廓检测
cnts = cv2.findContours(edges.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)
cv_show("contours_img",contours_img)
docCnt = None

高斯滤波结果
在这里插入图片描述
二值化后进行边缘检测
在这里插入图片描述
轮廓提取
在这里插入图片描述

  1. 透视变换
    通常情况下,我们要在检测出的轮廓中寻找我们想要的部分(这里就是答题卡部分)。检测的方法就是返回周长或者是面积最大的轮廓,然后将轮廓求近似矩形,将矩形的四个点的坐标传入函数中求变换矩阵,进行透视变换
#对检测到的轮廓进行筛选,为透视变化做准备
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 #找到了做透视变换的四个坐标了
#执行透视变换
warped = four_point_transform(gray,docCnt.reshape(4,2))
cv_show("warped",warped)

透视变换函数

def four_point_transform(image,pts):
    #获取坐标点
    rect = order_points(pts)
    (tl,tr,br,bl) = rect
    widthA = np.sqrt(((tl[0] - tr[0]) ** 2) + ((tl[1] - tr[1]) ** 2))
    widthB = np.sqrt(((bl[0] - br[0]) ** 2) + ((bl[1] - br[1]) ** 2))
    maxwidth = max(int(widthB),int(widthA))
    heightA = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
    heightB = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
    maxheight = max(int(heightB),int(heightA))

    #确定变换后坐标的位置
    dst = np.array([
        [0, 0],
        [maxwidth - 1, 0],
        [maxwidth - 1, maxheight - 1],
        [0, maxheight - 1]], dtype="float32")
    #计算变换矩阵
    M = cv2.getPerspectiveTransform(rect,dst)
    warped = cv2.warpPerspective(image,M,(maxwidth,maxheight))
    return warped

获取坐标点的函数

def order_points(pts):
#根据位置信息定位四个坐标点的位置
    rect = np.zeros((4,2),dtype="float32")
    s = pts.sum(axis=1)
    rect[0] = pts[np.argmin(s)]
    rect[2] = pts[np.argmax(s)]
    diff = np.diff(pts,axis=1)
    rect[1] = pts[np.argmin(diff)]
    rect[3] = pts[np.argmax(diff)]
    return rect

透视变换结果
在这里插入图片描述

  1. 我们要在透视变换的结果中找到每个选项的位置,从中找到每个题选择的答案。
    给透视变化后的图形做自适应阈值的二值化,然后筛选出25个选项的轮廓
    方法:检测出轮廓后画出近似矩形,通过判断近似矩形的大小,筛选出选项的轮廓;对选出的轮廓进行从上到下的排序,这样每一道题的五个选项的轮廓就排在一起了。
cnts = cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
cv2.drawContours(thresh_counters,cnts,-1,(0,0,255),3)
cv_show('thresh_counters',thresh_counters)
questionCnts = []
for c in cnts:
    (x,y,w,h) = cv2.boundingRect(c)
    ar = w/float(h)
    #设定标准
    if w>=20 and h>=20 and ar>=0.9 and ar<=1.1:
        questionCnts.append(c)
#将这些轮廓(每一个圆形的轮廓)按照从上到下进行排序
questionCnts = sort_contours(questionCnts,
	method="top-to-bottom")[0]

透视变换结果的二值化结果
在这里插入图片描述
找到上图中的轮廓,画出
在这里插入图片描述

给轮廓排序的函数

def sort_contours(cnts,method="left-to-right"):
    reverse = False
    i = 0
    if method == "right-to-left" or method == "bottom-to-top":
        reverse = True
    if method == "top-to-bottom" or method == "bottom-to-top":
        i = 1
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))
    return cnts,boundingBoxes
  1. 给每个题的五个选项的轮廓从左到右排序,制作每个选项的黑白掩模,与二值化后的图像做与操作,检测每个结果中白色像素点的数量,数量大的就是选择的选项,记录下来与正确选项对比,计算正确的个数。
for (q,i) in enumerate(np.arange(0,len(questionCnts),5)):
    cnts = sort_contours(questionCnts[i:i+5])[0]
    bubble = None
    for (j,c) in enumerate(cnts):
        mask = np.zeros(thresh.shape,dtype="uint8")
        cv2.drawContours(mask,[c],-1,255,-1)
        cv_show('mask',mask)
        #通过计算非零像素点的数量来计算是否选择这个答案
        mask = cv2.bitwise_and(thresh,thresh,mask=mask)
        #通过与操作,将答题卡上的涂答案的那一个圈(图像中这里是全黑的)全部变成白色
        cv_show('mask',mask)
        total = cv2.countNonZero(mask)
        if bubble is None or total>bubble[0]:
            bubble = (total,j)
    color = (0,0,255)
    k = ANSWER_KEY[q] #q代表现在检查的是第q个题
    #k是正确答案
    if k == bubble[1]:
        color = (0,255,0)
        correct += 1
    cv2.drawContours(warped,[cnts[k]],-1,color,3)

选项掩模的图像
在这里插入图片描述

  1. 计算出结果,可视化输出
score = (correct/5.0) * 100
print("[INFO] score:{:.2f}%".format(score)) #这个%只是为了显示为60%,没有其他意思
cv2.putText(warped,"{:.2f}%".format(score),(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.9,3)
cv_show("Original",image)
cv_show("Exam",warped)

在这里插入图片描述

四、收获

  1. 边缘检测后的图像进行轮廓检测的结果更好。
  2. zip与zip(星号)
  • zip(x,y)是将x中的元素与y中的元素一一对应,形成一个一个的元组(x1,y1),(x2,y2)…
  • zip(*)相当于解压,是zip的逆过程
a = [1,3,5,7,9]
b = [11,33,55,77,99]

c = list(zip(a,b))
print(c)
# list
d, e = zip(*c)  #zip(*)相当于解压,是zip的逆过程
# print(type(e))
print(d)
print(e)
# 打印结果:
# [(1, 11), (3, 33), (5, 55), (7, 77), (9, 99)]
# <class 'tuple'>
# (1, 3, 5, 7, 9) 
# (11, 33, 55, 77, 99)
  1. np.arange()
  2. 函数返回一个有终点和起点的固定步长的排列,如[1,2,3,4,5],起点是1,终点是6,步长为1。
  • 参数个数情况: np.arange()函数分为一个参数,两个参数,三个参数三种情况
  • 1)一个参数时,参数值为终点,起点取默认值0,步长取默认值1。
  • 2)两个参数时,第一个参数为起点,第二个参数为终点,步长取默认值1。
  • 3)三个参数时,第一个参数为起点,第二个参数为终点,第三个参数为步长。其中步长支持小数
  1. "{:.2f}”和“%.2f"的区别:
  • 一种是表达式:‘%.2f’ % num,在Python2.x和3.x都可用
  • 另一种是字符串对象的方法:‘{0:.2f}’.format(num),仅Python3.x可用
correct=3
score = (correct/5.0) * 100
print("[INFO] score:{:.2f}%".format(score))
>>>[INFO] score:60.00%
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
一、主要内容:OpenCV能够实现强大丰富的图像处理,但是它缺少一个能够支持它运行的界面。Csharp经过多年的发展,得益于它的“所见及所得”能力,非常方便编写界面。这两者如果能够“双剑合璧”,将有效帮助实际工作产出。本课着重推荐GOCW采用“Csharp基于CLR直接调用Opencv编写的算法库”方法,能够将最新的OpenCV技术引入进来,同时保证生成程序的最小化。    为了进一步说明Csharp和OpenCV的结合使用,首先一个较为完整的基于winform实现识别的例子,相比较之前的实现,本次进一步贴近生产实际、内涵丰富,对算法也进行了进一步提炼。同时我们对WPF下对OpenCV函数的调用、OpenCV.js的调用进行相关教授。       二、课程结构1、 EmguCV、OpenCVSharp和GOCW之间进行比较(方便代码编写、能够融入最新的算法、速度有保障、方便调试找错、拒绝黑箱化);2、视频采集模块的构建,视频采集和图像处理之间的关系;3、视频采集专用的SDK和“陪练”系统的介绍;4、在视频增强类项目中和图像处理项目中,算法的选择;5、Csharp界面设计、图片的存储和其他构建设计;6、较为完整的识别例子,兼顾界面设计和算法分析;8、WPF基于GOCW也同样可以基于GOCW实现算法调用;webForm虽然也可以通过类似方法调用,但是OpenCV.JS的方法更现代高效。9、关于软件部署的相关要点和窍门。       三、知识要点:1、基本环境构建和程序框架;2、CLR基本原理和应用方法;3、接入、采集、模拟输入;4、图像处理,通过构建循环采集图片;5、增强和实时处理;6、基于投影等技术的识别算法;7、存储、转换;8、部署交付。        课程能够帮助你掌握Csharp调用Opencv的基本方法,获得相应框架代码和指导;从而进一步提升实现“基于图像处理”的解决方案能力。  
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值