OMR项目cX/cY参数调试难题:标记位置偏移且数量不符
OMR光学标记识别项目:cX/cY参数定位问题修复
问题现象
开发OMR项目时,运行代码后生成的绿色标记仅显示5个(预期10个),且未精准定位到答题卡的填涂区域,手动调整cX、cY参数后无改善。
相关代码
主代码
import cv2 import numpy as np import utiliss ####################### path = "omr_sheetF2.jpg" heightImg=600 widthImg=600 questions=10 choices=5 # 修正:原数组中第5个元素为5,超出选项索引范围(0-4),改为有效索引4 ans = [0,2,1,4,4,0,3,2,2,1] ####################### img = cv2.imread(path) # 预处理 img = cv2.resize(img,(widthImg,heightImg)) imgContours = img.copy() imgBiggestcontours = img.copy() imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) imgBlur = cv2.GaussianBlur(imgGray,(5,5),1) imgCanny = cv2.Canny(imgBlur,10,50) # 查找轮廓 contours, hierarchy = cv2.findContours(imgCanny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE) cv2.drawContours(imgContours,contours,-1,(0,255,0),10) # 筛选矩形轮廓 rectcon = utiliss.rectcontours(contours) biggestcontour = utiliss.getcornerpoints(rectcon[0]) gradepoints = utiliss.getcornerpoints(rectcon[2]) if biggestcontour.size !=0 and gradepoints.size !=0: cv2.drawContours(imgBiggestcontours,biggestcontour,-1,(0,255,0),15) cv2.drawContours(imgBiggestcontours,gradepoints,-1,(255,0,0),15) biggestcontour= utiliss.reorder(biggestcontour) gradepoints = utiliss.reorder(gradepoints) # 透视变换校正答题卡区域 pt1 = np.float32(biggestcontour) pt2 = np.float32([[0,0],[widthImg,0],[0,heightImg],[widthImg,heightImg]]) matrix = cv2.getPerspectiveTransform(pt1,pt2) imgWarpcolored= cv2.warpPerspective(img,matrix,(widthImg,heightImg)) # 透视变换校正分数显示区域 ptG1 = np.float32(gradepoints) ptG2 = np.float32([[0, 0], [325, 0], [0, 150], [325, 150]]) matrixG = cv2.getPerspectiveTransform(ptG1, ptG2) imgGradeDisplay = cv2.warpPerspective(img, matrixG, (325, 150)) # 二值化处理 imgWarpGray= cv2.cvtColor(imgWarpcolored,cv2.COLOR_BGR2GRAY) imgThresh= cv2.threshold(imgWarpGray,100,255,cv2.THRESH_BINARY_INV)[1] # 分割填涂框 boxes= utiliss.splitboxes(imgThresh) # 统计每个填涂框的非零像素值 myPixelVal= np.zeros((questions,choices)) countC= 0 countR= 0 for image in boxes: totalpixels = cv2.countNonZero(image) myPixelVal[countR][countC] = totalpixels countC +=1 if (countC == choices):countR +=1 ;countC=0 # 获取每个问题的选中选项索引 myIndex = [] for x in range (0,questions): arr = myPixelVal[x] myIndexVal = np.where(arr==np.amax(arr)) myIndex.append(myIndexVal[0][0]) # 计算得分 grading=[] for x in range(0,questions): if ans [x]== myIndex[x]: grading.append(1) else:grading.append(0) score= (sum(grading)/questions) *100 # 最终得分 print(score) # 绘制答案标记 imgResult = imgWarpcolored.copy() imgResult = utiliss.showanswers(imgResult,myIndex,grading,ans,questions,choices) # 堆叠显示所有处理图像 imgBlank = np.zeros_like(img) imageArray = ([img,imgGray,imgBlur,imgCanny], [imgContours,imgBiggestcontours,imgWarpcolored,imgThresh], [imgResult,imgBlank,imgBlank,imgBlank]) imgStacked = utiliss.stackImages (imageArray,0.25) cv2.imshow("Stacked Images",imgStacked) cv2.waitKey(0)
工具类(utiliss.py)代码
import cv2 import numpy as np # 堆叠显示多张图像 def stackImages(imgArray,scale,lables=[]): rows = len(imgArray) cols = len(imgArray[0]) rowsAvailable = isinstance(imgArray[0], list) width = imgArray[0][0].shape[1] height = imgArray[0][0].shape[0] if rowsAvailable: for x in range ( 0, rows): for y in range(0, cols): imgArray[x][y] = cv2.resize(imgArray[x][y], (0, 0), None, scale, scale) if len(imgArray[x][y].shape) == 2: imgArray[x][y]= cv2.cvtColor( imgArray[x][y], cv2.COLOR_GRAY2BGR) imageBlank = np.zeros((height, width, 3), np.uint8) hor = [imageBlank]*rows hor_con = [imageBlank]*rows for x in range(0, rows): hor[x] = np.hstack(imgArray[x]) hor_con[x] = np.concatenate(imgArray[x]) ver = np.vstack(hor) ver_con = np.concatenate(hor) else: for x in range(0, rows): imgArray[x] = cv2.resize(imgArray[x], (0, 0), None, scale, scale) if len(imgArray[x].shape) == 2: imgArray[x] = cv2.cvtColor(imgArray[x], cv2.COLOR_GRAY2BGR) hor= np.hstack(imgArray) hor_con= np.concatenate(imgArray) ver = hor if len(lables) != 0: eachImgWidth= int(ver.shape[1] / cols) eachImgHeight = int(ver.shape[0] / rows) for d in range(0, rows): for c in range (0,cols): cv2.rectangle(ver,(c*eachImgWidth,eachImgHeight*d),(c*eachImgWidth+len(lables[d][c])*13+27,30+eachImgHeight*d),(255,255,255),cv2.FILLED) cv2.putText(ver,lables[d][c],(eachImgWidth*c+10,eachImgHeight*d+20),cv2.FONT_HERSHEY_COMPLEX,0.7,(255,0,255),2) return ver # 筛选矩形轮廓 def rectcontours(contours): rectcon=[] for i in contours: area = cv2.contourArea(i) if area>50: peri = cv2.arcLength(i,True) approx = cv2.approxPolyDP(i,0.02*peri,True) if len(approx)==4: rectcon.append(i) rectcon = sorted(rectcon,key= cv2.contourArea,reverse=True) return rectcon # 获取轮廓的角点 def getcornerpoints(cont): peri = cv2.arcLength(cont, True) approx = cv2.approxPolyDP(cont, 0.02 * peri, True) return approx # 重新排序角点(适配透视变换) def reorder(myPoints): myPoints = myPoints.reshape((4,2)) myPointsNew = np.zeros((4,1,2),np.int32) add = myPoints.sum (1) myPointsNew[0]= myPoints[np.argmin(add)] #[0, 0] myPointsNew[3]= myPoints[np.argmax(add)] #[w, h] diff = np.diff(myPoints,axis=1) myPointsNew[1]= myPoints[np.argmin(diff)] # [w, 0] myPointsNew[2]= myPoints[np.argmax(diff)] # [0, h] return myPointsNew # 分割填涂框为10行5列 def splitboxes(img): rows= np.vsplit(img,10) boxes = [] for r in rows: cols= np.hsplit(r,5) for box in cols: boxes.append(box) return boxes # 绘制答案标记(修复版) def showanswers(img,myIndex,grading,ans,questions,choices): # 修正:secW为图像宽度除以选项数(列数),secH为图像高度除以问题数(行数) secW= int(img.shape[1]/choices) secH= int(img.shape[0]/questions) for x in range (0, questions): myAns = myIndex[x] # 修正cX、cY计算逻辑,匹配行列分割方式 cX = (myAns * secW) + secW // 2 cY = (x * secH) + secH // 2 print(f"Iteration {x + 1}: myAns={myAns}, cX={cX}, cY={cY}") cv2.circle(img,(cX,cY),20,(0,255,0),cv2.FILLED) return img
问题根源
- 行列尺寸计算错误:原
showanswers函数中,secH错误使用图像宽度除以选项数,实际应该用图像高度除以问题数,导致行高计算过大,10个问题的标记超出图像范围,仅显示前5个。 - 答案数组索引越界:原
ans数组中第5个元素为5,但选项只有5个(索引范围0-4),导致myIndex出现无效索引,进一步造成cX计算异常,标记位置错误。 - cX/cY映射逻辑颠倒:原代码中secW和secH的计算对应关系错误,导致列宽和行高不匹配答题卡的实际分割方式。
修复效果
修正后,绿色标记将显示全部10个,且精准定位到每个问题的填涂选项中心位置,与答题卡的实际填涂区域对齐。
内容的提问来源于stack exchange,提问作者Wakeel Omololu
相关产品推荐
相关产品推荐

