opencv_python答题卡自动判卷

  1. 设定答题卡模板
    该图像为答题卡的答题区域,黑色边框是为了能够在各种环境中轻易的检测,左部分和上部分的黑色矩形,是为能够定位到答题选项的坐标而设置,同时题目数量为20×3共60道选择题,在进行批改试卷之前,需要手动输入该次考试的正确答案作为模板来对识别的内容进行比较判分。
  2. 读取答题卡图像并对图像进行灰度化处理
# 最大值法求图像灰度值
def graying(image):h, w = image.shape[0], image.shape[1]gray = np.zeros((h, w), np.uint8)for i in range(h):for j in range(w):gray[i, j] = max(image[i,j][0], image[i,j][1], image[i,j][2])return gray

3 高斯模糊图像去噪点

gray = cv2.GaussianBlur(gray, (3, 3), 0)

4使用大津法二值分割图像
经过上一步操作后,答题卡的前背景分明,已经能够轻易将标识矩阵和答题区域涂改信息和背景分离开来,接下来需要将图像的标识矩阵和答题区域的涂改信息提取出来,需要进一步规划数字图像信息,二值化图像能使图像的数据量大大减少,既保留原有的数字信息,又能将无用的数据舍去,将原本数值范围为0-255的图像信息分割成像素值为0或255的二值图像,在这里0值代表背景,无用且不需要处理的信息,255表示目标信息(标识矩阵和答题区域的涂改信息),其算法原理非常简单,图像中像素值大于阈值时为255,小于阈值时为0。在二值化时,需要确定一个阈值,而这个阈值人为来定义是无法随着环境变换随时处于最优状态,在这里我们使用1979年由学者大津提出的对图像分割的高效算法来处理。大津法算法原理:
有假设如下:
u: 图像像素值平均值
g: 图像类间方差
w0: 图像背景像素点数占图像的比例
u0: 图像背景像素点的平均值
w1: 图像前景像素点数占图像的比例
u1: 图像前景像素点的平均值
算法公式为:

将第一个公式代入第二个得:

5使用开运算去噪点
此时已经得到较为完美的预处理图,但是不难发现,我们答题卡有一小块干扰像素。在实际情况中,这种干扰信息是有可能出现的,且大小与清晰度并没有固定范围,因此,在判卷之前,需要尽可能的将这种干扰信息去除,前面的各种图像预处理方法仅仅是将图像的前景和背景分离提取出定位标识信息和涂改信息,面对这样的情况,选择使用机器视觉中常用的开运算方法处理图像可以得到较好的效果。

开运算:先对图像进行腐蚀操作,再进行膨胀操作,就是开运算操作,能够消除细小的物体,将两个物体的细小的连接处去除从而分离两个物体,且拥有平滑边界的效果,被广泛应用于去除图像噪声。
腐蚀算法原理:
步骤1:定义一个卷积核B,卷积核可以是随意的大小与形状,但通常是带参考点的正方形或圆形,作为腐蚀的模板。
步骤2:将卷积核与原图像进行卷积操作,计算原图像包裹卷积核B的区域的像素最小值,这个区域则作为腐蚀操作后的结果。
例如有原图像A,卷积核B

则经过腐蚀算法操作之后可得实验结果为如下:

实验发现,右上角的小块图像噪声被腐蚀掉了,同时,下方的像素块被腐蚀了一圈且两块被分割开来,为了尽量减少图像的信息被过度腐蚀掉,接下来一步需要使用膨胀算法,将图像像素膨胀回来,尽可能去掉图像噪声的同时,也减少图像信息的过度减少。
膨胀算法原理:
步骤1:定义一个卷积核B,开运算时直接使用腐蚀操作时使用的卷积核B。
步骤2:将卷积核B与原图像进行卷积操作,计算原图像包裹卷积核B的区域的像素最大值,这个区域则作为膨胀操作后的结果。使用腐蚀操作后的结果来进行膨胀操作,实验效果如下:

观察开运算操作的前后对比,可以得知,右上角的噪声完全去除的同时,与原图像信息相比较,仅仅有五个像素点被去除,是完全可以接受的。
开运算操作实际实验效果如下:

步骤进行到这,发现右下角那块较大的图像噪声,仍旧无法消除,主要原因是该噪声较大,形状大小与颜色深度与目标信息相似,在准确保留目标信息的情况下难以将其分割开来,因此接下来选择通过定位图像信息来排除该图像噪声,对其不进行操作。
代码网上找的,忘记从哪抄的了,百度一下都有。。。

# 图像腐蚀
def etch(img, size):h=img.shape[0]w=img.shape[1]img1=np.zeros((h,w),np.uint8)for i in range (1,h-1):for j in range (1,w-1):min=img[i,j]for k in range (i-size,i+size):for l in range (j-size,j+size):if k<0|k>=h-1|l<0|l>=w-1:continueif img[k,l]<min:min=img[k,l]img1[i,j]=minreturn img1
# 图像膨胀
def expand(img, size):h=img.shape[0]w=img.shape[1]img1=np.zeros((h,w),np.uint8)for i in range (1,h-1):for j in range (1,w-1):max=img[i,j]for k in range (i-size,i+size):for l in range (j-size,j+size):if k<0|k>=h-1|l<0|l>=w-1:continueif img[k,l]>max:max=img[k,l]img1[i,j]=maxreturn img1
# 开运算
def opening(image, size):etch_img = etch(image, size)expand_img = expand(etch_img, size)return expand_img

6使用canny边缘检测算法
canny边缘检测算法是一种运用非常广泛的算法,由john F.Canny在1986年提出的,一种多阶段的算法:
步骤1:对图像进行去噪
步骤2:计算图像的强度梯度
步骤3:在边缘上进行非极大值抑制
步骤4:对检测得到的边缘使用双阈值排查
步骤5:分析边缘之间的连接
通过这一系列的操作后便可得到图像内容里的边缘信息,我们前面已经对图像进行了深度的去噪操作,已经将大部分噪音完全清除,接下来的操作应该是区分定位区域和判卷区域的坐标,来对其进行判断处理,这一步只是为了观察图像的边缘信息,属于测试步骤,在实际的运用中,并不会使用该步骤来处理图像。
实验结果如下:

7筛选答题区域轮廓,透视变换矫正目标区域
通过轮廓检测可以计算多边形的外界,在这里我们需要检测出答题卡涂改区域的黑色边框位置,定位得到边框的四个顶点坐标,再对目标进行透视矫正操作。到这一步骤,已经得到矫正后的答题区域,接下来需要对图像的答题区域进行定位判断。
具体实验效果如下:

# 透视变换
from imutils.perspective import four_point_transform
def wPs(image, points):warped = four_point_transform(image, points)return warped


最后判断黑色集中的地方就可以判断选项了

使用摄像头实时判卷部分

调用摄像头部分的处理方式是对图像处理里使用的方法的一个总和。最终该系统的实时判卷,准确率达到百分之百,并且是在环境较差的的情况下进行判卷。



全部代码如下

import cv2
import numpy as np
# 选取区域去除边缘
dist = 5
# 画图线粗度
line_w = 2
# 画笔颜色
red = (0, 0, 255)
green = (0, 255, 0)
blue = (255, 0, 0)
# 高斯模糊算法
#防止颜色值超出颜色取值范围(0-255)
# 开运算,先腐蚀,后膨胀
# 图像腐蚀
def etch(img, size):h=img.shape[0]w=img.shape[1]img1=np.zeros((h,w),np.uint8)for i in range (1,h-1):for j in range (1,w-1):min=img[i,j]for k in range (i-size,i+size):for l in range (j-size,j+size):if k<0|k>=h-1|l<0|l>=w-1:continueif img[k,l]<min:min=img[k,l]img1[i,j]=minreturn img1
# 图像膨胀
def expand(img, size):h=img.shape[0]w=img.shape[1]img1=np.zeros((h,w),np.uint8)for i in range (1,h-1):for j in range (1,w-1):max=img[i,j]for k in range (i-size,i+size):for l in range (j-size,j+size):if k<0|k>=h-1|l<0|l>=w-1:continueif img[k,l]>max:max=img[k,l]img1[i,j]=maxreturn img1
# 开运算
def opening(image, size):etch_img = etch(image, size)expand_img = expand(etch_img, size)return expand_img
# 最大值法求图像灰度值
def graying(image):h, w = image.shape[0], image.shape[1]gray = np.zeros((h, w), np.uint8)for i in range(h):for j in range(w):gray[i, j] = max(image[i,j][0], image[i,j][1], image[i,j][2])return gray
# OTSU
# 二值化
def otsu(img):h=img.shape[0]w=img.shape[1]m=h*wotsuimg=np.zeros((h,w),np.uint8)threshold_max=threshold=0histogram=np.zeros(256,np.int32)probability=np.zeros(256,np.float32)for i in range(h):for j in range(w):s=img[i,j]histogram[s]+=1for k in range(256):probability[k]=histogram[k]/mfor i in range(255):w0 = w1 = 0fgs = bgs = 0for j in range (256):if j<=i:w0+=probability[j]fgs+=j*probability[j]else:w1+=probability[j]bgs+=j*probability[j]u0=fgs/w0u1=bgs/w1g=w0*w1*(u0-u1)**2if g>=threshold_max:threshold_max=gthreshold=ifor i in range (h):for j in range (w):if img[i,j]<threshold:otsuimg[i,j]=255else:otsuimg[i,j]=0return otsuimg
# 透视变换
from imutils.perspective import four_point_transform
def wPs(image, points):warped = four_point_transform(image, points)return warped
# 轮廓检测函数
def find_contour(image):contours = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]return contours
# 冒泡排序
def bubble_sort(lists, type):''':param lists: 排序列表:param type: 排序类型:return: 排序结果'''count = len(lists)for i in range(0, count):xi, yi = cv2.boundingRect(lists[i])[0], cv2.boundingRect(lists[i])[1]for j in range(i + 1, count):xj, yj = cv2.boundingRect(lists[j])[0], cv2.boundingRect(lists[j])[1]if type == "title":if yi > yj:lists[i], lists[j] = lists[j], lists[i]elif type == "answer":if xi > xj:lists[i], lists[j] = lists[j], lists[i]else:return print("排序出错")return lists
#统计结果
def count(roi):''':param roi: 答题选项区域:return: 选择结果'''grade = 0long = roi.shape[1] / 8contour = find_contour(roi)if len(contour) == 0:return 0elif len(contour) >= 2:return -2for c in contour:perimeter = cv2.arcLength(c, True)if perimeter > 5:x = cv2.boundingRect(c)[0]if x < long:grade = 1elif x < long*3:grade = 2elif x < long*5:grade = 3else:grade = 4return grade
# 轮廓检测处理
def contours(img, dst):''':param img: 查看效果图像:param dst: 轮廓检测对象:return: 效果图像,轮廓检测效果图像,检测结果'''img_dst = img.copy()edged = cv2.Canny(dst, 10, 100)img_cnts = find_contour(edged)# 如果未检测到轮廓则退出c_len = len(img_cnts)if c_len == 0:print("error:No find contours!")return img, dst# 画出所有轮廓## 得到答题区域pt = Nonefor c in img_cnts:cv2.drawContours(img, [c], -1, red, line_w)perimeter = cv2.arcLength(c, True)if perimeter < 40:continueapprox = cv2.approxPolyDP(c, 0.02*perimeter, True)if len(approx) == 4:pt = approxhull = cv2.convexHull(c)cv2.polylines(img, [hull], True, green, line_w)pt = pt.reshape(4,2)# 透视变换img_dst = wPs(img_dst, pt)dst = wPs(dst, pt)img_dst = img_dst[dist:img_dst.shape[0]-dist,dist:img_dst.shape[1]-dist]dst = dst[dist:dst.shape[0]-dist,dist:dst.shape[1]-dist]# 处理答题卡答题区域部分contours_roi = find_contour(dst)title, answer = [], []for c in contours_roi:x, y, w, h = cv2.boundingRect(c)if x >= dist and y <= dist:answer.append(c)if x < dist and y > dist:title.append(c)# 冒泡排序title = bubble_sort(title, "title")answer = bubble_sort(answer, "answer")# 判卷result = np.zeros(60, dtype=np.int8)for title_number in range(60):miny = cv2.boundingRect(title[title_number%20])[1]x, y, w, h = cv2.boundingRect(answer[int(title_number/20+1)*4-1])x1= cv2.boundingRect(answer[int(title_number/20+1)*4-4])[0]maxx, maxy = x+w, miny+hcv2.rectangle(img_dst, (x1, miny), (maxx, maxy), blue, line_w)roi = dst[miny:maxy, x1:maxx]grade = count(roi)result[title_number] = gradeprint("title"+str(title_number+1)+":",grade)return img, img_dst, result
def new_contours(img_dst, aim_otsu):''':param img_dst: 查看效果图像:param aim_otsu: 答题卡区域:return: 效果图像, 识别结果'''# 处理答题卡答题区域部分contours_roi = find_contour(aim_otsu)title, answer = [], []for c in contours_roi:x, y, w, h = cv2.boundingRect(c)if x >= dist and y <= dist:answer.append(c)if x < dist and y > dist:title.append(c)# 冒泡排序title = bubble_sort(title, "title")answer = bubble_sort(answer, "answer")# 判卷result = np.zeros(60, dtype=np.int8)for title_number in range(60):miny = cv2.boundingRect(title[title_number % 20])[1]x, y, w, h = cv2.boundingRect(answer[int(title_number / 20 + 1) * 4 - 1])x1 = cv2.boundingRect(answer[int(title_number / 20 + 1) * 4 - 4])[0]maxx, maxy = x + w, miny + hcv2.rectangle(img_dst, (x1, miny), (maxx, maxy), blue, 1)roi = aim_otsu[miny:maxy, x1:maxx]grade = count(roi)result[title_number] = gradereturn img_dst, result
# 主要步骤
def run(img):''':param img: 可操作的原图像:return: 预处理后的图像'''print("image.shape:", img.shape)# 最小值法求图像灰度值gray = graying(img)# 二值分割大津法thresh = otsu(gray)img_open = opening(thresh, 1)return img_open
from PIL import Image, ImageDraw, ImageFont
font_china = ImageFont.truetype('simhei.ttf', 40, encoding="utf-8")
def ChinaToImage(image, str, color):''':param image: 原图像:param str: 需要写的字:param color:画笔颜色:return:写完字的图像'''img_PIL = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(img_PIL)draw.text((20, 20), str, color,font=font_china)return cv2.cvtColor(np.asarray(img_PIL),cv2.COLOR_RGB2BGR)
# 提示是否可以开始函数
def hint(image, b):''':param image: 摄像头画面:param b: 提示是否可以批卷:return: 返回写入提示的画面'''str_s = '按下Esc退出!\n按下空格开始!'str_e = '按下Esc退出!\n请调整好角度!'if b:image = ChinaToImage(image, str_s, green)else:image = ChinaToImage(image, str_e, red)return image
# 查找答题卡轮廓,提示是否可以开始
def star_bool(image):''':param image: 摄像头画面:return: 是否可以开始批卷'''image_gray = graying(image)edged = cv2.Canny(image_gray, 10, 100)con = find_contour(edged)b = Falsepoints = Nonefor c in con:cv2.drawContours(image, [c], -1, red, line_w)perimeter = cv2.arcLength(c, True)w, h = cv2.minAreaRect(c)[1]if w == 0 or h == 0 or w/h < 0.6:continueif perimeter < 200:continueapprox = cv2.approxPolyDP(c, 0.02 * perimeter, True)if len(approx) != 4:continueb = Truepoints = approxhull = cv2.convexHull(c)cv2.polylines(image, [hull], True, green, line_w)aim_c = Noneaim_otsu = Noneif b:try:points = points.reshape(4, 2)aim = wPs(image_gray, points)aim_c = wPs(image, points)aim = aim[dist:aim.shape[0] - dist, dist:aim.shape[1] - dist]aim_c = aim_c[dist:aim_c.shape[0] - dist, dist:aim_c.shape[1] - dist]aim_otsu = otsu(aim)cv2.imshow('aim_otsu', aim_otsu)except:print('角度误差大!')return b, aim_c, aim_otsu
# 批改函数
def correct(model_answer, result):''':param model_answer: 该试卷正确答案:param result: 识别答案:return: 显示批卷结果,显示效果,可检测对象'''if len(model_answer) != 60:print('答案模板数量不对!\n请重新设置答案。')return 0# 成绩grade = {'score':0, 'no choice':0, 'mul':0}no_choice_number = []mul_number = []# 题的分值,topik考试基本每题两分cube = 2# 计算分数for index in range(len(model_answer)):if model_answer[index] > 4 or model_answer[index] < 1:print("答案模板有误!\n请重新设置答案。")return 0if result[index] == 0:no_choice_number.append(index+1)grade['no choice'] += 1continueif result[index] == -2:mul_number.append(index+1)grade['mul'] += 1continueif model_answer[index] == result[index]:grade['score'] += cube# 批卷完成print('-' * 70)print('-' * 70)print('正确答案:\n', model_answer)print('识别结果:\n', result)print('-'*35)print('分值:', grade['score'])print('-' * 35)print('空选数量:', grade['no choice'])print('空选题号:\n', no_choice_number)print('-' * 35)print('多选数量:', grade['mul'])print('多选题号:\n', mul_number)print('-' * 70)print('-' * 70)
def main():# 该变量为本次试卷正确答案模板,需要根据试卷受到修改原本正确答案model_answer = [1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,1, 2, 3, 4,]cap = cv2.VideoCapture(0)cv2.namedWindow("image", 0)cv2.resizeWindow("image", 640, 480)while True:sucess, img = cap.read()img_temp = img.copy()b, aim, aim_otsu = star_bool(img_temp)img_temp = hint(img_temp, b)cv2.imshow("image", img_temp)k = cv2.waitKey(16)# Esc结束if k == 27:break# 空格按下开始elif k == 32:try:img_dst, result = new_contours(aim, aim_otsu)correct(model_answer, result)cv2.imshow('answer_roi', img_dst)except:print("您拍答题卡的角度误差过大")else:if cv2.waitKey(0) == 27:breakelse:continuecap.release()cv2.destroyAllWindows()
if __name__=="__main__":main()

感谢各位大爷~

opencv_python答题卡自动判卷相关推荐

  1. opencv图像处理—项目实战:答题卡识别判卷

    哔站唐宇迪opencv课程--项目实战:答题卡识别判卷 [计算机视觉-OpenCV]唐宇迪博士教会了我大学四年没学会的OpenCV OpenCV计算机视觉实战全套课程(附带课程课件资料+课件笔记+源码 ...

  2. OpenCV实战(二)——答题卡识别判卷

    代码见 https://github.com/skyerhxx/Answer-card-recognition-and-judgment 答题卡识别判卷 识别出考生选择的答案并能自动判分 Python ...

  3. 深入学习OpenCV文档扫描OCR识别及答题卡识别判卷(文档扫描,图像矫正,透视变换,OCR识别)

    人工智能学习离不开实践的验证,推荐大家可以多在FlyAI-AI竞赛服务平台多参加训练和竞赛,以此来提升自己的能力.FlyAI是为AI开发者提供数据竞赛并支持GPU离线训练的一站式服务平台.每周免费提供 ...

  4. 17.答题卡识别判卷

    目录 1  项目介绍 2  代码分析 2.1  导入库 2.2  设置参数 2.3  设置正确答案 2.4  定义找到四个角点的函数 2.5  定义变换函数 2.6  定义 sort_contours ...

  5. Opencv之答题卡识别判卷

    项目要求 提供一张答题卡图像,通过图像处理识别出答题卡上每个题的选项,与正确答案对比,得出分数并写在答题卡上. 代码实现过程 1.引入需要的库 import numpy as np import cv ...

  6. 人工智能项目实战-使用OMR完成答题卡识别判卷

  7. python OpenCV 答题卡识别判卷

    完整代码: #导入工具包 import numpy as np import argparse import imutils import cv2# 设置参数 ap = argparse.Argume ...

  8. 自动判卷 、答题卡识别、六级答题卡客观题自动判卷系统1.0

    自动判卷 .答题卡识别.六级答题卡客观题自动判卷系统1.0 一.前言 二.代码 三.处理原图片.以及效果图片 四.总结 一.引言: 1.本程序设计想法来源于一次四六级考试,因为六级没有好好准备,裸考上 ...

  9. 计算机Excel批量改试卷,用Excel设计标准化试卷的自动判卷系统.doc

    用Excel设计标准化试卷的自动判卷系统 计 算 机 与 现 代 化2012 年第 1 期总第 197 期JISUANJI YU XIANDAIHUA文章编号: 1006-2475( 2012) 01 ...

最新文章

  1. Internet History, Technology, and Security----第三周
  2. php intl make 错误,无法在Debian上为php安装’intl’扩展名
  3. OncePerRequestFilter-源码解析
  4. Java Arrays.sort()的几种用法
  5. 系统间通信2:通信管理与远程方法调用RMI
  6. ubuntu16.04配置sonarqube+MySQL
  7. oracle 建表id自增长_oracle 左连接、右连接、全外连接、内连接、以及 (+) 号用法...
  8. 后端要学MySQL_后端程序员需要掌握MySQL数据库吗
  9. python可执行程序必须以管理员_Python实现管理员权限运行CMD指令
  10. MES系统在汽车零部件行业的应用
  11. 手机奥维地图在测量中的应用
  12. Hadoop性能调优
  13. [搞笑] 后舍男生最完美的视频收藏全纪录
  14. 前端框架及项目面试题库介绍
  15. Oracle增加修改删除字段
  16. Ubuntu安装etter
  17. 古为今鉴——为臣(为属)之道
  18. java怎么获取屏幕PPI_智能手机屏幕清晰度用户体现的分析:PPI与PPI2
  19. QQ好友辅助验证不要信
  20. 1000亩盐碱地试验田 国稻种芯-田国庆:拓荒精神荒滩变良田

热门文章

  1. 把金庸小说数据化——关于语言的思一点考
  2. SpringCloud04-Ribbon、OpenFeign、Hystrix
  3. dl388 Gen10服务器U盘装系统,HPE DL388 Gen10 服务器
  4. JS高阶编程之柯理化函数
  5. 使用Kali Linux虚拟机破解WiFi密码的一波三折及详细操作步骤
  6. 通过实例学习C#(1)
  7. [开心学PHP100天(七)]精讲PHP构造函数:父亲和儿子的恩恩怨怨
  8. 阿里大文娱:打造更多社会需要的优质原创视频内容
  9. 对Python threading.Thread(daemon=True)线程守护的理解
  10. 功能测试用例设计思路