实战三、OpenCv答题卡识别判卷

import cv2
import numpy as np

ANSWER_KEY={0:1,1:4,2:0,3:3,4:1}

# 获取坐标点
def order_points(pts):
    # 一共4个坐标点
    rect = np.zeros((4, 2), dtype="float32")

    # 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
    # 计算左上,右下
    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


def four_point_transform(image, pts):#pts是原始图像的四个坐标点
    # 获取输入坐标点
    rect = order_points(pts)
    (tl, tr, br, bl) = rect

    #计算输入的w和h值
    widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))#根号下(x3-x4)^2+(y3-y4)^2
    widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))#根号下(x2-x1)^2+(y2-y1)^2
    maxWidth = max(int(widthA), int(widthB))#取得两个中的最大值,作为目标图像的宽

    heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))#根号下(x4-x1)^2+(y4-y1)^2
    heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))#根号下(x3-x2)^2+(y3-y2)^2
    maxHeight = max(int(heightA), int(heightB))#取得两个中的最大值,作为目标图像的高

    # 变换后对应坐标位置:目标图像中对应的四个点
    dst = np.array([
        [0, 0],#目标图像中的tl坐标
        [maxWidth - 1, 0],#目标图像中tr坐标
        [maxWidth - 1, maxHeight - 1],#目标图像的br坐标
        [0, maxHeight - 1]], dtype="float32")#目标图像的bl坐标

    # 计算变换矩阵
    M = cv2.getPerspectiveTransform(rect, dst)  #通过输入和输出的四个坐标,就可以计算出变换矩阵
    warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))#dsize是目标图像的大小
    # 返回变换后结果
    return warped

def sort_contours(contours,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

    # 把找到的形状用最小的矩形包起来,外接矩形,x,y,h,w
    boundingBoxes = [cv2.boundingRect(c) for c in contours]
    # 直接用x来判断出来轮廓的排列顺序
    (contours, boundingBoxes) = zip(*sorted(zip(contours, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))

    return contours, boundingBoxes


#图像的预处理操作
image=cv2.imread("/Users/macbook/Desktop/project3.png")#读入原始图像
contours_img=image.copy()
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)#将BGR图像转换成灰度图像
blurred=cv2.GaussianBlur(gray,(5,5),0)#对灰度图像进行高斯滤波操作,去除噪点
edged=cv2.Canny(blurred,75,200)#对滤波后进行边缘检测操作
cv2.imshow("edged",edged)#显示边缘检测后的图像

#检测外轮廓
cnts,hierarchy=cv2.findContours(edged.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)#在原始图像上用红色画笔画出轮廓

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)#c表示输入的点集,True表示闭合
        if(len(approx)==4):#表示检测到了矩形的轮廓,直接退出循环即可
            docCnt=approx
            break

#透视变换
warped=four_point_transform(gray,docCnt.reshape(4,2))
thresh=cv2.threshold(warped,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]#自定义阈值,二值化操作
cv2.imshow("thresh",thresh)#显示二值化图像处理的结果
# 学生涂的区域白色部分比较多,就是非0区域面积比较大,没涂的部分非0区域比较少

thresh_Contours=thresh.copy()

#找到每一个圆圈选项的轮廓:对应有各自的掩码图像
Cnts,hierarchy=cv2.findContours(thresh.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(thresh_Contours,Cnts,-1,(0,0,255),3)
cv2.imshow("thresh_Contours",thresh_Contours)

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]

correct=0#表示正确的题目数量

for(q,i) in enumerate(np.arange(0,len(questionCnts),5)):#每道题目有五个选项:5
    cnts=sort_contours(questionCnts[i:i+5])[0]
    bubbled=None
    for(j,c)in enumerate (cnts):
        mask = np.zeros(thresh.shape, dtype='uint8')#作出与二进制图像大小相同的掩码图像
        cv2.drawContours(mask, [c], -1, 255, -1)#在掩码图像上对轮廓所处的区域用白色填充好
        cv2.imshow("mask2",mask)#展示此时的掩码图像
        mask=cv2.bitwise_and(thresh,thresh,mask=mask)#进行与操作,掩码白色区域的thresh图像保留下来,其他变成黑色
        cv2.imshow("mask3",mask)#展示此时的掩码图像
        total=cv2.countNonZero(mask)#计算掩码区域非零的值,涂黑区域的非零值会更多

        if bubbled is None or total>bubbled[0]:
            #表示是每一题的第一个选项或者现在的非零区域更多,就把bubbled的值换成目前的total值
            bubbled=(total,j)#j表示q题学生填写答案的列数

    #对比正确答案:
    color=(0,0,255)
    k=ANSWER_KEY[q]#q代表题数,从0开始,ANSWER_KEY[k]就是q题目对应的正确答案的列数

    if k==bubbled[1]:#表示填写答案与正确答案相同
        color=(0,255,0)#绿色表示正确
        correct+=1

    #绘图
    cv2.drawContours(warped,[cnts[k]],-1,color,3)

#计算正确率
score=(correct/5.0)*100
print("[INFO] score: {:.2f}%".format(score))
cv2.putText(warped, "{:.2f}%".format(score), (10, 30),cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
result=cv2.cvtColor(warped,cv2.COLOR_GRAY2BGR)
cv2.imshow("result",result)

cv2.waitKey(0)
cv2.destroyAllWindows()

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值