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()
实战三、OpenCv答题卡识别判卷
最新推荐文章于 2024-07-17 23:36:18 发布