基于OpenCV的答题卡识别与评分系统 基于OpenCV的答题卡识别与评分系统Python实现 摘要在教育和考试场景中自动批改答题卡能极大提升效率。本文使用OpenCV和Python实现了一个完整的答题卡识别与评分系统涵盖图像预处理、透视变换、轮廓检测、选项识别和自动判分。同时我将分享在实际运行中遇到的经典错误及解决方案帮助初学者少走弯路。1️⃣ 项目背景与目标输入一张拍摄的答题卡图片含四个角标记、圆形涂写选项。输出每道题的判定结果正确/错误以及总得分并在原图上标注。核心流程边缘检测 轮廓查找定位答题卡主体四边形。透视变换矫正倾斜。二值化 轮廓筛选提取所有圆形选项。按行分组对每行5个选项排序。利用掩膜统计每个选项的涂黑像素数选出被选中的选项。与标准答案比对计算得分并可视化。2️⃣ 技术原理速览技术点作用Canny边缘检测提取图像中的边缘便于轮廓查找轮廓查找与近似cv2.findContourscv2.approxPolyDP找出四边形区域透视变换cv2.getPerspectiveTransformcv2.warpPerspective矫正视角OTSU二值化自动阈值分割突出涂黑区域THRESH_BINARY_INV将涂黑变白轮廓筛选依据宽高比≈1和尺寸筛选圆形选项掩膜Mask对每个选项单独创建掩膜统计该区域内的非零像素数确定是否被选中排序自定义sort_contours按坐标排序从上到下从左到右3️⃣ 代码实现详解分步注释3.1 工具函数定义importnumpyasnpimportcv2 ANSWER_KEY{0:1,1:4,2:0,3:3,4:1}# 每道题的正确答案索引从0开始***order_points – 顺序化四个角点***deforder_points(pts):rectnp.zeros((4,2),dtypefloat32)spts.sum(axis1)rect[0]pts[np.argmin(s)]# 左上和最小rect[2]pts[np.argmax(s)]# 右下和最大diffnp.diff(pts,axis1)rect[1]pts[np.argmin(diff)]# 右上差最小rect[3]pts[np.argmax(diff)]# 左下差最大returnrect***four_point_transform – 透视变换***deffour_point_transform(image,pts):rectorder_points(pts)(tl,tr,br,bl)rect# 计算目标宽度取上下边平均widthAnp.sqrt(((br[0]-bl[0])**2)((br[1]-bl[1])**2))widthBnp.sqrt(((tr[0]-tl[0])**2)((tr[1]-tl[1])**2))maxWidthmax(int(widthA),int(widthB))# 计算目标高度取左右边平均heightAnp.sqrt(((tr[0]-br[0])**2)((tr[1]-br[1])**2))heightBnp.sqrt(((tl[0]-bl[0])**2)((tl[1]-bl[1])**2))maxHeightmax(int(heightA),int(heightB))dstnp.array([[0,0],[maxWidth-1,0],[maxWidth-1,maxHeight-1],[0,maxHeight-1]],dtypefloat32)Mcv2.getPerspectiveTransform(rect,dst)warpedcv2.warpPerspective(image,M,(maxWidth,maxHeight))returnwarped***sort_contours – 轮廓排序支持四种方向***defsort_contours(cnts,methodleft-to-right):reverseFalsei0ifmethodright-to-leftormethodbottom-to-top:reverseTrueifmethodtop-to-bottomormethodbottom-to-top:i1# 按Y坐标排序boundingBoxes[cv2.boundingRect(c)forcincnts](cnts,boundingBoxes)zip(*sorted(zip(cnts,boundingBoxes),keylambdab:b[1][i],reversereverse))returncnts,boundingBoxes***cv_show – 辅助显示***defcv_show(name,img):cv2.imshow(name,img)cv2.waitKey(0)### 3.2 主流程重点标注步骤1读取图像预处理 imagecv2.imread(r./images/test_01.png)contours_imgimage.copy()graycv2.cvtColor(image,cv2.COLOR_BGR2GRAY)blurredcv2.GaussianBlur(gray,(5,5),0)edgedcv2.Canny(blurred,75,200)步骤2检测答题卡主体四边形 cntscv2.findContours(edged.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]cntssorted(cnts,keycv2.contourArea,reverseTrue)forcincnts:pericv2.arcLength(c,True)approxcv2.approxPolyDP(c,0.02*peri,True)iflen(approx)4:docCntapproxbreak步骤3透视变换矫正 warped_tfour_point_transform(image,docCnt.reshape(4,2))warped_newwarped_t.copy()warpedcv2.cvtColor(warped_t,cv2.COLOR_BGR2GRAY)步骤4二值化反色使涂黑区域为白色 threshcv2.threshold(warped,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)[1]步骤5提取所有圆形轮廓并筛选 cntscv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]# 注意索引questionCnts[]forcincnts:(x,y,w,h)cv2.boundingRect(c)arw/float(h)ifw20andh20and0.9ar1.1:questionCnts.append(c)步骤6按行分组并识别每道题的选择 questionCntssort_contours(questionCnts,methodtop-to-bottom)[0]correct0for(q,i)inenumerate(np.arange(0,len(questionCnts),5)):cntssort_contours(questionCnts[i:i5])[0]# 每行5个选项bubbledNonefor(j,c)inenumerate(cnts):masknp.zeros(thresh.shape,dtypeuint8)cv2.drawContours(mask,[c],-1,255,-1)thresh_mask_andcv2.bitwise_and(thresh,thresh,maskmask)totalcv2.countNonZero(thresh_mask_and)ifbubbledisNoneortotalbubbled[0]:bubbled(total,j)kANSWER_KEY[q]color(0,0,255)# 默认红色错误ifkbubbled[1]:color(0,255,0)# 绿色正确correct1cv2.drawContours(warped_new,[cnts[k]],-1,color,3)步骤7计算得分并输出 score(correct/5.0)*100print(f[INFO] score:{score:.2f}%)cv2.putText(warped_new,f{score:.2f}%,(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,0,255),2)cv2.imshow(Original,image)cv2.imshow(Exam,warped_new)cv2.waitKey(0)4️⃣ 运行效果与总结在测试图片test_01.png上运行后程序会显示每一步处理结果模糊、边缘、轮廓、透视矫正、二值化、掩膜等。最终输出带评分和标注的答题卡图像。效果展示示例识别出的选项用绿色框正确或红色框错误标记。左上角显示得分百分比。 参考资料OpenCV官方文档https://docs.opencv.org/透视变换原理https://docs.opencv.org/4.x/da/d6e/tutorial_py_geometric_transformations.html