目录

一、基本流程

二、代码实现

1. 导入工具包和定义常量

2. 辅助函数定义

2.1 坐标点排序函数

2.2 透视变换函数

2.3 轮廓排序函数

2.4 图像显示函数

3. 主程序处理流程

3.1 图像预处理

3.2 轮廓检测与透视变换

3.3 阈值处理与选项检测

3.4 答案识别与评分


我们拿到类似于下图的答题卡照片,我们要对其进行识别判断答案的正确与最后的评分。

        OpenCV在答题卡识别中发挥着重要作用,它能够通过一系列图像处理技术,实现对答题卡的自动识别,并进行答题结果的统计。以下是一个基于OpenCV的答题卡识别的基本流程和关键步骤:

一、基本流程

  1. 图片读取:首先,使用OpenCV读取答题卡的图像文件。
  2. 图片预处理:对读取的图像进行预处理,包括灰度化、滤波去噪、边缘检测等,以突出答题卡中的关键信息。
  3. 轮廓检测:通过轮廓检测算法,找到答题卡中各个选项或区域的轮廓。
  4. 透视变换:对检测到的轮廓进行透视变换,以校正答题卡的视角,使其更加符合后续处理的需求。
  5. 阈值处理:对校正后的图像进行阈值处理,将图像转换为二值图像,便于后续的分析和识别。
  6. 答题区域识别:在二值图像中,识别出答题卡上的各个答题区域。
  7. 答题结果判断:根据答题区域的填充情况,判断答题结果,并与正确答案进行对比,计算答题正确率。

二、代码实现

1. 导入工具包和定义常量

import numpy as np
import cv2ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}  # 正确答案,键是题目索引,值是正确选项索引
  • 导入了numpy用于数值计算,cv2用于图像处理
  • 定义了标准答案,假设共有 5 道题,每道题的正确选项分别是索引 1、4、0、3、1

2. 辅助函数定义

2.1 坐标点排序函数
def order_points(pts):# 初始化4个坐标点的存储数组rect = np.zeros(shape=(4, 2), dtype="float32")# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下s = pts.sum(axis=1)  # 对pts矩阵的每一行进行求和操作,(x+y)rect[0] = pts[np.argmin(s)]  # 左上点:x+y最小rect[2] = pts[np.argmax(s)]  # 右下点:x+y最大diff = np.diff(pts, axis=1)  # 对pts矩阵的每一行进行求差操作,(y-x)rect[1] = pts[np.argmin(diff)]  # 右上点:y-x最小rect[3] = pts[np.argmax(diff)]  # 左下点:y-x最大return rect

这个函数用于将四个点按 "左上、右上、右下、左下" 的顺序排列,为后续透视变换做准备。

2.2 透视变换函数
def four_point_transform(image, pts):# 获取排序后的坐标点rect = order_points(pts)(tl, tr, br, bl) = rect# 计算宽度和高度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

这个函数实现了图像的透视变换,能将倾斜的答题卡转换为正视角度的矩形图像,方便后续处理。

2.3 轮廓排序函数
def sort_contours(cnts, method='left-to-right'):reverse = Falsei = 0if method == 'right-to-left' or method == 'bottom-to-top':reverse = Trueif method == 'top-to-bottom' or method == 'bottom-to-top':i = 1  # 按y坐标排序# 获取边界框并排序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

这个函数用于对轮廓进行排序,可以按不同方向(左右、上下)排序,方便按题目顺序处理选项。

2.4 图像显示函数
def cv_show(name, img):cv2.imshow(name, img)cv2.waitKey(0)

封装了 OpenCV 的图像显示功能,方便调试过程中查看图像。

3. 主程序处理流程

3.1 图像预处理
# 读取图像
image = cv2.imread(r'./images/test_03.png')
contours_img = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 转为灰度图
blurred = cv2.GaussianBlur(gray, (5, 5), 0)  # 高斯模糊去噪
edged = cv2.Canny(blurred, 75, 200)  # Canny边缘检测

这部分将彩色图像转为灰度图,进行模糊处理以减少噪声,然后使用 Canny 算法检测边缘。

3.2 轮廓检测与透视变换
# 轮廓检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3)
docCnt = None# 根据轮廓大小进行排序,找到答题卡的四个角
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 = approxbreak# 执行透视变换,将答题卡转正
warped_t = four_point_transform(image, docCnt.reshape(4, 2))
warped_new = warped_t.copy()
warped = cv2.cvtColor(warped_t, cv2.COLOR_BGR2GRAY)

这部分先检测图像中的所有轮廓,然后找到最大的四边形轮廓(假设是答题卡),最后通过透视变换将答题卡转换为正视角度。

3.3 阈值处理与选项检测
# 阈值处理,将图像转为黑白二值图
thresh = cv2.threshold(warped, 0, 255,cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]# 找到每一个圆圈轮廓(选项)
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
warped_Contours = cv2.drawContours(warped_t, cnts, -1, (0, 255, 0), 1)# 筛选出符合条件的选项轮廓
questionCnts = []
for c in cnts:(x, y, w, h) = cv2.boundingRect(c)ar = w / float(h)# 根据实际情况指定标准(大小和长宽比)if w >= 20 and h >= 20 and 0.9 <= ar <= 1.1:questionCnts.append(c)

这部分将图像转为黑白二值图,然后检测所有可能是选项的圆形轮廓,并根据大小和形状筛选出有效的选项。

3.4 答案识别与评分
# 按照从上到下进行排序(题目顺序)
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]  # 对每个题的5个选项按左右排序bubbled = None# 遍历每个选项,判断哪个被选中for (j, c) in enumerate(cnts):# 创建掩膜mask = np.zeros(thresh.shape, dtype="uint8")cv2.drawContours(mask, [c], -1, color=255, thickness=-1)# 计算非零点数量来判断是否选择这个答案thresh_mask_and = cv2.bitwise_and(thresh, thresh, mask=mask)total = cv2.countNonZero(thresh_mask_and)# 保存灰度值最大的选项(被选中的)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_new, [cnts[k]], -1, color=color, thickness=3)# 计算并显示分数
score = (correct / 5.0) * 100
print("[INFO] score: {:.2f}%".format(score))
cv2.putText(warped_new, "{:.2f}%".format(score), org=(10, 30),fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=0.9, color=(0, 0, 255), thickness=2)# 显示最终结果
cv2.imshow("Original", image)
cv2.imshow("Exam", warped_new)
cv2.waitKey(0)

这部分是核心的答案识别和评分逻辑:

  1. 按题目顺序(从上到下)处理每个题目
  2. 对每个题目的选项(从左到右)进行分析
  3. 通过计算每个选项区域的像素值判断哪个选项被选中
  4. 与标准答案对比,正确的标记为绿色,错误的标记为红色
  5. 计算并显示最终得分

整个流程利用计算机视觉技术实现了自动识别答题卡答案并评分的功能,适用于标准化考试的自动阅卷场景

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/922371.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/922371.shtml
英文地址,请注明出处:http://en.pswp.cn/news/922371.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java面试问题记录(四)

四、设计模式1、设计模式6大原则1&#xff09;单一职责(一个类和方法只做一件事)、2&#xff09;里氏替换(多态&#xff0c;子类可扩展父类)、3&#xff09;依赖倒置(细节依赖抽象&#xff0c;下层依赖上层)、4&#xff09;接口隔离(建立单一接口)、迪米特原则(最少知道&#x…

高等教育学

高等教育学第一章 高等教育与高等教育学第二章 高等教育发展史2-1西方高等教育发展史2-2中国高等教育发展史第三章 高等教育理念3.1-王一军-高等教育理念的构成要素3.2-王一军-高等教育理念的主要流派第四章 高等学校教育4.1 高等学校教育制度4.2-陈何芳-高等教育办学体制 &…

unordered_map使用MFC的CString作为键值遇到C2056和C2064错误

文章目录unordered_map使用MFC的CString作为键值遇到C2056和C2064错误问题出现的背景解决方案总结unordered_map使用MFC的CString作为键值遇到C2056和C2064错误 问题出现的背景 在我的一个老工程项目中&#xff0c;使用C的std::unordered_map时&#xff0c;使用了MFC的CStrin…

Maven 本地仓库的 settings.xml 文件

本地仓库目录位置&#xff1a;C:/用户/用户名/.m2/repository 需要修改配置&#xff0c;具体的修改方法请看 ↓↓↓ 2024版 IDEA 用 Maven 创建 java 项目&#xff08;Maven 安装和配置&#xff09; <?xml version"1.0" encoding"UTF-8"?><!…

vue动画内置组件

文章目录vue动画的官方类名EnterLeaveTransition组件注意事项触发实例TransitionGroup组件注意事项触发机制实例拓展vue动画的官方类名 如下来自vue官方文档&#xff0c;提供了dom元素&#xff0c;插入Enter和删除Leave的类名 Enter v-enter-from&#xff1a;进入动画的起始…

软考中级信息安全与病毒防护知识点

### 一、核心知识点梳理这部分内容可以大致分为三个方面&#xff1a;**信息安全基本概念**、**加解密技术** 和 **恶意代码&#xff08;病毒&#xff09;防护**。#### 1. 信息安全的基本目标&#xff08;CIA三元组&#xff09; 这是所有信息安全问题的基石&#xff0c;必须熟练…

数组存储 · 行主序与列主序 | 应用 / 基地址 / 选择策略

注&#xff1a;本文为 “数组存储 行主序与列主序” 相关合辑。 英文引文&#xff0c;机翻未校。 中文引文&#xff0c;略作重排。 未整理去重&#xff0c;如有内容异常&#xff0c;请看原文。 Row major and Column Major Address calculations 按行主序和按列主序的地址计算…

在 CentOS 中安装 VirtualBox 增强功能的步骤

很好&#xff0c;你看到 /run/media/asfor/VBox_GAs_7.2.2&#xff0c;这说明你已经在 VirtualBox 中挂载了“增强功能&#xff08;Guest Additions&#xff09;”光盘&#xff0c;接下来只要手动安装就可以启用共享剪贴板、全屏分辨率、鼠标无缝移动等功能。&#x1f4dd; 在 …

Python快速入门专业版(三十):函数进阶:函数嵌套与作用域(内部函数访问外部变量)

目录引一、函数嵌套&#xff1a;在函数内部定义函数1. 基本语法与调用方式示例1&#xff1a;简单的函数嵌套结构2. 嵌套函数的典型应用&#xff1a;隐藏辅助逻辑示例2&#xff1a;用嵌套函数隐藏辅助逻辑二、嵌套函数的作用域&#xff1a;变量访问规则1. 内部函数访问外部函数的…

C++数组与字符串:从基础到实战技巧

C中的数组和字符串是处理数据集合和文本的基础工具。数组用于存储相同类型的元素集合&#xff0c;而字符串则专门用于处理文本数据。C提供了两种主要的字符串处理方式&#xff1a;C风格字符串&#xff08;字符数组&#xff09;和C的std::string类。 &#x1f4ca; 1. 数组 (Arr…

艾迈斯欧司朗推出首款高功率多芯片激光器封装

在投影显示领域掀起技术革新的浪潮中&#xff0c;艾迈斯欧司朗犹如一位技艺精湛的工匠&#xff0c;精心打造出Vegalas Power系列高功率激光二极管的首颗明珠——PLPM7_455QA激光器。这款采用多颗GaN基功率激光器集成封装的新品&#xff0c;在短脉冲周期内绽放出42W的璀璨光芒&a…

机器视觉中的工业相机接口该如何选择?

工业相机接口&#xff1a;数据传输的“高速公路”&#xff0c;选对了才够快 在机器视觉系统里&#xff0c;工业相机就像“眼睛”&#xff0c;而接口则是连接“眼睛”与“大脑”&#xff08;后端处理系统&#xff09;的“高速公路”。这条“路”的宽窄、长短、抗干扰能力&#x…

[数据结构——lesson10.2堆排序以及TopK问题]

目录 前言 学习目标 堆排序 TopK问题&#xff1a; 解法一&#xff1a;建立N个数的堆 解法二&#xff1a;建立K个数的堆&#xff08;最优解&#xff09; 完整代码 结束语 前言 上节内容我们详细讲解了堆[数据结构——lesson10.堆及堆的调整算法]&#xff0c;接下来我们…

使用HTTPS 服务在浏览器端使用摄像头的方式解析

1.方式1 // vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import basicSsl from vitejs/plugin-basic-sslexport default defineConfig({plugins: [vue(),basicSsl({name: test,domains: [192.168.15.166, localhost], // 添加您的IPc…

上下文管理器和异步I/O

目录 一、上下文管理器 1.1 定义 1.2 特点 1.3 适用场景 1.4 具体实现 1.5 具体实例 1.5.1 文件管理器 1.5.2 线程锁释放资源 二、异步I/O 2.1 定义 2.2 特点 2.3 实现方式 2.4 适用场景 高并发网络服务&#xff1a;Web服务器、API服务等需要处理大量并发连接 2…

LabVIEW信号监测与分析

借助 LabVIEW 平台&#xff0c;生成含正弦波与噪声的信号&#xff0c;经频谱分析等处理&#xff0c;结合动态限值判断信号是否超限&#xff0c;广泛用于音频、振动等领域的信号监测&#xff0c;助力高效开展信号分析与质量把控。概念说明系统围绕信号的生成、处理、分析及监测展…

MySQL数据库与表的创建、修改及数据操作指南

精选专栏链接 &#x1f517; MySQL技术笔记专栏Redis技术笔记专栏大模型搭建专栏Python学习笔记专栏深度学习算法专栏 欢迎订阅&#xff0c;点赞&#xff0b;关注&#xff0c;每日精进1%&#xff0c;与百万开发者共攀技术珠峰 更多内容持续更新中&#xff01;希望能给大家带来…

​new species of flying reptile1 discovered in Scotland​

Pterosaur: new species of flying reptile1 discovered in Scotland 苏格兰斯凯岛发现新翼龙物种 考古学家们在苏格兰斯凯岛发现了一个新的翼龙物种。这种独特的飞行爬行动物生活在1.68 – 1.66亿年前。 This flying reptile soared over the heads of dinosaurs2 when Scotla…

03 节点行为

审批流程图如下图&#xff0c;在此流程图中&#xff0c;存在两个UserTask节点&#xff0c;第一个节点是主管审批&#xff0c;第二个节点是产品经理审批&#xff0c;两个节点中间有一个排他网关&#xff0c;此网关用来对主管审批的结果进行判断&#xff0c;如果主管审批通过&…

深度卷积生成对抗网络详解与实现

深度卷积生成对抗网络详解与实现 0. 前言 1. 网络架构 1.1 批归一化 1.2 激活 1.3 上采样 2. 构建 DCGAN 2.1 生成器 2.2 判别器 2.3 训练 DCGAN 0. 前言 深度卷积生成对抗网络 (Deep Convolutional Generative Adversarial Network, DCGAN) 是基于生成对抗网络 (Generative A…