还在手动阅卷?教你用python实现自动阅卷,解放自己的双手

2023-11-03

随着现代图像处理和人工智能技术的快速发展,不少学者尝试讲CV应用到教学领域,能够代替老师去阅卷,将老师从繁杂劳累的阅卷中解放出来,从而进一步有效的推动教学质量上一个台阶。

传统的人工阅卷,工作繁琐,效率低下,进度难以控制且容易出现试卷遗漏未改、登分失误等现象。

现代的“机器阅卷”,工作便捷、效率高、易操作,只需要一个相机(手机),拍照即可获取成绩,可以导入Excel表格便于存档管理。

下面我们从代码实现的角度来解释一下我们这个简易答题卡识别系统的工作原理。 第一步,导入工具包及一系列的预处理

#Python学习群827513319
import numpy as np
import argparse
import imutils
import cv2
# 设置参数
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--image", default="test_01.png")
args = vars(ap.parse_args())
# 正确答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1} #
def order_points(pts):
   # 一共4个坐标点
   rect = np.zeros((4, 2), dtype = "float32")

   # 按顺序找到对应坐标0,1,2,3分别是 左上,右上,右下,左下
   # 计算左上,右下
   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):
   # 获取输入坐标点
   rect = order_points(pts)
   (tl, tr, br, bl) = rect
   # 计算输入的w和h值
   widthA = np.sqrt(((br[0]-bl[0])** 2) + ((br[1]-bl[1])**2))
   widthB = np.sqrt(((tr[0] -tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
   maxWidth = max(int(widthA), int(widthB))
   heightA = np.sqrt(((tr[0]-br[0])**2)+((tr[1]-br[1])**2))
   heightB = np.sqrt(((tl[0]-bl[0])**2)+((tl[1]-bl[1])**2))
   maxHeight = max(int(heightA), int(heightB))
   # 变换后对应坐标位置
   dst = np.array([
      [0, 0],
      [maxWidth - 1, 0],
      [maxWidth - 1, maxHeight - 1],
      [0, maxHeight - 1]], dtype = "float32")
   # 计算变换矩阵
   M = cv2.getPerspectiveTransform(rect, dst)
   warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
   return warped # 返回变换后结果

def sort_contours(cnts, 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
    boundingBoxes = [cv2.boundingRect(c) for c in cnts]
    (cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes),
                                        key=lambda b: b[1][i], reverse=reverse))
    return cnts, boundingBoxes
def cv_show(name,img):
        cv2.imshow(name, img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

image = cv2.imread(args["image"])
contours_img = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edged = cv2.Canny(blurred, 75, 200)
# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,
   cv2.CHAIN_APPROX_SIMPLE)[1]
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)
      # 准备做透视变换
      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]
thresh_Contours = thresh.copy()
# 找到每一个圆圈轮廓
cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,
   cv2.CHAIN_APPROX_SIMPLE)[1]
cv2.drawContours(thresh_Contours,cnts,-1,(0,0,255),3)
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
# 每排有5个选项
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)):
   cnts = sort_contours(questionCnts[i:i + 5])[0]
   bubbled = None
   for (j, c) in enumerate(cnts): # 遍历每一个结果
      # 使用mask来判断结果
      mask = np.zeros(thresh.shape, dtype="uint8")
      cv2.drawContours(mask, [c], -1, 255, -1) #-1表示填充
      # 通过计算非零点数量来算是否选择这个答案
      mask = cv2.bitwise_and(thresh, thresh, mask=mask)
      total = cv2.countNonZero(mask)
      # 通过阈值判断
      if bubbled is None or total > bubbled[0]:
         bubbled = (total, j)
   # 第二步,与正确答案进行对比
   color = (0, 0, 255)
   k = ANSWER_KEY[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)
cv2.imshow("Input", image)
cv2.imshow("Output", warped)
cv2.waitKey(0)

最终实现的效果如下:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

还在手动阅卷?教你用python实现自动阅卷,解放自己的双手 的相关文章

  • 删除通过pandas创建的html表格的边框

    我正在使用 python 脚本在网页上显示数据框 我用了df to html将我的数据框转换为 HTML 但是 默认情况下 它将边框设置为 0 我尝试通过自定义 css 模板来覆盖它 但它不起作用 这是我的熊猫代码 ricSubscript
  • 使用 os.popen 在 python 中创建列表

    以前 我已经能够使用类似于以下内容的命令创建列表 os popen ls fits gt samplelist 现在 我尝试通过按编号将文件分组来将文件组织到列表中 这些文件的命名如下 Name 0000 J fits Name 0001
  • Python 使用 ssl.getpeercert() 从 URL 获取通用名称

    我正在尝试获取证书颁发者信息 通用名称 但链接中的代码不适用于某些 URL 如何在 python 中获取证书颁发者信息 https stackoverflow com questions 30862099 how can i get cer
  • Python 中的“断言”有什么用?

    什么是assert意思是 它是如何使用的 The assert语句几乎存在于所有编程语言中 它有两个主要用途 它有助于在程序早期发现问题 原因明确 而不是等到其他操作失败时才发现问题 例如 Python 中的类型错误在实际引发错误之前可能会
  • 如果文件不存在,使用 python 添加一行

    我有一个 xml 文件如下
  • 动态添加字段到数据类对象

    我正在编写一个库来访问 REST API 它返回带有用户对象的 json 我将其转换为 dict 然后将其转换为数据类对象 问题是并非所有字段都是固定的 我想动态添加其他字段 未在我的数据类中指定 我可以简单地为我的对象分配值 但它们不会出
  • df.drop(如果存在)

    下面是一个函数 它接受一个文件并删除列名row num start date end date 问题是并非每个文件都有这些列名 因此该函数返回错误 我的目标是更改代码 以便删除这些列 如果存在 但如果某个列不存在则不会返回错误 def re
  • 如何在 python 中使用 requests.post() 进行代理身份验证?

    from bs4 import BeautifulSoup import requests from requests auth import HTTPProxyAuth url http www transtats bts gov Dat
  • 如何访问数据框中的一行嵌套字典

    我有一个 json 文件 如下所示 file name main question no Q 1 question what is answer user John comment It is defined as value number
  • ValueError:展开时包装器循环

    我的示例代码中的 Python3 测试用例 文档测试 失败 但在 Python2 中同样可以正常工作 test py class Test object def init self a 0 self a a def getattr self
  • Django 星级评定系统和 AJAX

    我正在尝试在 Django 网站上实现星级评级系统 在我的模型中存储评级是排序的 就像在页面上显示分数一样 但我希望用户能够对页面进行评分 基本上从 1 到 5 而无需刷新或更改页面 我发现了以下内容 并且喜欢这里明星的风格 http jv
  • 在Python中使用readlines?第一次

    我有一个包含数据列的文本文件 我需要将这些列转换为单独的列表或数组 这就是我到目前为止所拥有的 f open data txt r temp for row in f readlines Data row split temp append
  • 来自 ANTLR 解析树的 Python AST?

    我找到了一个ANTLR4 Python3 语法 https github com bkiers python3 parser 但它会生成一个解析树 该树通常有许多无用的节点 我正在寻找一个已知的包来从该解析树获取 Python AST 这样
  • 像多米诺骨牌一样对 Python 中的元组进行排序/查找顶点连接

    我有一个像这样的整数元组列表 L 1 2 7 6 2 3 8 5 3 8 5 7 每对定义两个顶点之间的边 我想找到顶点连接性 没有循环 元组总是像多米诺骨牌一样唯一地链接起来 因此在这种情况下 排序列表应如下所示 L sorted 1 2
  • manage.pysyncdb 不会为某些模型添加表

    今天我的第二个不太熟练的问题 我有一个 django 项目 其中安装了四个应用程序 当我运行manage py syndb时 它只为其中两个创建表 据我所知 我的任何模型文件都没有问题 并且所有应用程序都在我的设置文件中的 INSTALLE
  • Python 有限边界 Voronoi 单元

    我正在尝试改编我在 stackoverflow 上找到的代码来创建具有有限边界的 voronoi 单元 我发现下面的代码https stackoverflow com a 20678647 2443944 https stackoverfl
  • 从 pandas 数据帧中提取阶段/段以及相应的时间戳

    我有以下数据框 Sleep Stage Time hh mm ss Event Duration s 0 SLEEP S0 23 27 14 SLEEP S0 30 1 SLEEP S0 23 27 44 SLEEP S0 30 2 SLE
  • 在 folium 中显示栅格数据(不处理任何数据值)。

    我正在尝试使用 folium 显示栅格类型数据 numpy 数组 这是我到目前为止所拥有的 import folium from folium plugins import ImageOverlay import numpy as np f
  • 一旦相关命令更改,如何自动运行 py.test?

    通过autonose或nosy 一旦某些测试文件或相关文件发生更改 它将自动运行nosetests 请问py test是否提供了类似的功能 有没有其他工具可以自动激发py test 您可以安装pytest xdist 插件 http pyp
  • 在 Raspberry Pi 4 上的多个输出设备上播放多个 mp3 文件

    我需要 4 8 个同时播放立体声音频音乐频道 连续播放 SD 卡上特定文件夹中的 mp3 音乐 Working 板载 3 5 音频插孔 USB声卡正常播放音乐 Problem 但一旦我尝试在树莓派上使用带有 USB 声卡的第三个音频输出 其

随机推荐