Python OpenCV实现答题卡识别与自动评分

Aria
Aria
发布时间:2026-10-14 | 4人浏览过
原创

本文详细介绍如何利用Python和OpenCV框架实现答题卡填涂区域的自动识别与评分。通过灰度化、透视变换及轮廓检测提取选项坐标,结合X/Y坐标排序还原题目顺序,最终对比标准答案计算得分。

答题卡识别与评分原理概述

大家好,这里是滑稽研究所。在之前的图像处理系列中,我们已经利用OpenCV框架成功实现了答题卡填涂区域的识别功能。近期收到后台读者的反馈,希望进一步完善功能,实现选项对错判断及自动打分。本期内容将重点讲解这一功能的实现过程。回顾往期代码原理,处理流程包括:对素材进行灰度化、透视变换、轮廓检测、腐蚀膨胀处理、区域分割、边框计算及区域计算。核心逻辑是通过像素面积过滤、填涂区域优化以及获取选项坐标来完成答题卡的初步识别。素材:

在获取到答题卡的填涂区域后,后续处理相对简单。首先对答题卡进行分隔以去除干扰项,并为不同区域打上标签。由于答题卡题号是自上而下排列的,我们利用获取到的填涂项横坐标(x坐标)进行区分。选项A至E必然占据五个不同的区域,通过已打好的标签,我们可以将像素坐标转换为具有实际意义的选项标识。接下来使用if判断语句进行逻辑处理,此时填涂项已具备明确的选项意义。纵坐标(y坐标)同样关键,虽然前一步确定了选项,但未进行排序的选项是无意义的。鉴于题号顺序自上而下,我们在遍历选项时同时获取x和y坐标,确保坐标配对。将横纵坐标分别存入两个列表,使用zip方法合并后,依据列表第二个元素(即纵坐标)进行从小到大排序,从而得到正确的题目顺序。至此,我们真正获取了考生填涂的选项顺序。新建一个存放正确答案的列表,与考生答案进行对比,计算正确率并给出最终分数。思路已清晰,下面展示代码实现。

核心代码实现与逻辑解析


import cv2
import numpy as np

path = './test_01.png'
img = cv2.imread(path)

imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
imgBlur = cv2.GaussianBlur(imgGray,(3,3),1)
imgCanny = cv2.Canny(imgBlur,100,120)

cv2.imshow("O", imgCanny)

imgContour = img.copy()

cnts = cv2.findContours(imgCanny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
for cnt in cnts:
    area = cv2.contourArea(cnt)
    # 这个输出各个轮廓的面积
    #print(area)
#
if area >= 500:
    cv2.drawContours(imgContour, cnt, -1, (255, 0, 0), 3)
    peri = cv2.arcLength(cnt, True)
    # 找出轮廓的突变值
    approx = cv2.approxPolyDP(cnt, 0.02 * peri, True)
    # approx找到的是一个轮廓有几个突变值,有几个角就会有几个突变值
    # 返回的是一个list,输出他的长度,就可以知道到底有几个角
    #print(approx)
    a1,a2,a3,a4 = list(approx[0][0]),list(approx[1][0]),list(approx[2][0]),list(approx[3][0])

#cv2.imshow("Canny Image",imgContour)

mat1 = np.array([a1,a2,a3,a4],dtype=np.float32)

#透视变换
#计算矩形宽高
width = 402#int(((a4[0]-a1[0])+(a3[0]-a2[0]))/2)
height = 518#int(((a2[1]-a1[1])+(a3[1]-a4[1]))/2)

#计算还原后的坐标
new_a1 = [0,0]
new_a2 = [0,height]
new_a3 = [width,height]
new_a4 = [width,0]

mat2 = np.array([new_a1,new_a2,new_a3,new_a4],dtype=np.float32)
#计算变换矩阵
mat3 = cv2.getPerspectiveTransform(mat1,mat2)

#进行透视变换
res = cv2.warpPerspective(imgCanny,mat3,(width,height))
res1 = cv2.warpPerspective(img,mat3,(width,height))

imgxx = cv2.cvtColor(res1,cv2.COLOR_BGR2GRAY)
binary = cv2.threshold(imgxx,0,255,cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU )[1]
#变换完成
#cv2.imshow("Output",res1)

cntss = cv2.findContours(res, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]
for cnt1 in cntss:
    area1 = cv2.contourArea(cnt1)
    # 这个输出各个轮廓的面积
    #print(area)
#
    if area1 >= 1500 and area1<=1700:
        #把圆的轮廓画成黑色
        cv2.drawContours(binary, cnt1, -1, (0, 0, 0), 10)

        kernel = np.ones((5, 5), np.uint8)
        imgDialation = cv2.dilate(binary, kernel, iterations=1)

cv2.imshow("Out", imgDialation)

cntsss = cv2.findContours(imgDialation, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)[0]

l1 = []
l2 = []
l3 = ['B','E','A','D','B']

for cnt2 in cntsss:
    area2 = cv2.contourArea(cnt2)
            #print(area)

    if area2 <= 1200 and 800<=area2:
                #cv2.drawContours(res1, cnt, -1, (0, 255, 0), 5)
                #轮廓长
        peri = cv2.arcLength(cnt2, True)
                # 找出轮廓的突变值
        approx1 = cv2.approxPolyDP(cnt2, 0.02 * peri, True)

        x, y, w, h = cv2.boundingRect(approx1)
                #外接矩形
        #print(x+w//2,y+h//2)

        m = x+w//2
        n = y+h//2
        l1.append(m)
        l2.append(n)
        #拼接两个一维列表,使x,y坐标配对。
        mix1 = list(zip(l1,l2))
        #按列表第二个元素升序,即按y值由小到大排列。
        #这是我们得到的答案为正确顺序。
        mix1.sort(key=lambda x: x[1])

        if 400>x>80 and 50<y<350:
            cv2.rectangle(res1, (x, y), (x + w, y + h), (0, 0, 255), 2)
            #圆心
            # (图像,x.y位置,半径,颜色,轮廓粗细)
            cv2.circle(res1, (x+w//2,y+h//2), 1, (255, 0, 0), 5)

l4 = []
for i in mix1:
    if 75 < i[0] < 130:
        print("A")
        l4.append('A')
    elif 130 < i[0] < 185:
        print("B")
        l4.append('B')
    elif 185 < i[0] < 240:
        print("C")
        l4.append('C')
    elif 240 < i[0] < 295:
        print("D")
        l4.append('D')
    elif 295 < i[0] < 350:
        print("E")
        l4.append('E')

print('正确答案:',l3)
print('考生答案',l4)


h = 0
for i in range(0, len(l3)):
    if l3[i] == l4[i]:
        h=h+1
print('得分:',str(h/5*100)+'分')

cv2.imshow("cc Image",res1)

cv2.imshow("dd Image",binary)

cv2.waitKey(0)

运行结果验证

运行结果:


以上展示了两个图片素材的部分运行结果,其余素材请读者自行实验验证。从结果可以看出,程序成功识别了考生填涂的答题卡,并准确输出了考生答案、标准答案以及考生的最终得分。综上所述,功能实现完毕,任务完成。

以上就是Python识别答题卡并给出分数的详细内容,更多关于Python识别答题卡的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表