当前位置: 首页 > news >正文

15- 答题卡识别及分数判定项目 (OpenCV系列) (项目十五)

项目要点

  • 图片读取 : img = cv2.imread('./images/test_01.png')
  • 灰度图:  gray = cv2.cvtColor(img, 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)[-2]    # 两个返回值 contours, hierarchy
  • 描绘轮廓cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 2)
  • 轮廓面积排序:  cnts = sorted(cnts, key = cv2.contourArea, reverse = True)
  • 计算轮廓周长:  perimeter = cv2.arcLength(c, True)
  • 得到近似轮廓:  approx = cv2.approxPolyDP(c, 0.15 * perimeter, True)
  • 计算变换矩阵:  M = cv2.getPerspectiveTransform(rect, dst)  # dst 为目标值
  • 通过坐标透视变换转换: warped = cv2.warpPerspective(image,M,(max_width, max_height))  # 注意传参
  • ret, thresh1 = cv2.threshold(img,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)  二值化处理图片
  • 计算非零个数, 先做与运算:  mask = cv2.bitwise_and(thresh, thresh, mask = mask)
  • 计算非零个数,选中的选项, 非零个数比较多:  total = cv2.countNonZero(mask)
  • 画出正确选项cv2.drawContours(warped, [cnts[k]], -1, color, 3)
  • cv2.putText(warped, f'{score:.2f}',(210,36), cv2.FONT_HERSHEY_SIMPLEX,0.9, (0,0,255), 2)  # 在图片上体现分数
  • 显示图片cv2.imshow(name, img)


1 提取答题卡部分图像内容

1.1 读取图片

  • 定义显示函数
def cv_show(name, img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()
import cv2
import numpy as np# 读图片,预处理
img = cv2.imread('./images/test_01.png')
# 转变为黑白图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv_show('img', img)

1.2 边缘检测

  • 定义显示函数
# 高斯模糊去噪点
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 边缘检测
edged = cv2.Canny(blurred, 75, 200)
cv_show('img', edged)

        

1.3 检测轮廓

# 检测轮廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
# 画轮廓会修改原图
contours_img = img.copy()
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 2)
cv_show('contours_img', contours_img)

1.4 找出答题卡轮廓

  • 本次其实原本也只检测到了一个轮廓.

# 确保拿到的轮廓是答题卡的轮廓
if cnts:# 对轮廓面积排序cnts = sorted(cnts, key = cv2.contourArea, reverse = True)# 遍历每一个轮廓for c in cnts:# 计算周长perimeter = cv2.arcLength(c, True)# 得到近似轮廓approx = cv2.approxPolyDP(c, 0.15 * perimeter, True)# 应该只剩下四个角的坐标print(len(c))print(len(approx))if len(approx) == 4:# 保存approxdocCnt = approx # 找到后直接退出break
print(docCnt)
contours_img = img.copy()
cv2.drawContours(contours_img, docCnt, -1, (0, 0, 255), 10)
cv_show('contours_img', contours_img)

     

 1.5 透视变换

# 找到进行透视变换的点
# 要求变换矩形的四个坐标
# 先对获取到的4个角点坐标排序
# 排序功能封装为一个函数
def order_points(pts):# 创建全为0 的矩阵, 来接收找到的坐标rect = np.zeros((4, 2), dtype = 'float32')s = pts.sum(axis = 1)# 左上角的坐标一定是X,Y相加最小的,右下为最大的rect[0] = pts[np.argmin(s)]rect[2] = pts[np.argmax(s)]# 右上角的x,y 相减的差值一定是最小的# 左下角的x,y 相减,差值一定是最大的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)   # 调取函数: order_points(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)max_width = 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)max_wdith = (int(widthA), int(widthB))max_height =max(int(heightA), int(heightB))# 构造变换后的空间坐标dst = np.array([[0, 0],[max_width - 1, 0],[max_width - 1, max_height -1],[0, max_height - 1]], dtype = 'float32')# 计算变换矩阵M = cv2.getPerspectiveTransform(rect, dst)  # dst 为目标值# 透视变换warped = cv2.warpPerspective(image, M, (max_width, max_height))  # 注意传参return warped# 进行透视变换
warped = four_point_transform(gray, docCnt.reshape(4, 2))
cv_show('warped', warped)

2 处理答题卡

2.1 二值化图像  (两个返回值)

# 二值化   # +inv 黑白颠倒
# ret, thresh1 = cv2.threshold(img,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)
thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv_show('thresh', thresh)

2.2 找每一个圆圈的轮廓

# 找每一个圆圈的轮廓
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
thresh_contours = thresh.copy()
cv2.drawContours(thresh_contours, cnts, -1, 255, 3)  # 255表示填充白色,填充为0时为后图
cv_show('thresh_contours', thresh_contours)

 

 2.3 找出特定轮廓  (选项)

# 遍历所有轮廓, 找到特点宽高和特定比例的轮廓, 及圆圈的轮廓
question_cnts = []
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:question_cnts.append(c)
print(len(question_cnts))       # 25

3 分数判定

3.1 轮廓排序封装函数

# 设计做法看轮廓,找出选择的答案
# 轮廓排序封装函数
def sort_contours(cnts, method = 'left-to-right'):reverse = False# 排序的时候取x轴数据, i= 0, 取y轴数据 i=1i = 0if method == 'right-to-left' or method == 'bottom-to-top':reverse = Trueif method == 'top-to-bottom' or method == 'bottom-to-top':i = 1# 计算每个轮廓的外接矩形bounding_boxes = [cv2.boundingRect(c) for c in cnts]   # 计算轮廓的垂直边界最小矩形,矩形是与图像上下边界平行的(cnts, bounding_boxes) = zip(*sorted(zip(cnts, bounding_boxes),key = lambda a: a[1][i], reverse = reverse))# a 表示zip(cnt, bounding_boxes)# rint(bounding_boxes[1])return cnts, bounding_boxes# 按照从上到下的排序 :question_cnts
question_cnts = sort_contours(question_cnts, method = 'top-to-bottom')[0]
print(len(question_cnts))   # 25

3.2 找出选择答案, 然后评分

# 正确答案
ANSWER_KEY = {0:1, 1:2, 2:1, 3:2, 4:1}   # 1,4,0,2,1
correct = 0
# enumerate,遍历了所有元素,并从零开始的计为每个元素生成索引, q为index
for (q, i) in enumerate(np.arange(0, 25, 5)):  first_num = True# 每次取出5个,再按x大小排序# 每5个一组进行排序, 0表示cnts 原轮廓, 默认排序'left-to-right'cnts = sort_contours(question_cnts[i: i+ 5])[0]  print('-------------------', len(cnts))   # 5个选项bubbled = None  # 冒泡# 遍历每一个结果for (j, c) in enumerate(cnts):#使用掩膜,即maskmask = np.zeros(thresh.shape, dtype = 'uint8')cv2.drawContours(mask, [c], -1, 255, -1)    # 255表示填充白色# 计算非零个数, 先做与运算mask = cv2.bitwise_and(thresh, thresh, mask = mask)# cv_show('mask', mask)# 计算非零个数,选中的选项, 非零个数比较多, 没选中的非零个数小一些total = cv2.countNonZero(mask)# print('******', total,j)if bubbled is None or total > bubbled[0]:bubbled = (total, j)    # 正确选项的 total 值较高color = (0, 0 ,255)k = ANSWER_KEY[q]    # enumerate 的序列值# print(len(cnts))# 判断是否正确if k == bubbled[1] and first_num == True:correct += 1first_num = Falseprint('正确选项: ',k)cv2.drawContours(warped, [cnts[k]], -1, color, 3)  # 画出正确选项# 计算得分
score = int((correct / 5)* 100)
print(f'score:{score:.2f} 分')warped_copy = warped.copy()
cv2.putText(warped_copy, f'{score:.2f}', (210, 36), cv2.FONT_HERSHEY_SIMPLEX,0.9, (0, 0, 255), 2)
cv_show('result', warped_copy)

相关文章:

15- 答题卡识别及分数判定项目 (OpenCV系列) (项目十五)

项目要点 图片读取 : img cv2.imread(./images/test_01.png)灰度图: gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)高斯模糊: blurred cv2.GaussianBlur(gray, (5, 5), 0) # 去噪点边缘检测: edged cv2.Canny(blurred, 75, 200)检测轮廓: cnts cv2.findContours(e…...

LeetCode 热题 C++ 146. LRU 缓存

力扣146 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中&#xff0c;则返回关键字的值&#xff0c;否…...

Java线程池使用与原理解析(线程池优点、使用方法、参数含义及线程池运转机制)

为什么要使用线程池&#xff1f; JDK1.5后JUC包添加了线程池相关接口&#xff0c;在Java诞生之初并没有线程池这个概念。刚开始Java程序都是自行创建线程去处理任务。随着应用使用的线程越来越多&#xff0c;JDK开发者们发现有必要使用一个统一的类来管理这些线程&#xff0c;…...

mybatis入门配置

mybatis mybatis是一款持久层框架&#xff0c;用于简化JDBC开发 持久层&#xff1a;负责将数据保存到数据库的那一层代码JavaEE的三层架构&#xff1a;表现层、业务层、持久层、&#xff0c;就相当与mvc设计模式过程中的Controller、service、dao 1.创建一个maven模块&#…...

黑客入门(超级详细版)

据我了解&#xff0c;“黑客”大体上应该分为“正”、“邪”两类&#xff0c;正派黑客依靠自己掌握的知识帮助系统管理员找出系统中的漏洞并加以完善&#xff0c;而邪派黑客则是通过各种黑客技能对系统进行攻击、入侵或者做其他一些有害于网络的事情&#xff0c;因为邪派黑客所…...

Java多线程(三)---synchronized、Lock和volatile

Java内存模型&#xff08;非JVM&#xff09;Java内存模型(Java Memory Model简称JMM)&#xff0c;是一种共享内存模型&#xff0c;是多线程的东西&#xff0c;并不是JVM&#xff08;Java Virtual Machine(Java虚拟机)的缩写&#xff09;&#xff0c;这是俩玩意儿&#xff01;&a…...

JVM-Java内存区域

运行时数据区&#xff1a;1、程序计数器&#xff1a;当前线程所执行的字节码指令的行号指示器。在Java虚拟机的概念模型里&#xff0c;字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令&#xff0c;它是程序控制流的指示器&#xff0c;分支、循环…...

毕业季,毕业论文查重,paper系列五个免费查重网站推荐

推荐五个常用的免费查重网站 注意&#xff1a; &#xff08;1&#xff09;这些网站基本上都可以通过关注公众号或者转发等来获取免费查重机会&#xff0c;但有些也会有字数限制。每个网站的数据库可能不同&#xff0c;所以建议大家多换几个平台查一查&#xff0c;反正是免费的…...

破解票房之谜:为何高票房电影绕不过“猫眼们”?

如此火爆的春节档很多&#xff0c;如此毁誉参半的春节档鲜有。2023开年&#xff0c;集齐张艺谋、沈腾的《满江红》&#xff0c;以及有票房前作打底的《流浪地球2》接连两部春节档电影票房进入前十&#xff0c;为有些颓靡的中国电影市场注入了一针“强心剂”。与票房同样热闹起来…...

订单服务-----遇到的问题及解决方案

订单服务的问题及解决方案问题1&#xff1a;Feign远程调用时丢失请求头编辑出现这个Feign远程调用时丢失请求头的问题是因为Feign在远程调用的时候会创建一个新的请求&#xff0c;但是这个新的请求里啥都没有&#xff0c;没有cookie值&#xff0c;而这个cookie值里有成功登录后…...

项目经理如何度量项目?及项目度量指标实例【静说】

度量项目是项目经理的一个重要职责&#xff0c;通过度量项目&#xff0c;项目经理可以了解项目的进展情况&#xff0c;及时发现问题并采取相应的措施&#xff0c;以确保项目能够按时、按质、按预算完成。 分享给大家一些常见的项目度量指标&#xff1a; 1. 项目进度&#xff…...

我们应该如何优雅的处理 React 中受控与非受控

引言 大家好&#xff0c;我是19组清风。有段时间没有和大家见面了&#xff0c;最近因为有一些比较重要的事情&#xff08;陪女朋友和换了新公司&#xff09;在忙碌所以销声匿迹了一小段时间&#xff0c; 后续会陆陆续续补充之前构建 & 编译系列中缺失的部分&#xff0c;提…...

力扣热题100Day06:20. 有效的括号,21. 合并两个有序链表,22. 括号生成

20. 有效的括号 题目链接&#xff1a;20. 有效的括号 - 力扣&#xff08;Leetcode&#xff09; 思路&#xff1a;使用栈 &#xff08;1&#xff09;遇到左括号就将其对应的右括号压入到栈中 &#xff08;2&#xff09;如果遇到右括号 a. 如果弹出的元素与当前不等&#xff…...

【Yolov5】保姆级别源码讲解之-推理部分detect.py文件

推理部分之detect.py文件讲解1.下载Yolov5的源码2. 主函数讲解3.文件标头的注释4. main函数的5. run函数5.1 第一块参数部分5.2第二块&#xff0c;传入数据预处理5.3 第三块创建文件夹5.4 第四块 加载模型的权重5.5 第五块 Dataloader 加载模块5.6 第六块 推理部分 Run inferen…...

无重叠区间-力扣435-java贪心策略

一、题目描述给定一个区间的集合 intervals &#xff0c;其中 intervals[i] [starti, endi] 。返回 需要移除区间的最小数量&#xff0c;使剩余区间互不重叠 。示例 1:输入: intervals [[1,2],[2,3],[3,4],[1,3]]输出: 1解释: 移除 [1,3] 后&#xff0c;剩下的区间没有重叠。…...

Python使用VTK对容积超声图像进行体绘制(三维重建)

目录VTK简介什么是体绘制&#xff1f;体绘制效果图流程CodeQ&AReferenceVTK简介 VTK&#xff08;Visualization Toolkit&#xff09;是一个用于3D计算机图形学、图像处理和可视化的开源软件包。它包括一组C类和工具&#xff0c;可以让用户创建和处理复杂的3D图形和数据可视…...

JAVA设计模式之工厂模式讲解

目录 前言 开始表演 前言 Java中使用工厂模式的主要原因是为了实现代码的灵活性和可维护性。工厂模式是一种创建型设计模式&#xff0c;它提供了一种将对象的创建和使用进行分离的方式。具体来说&#xff0c;工厂模式可以将对象的创建过程封装在一个独立的工厂类中&#xff…...

近万字概述L3及以上自动驾驶故障运行和故障安全机制

本文描述了对ADS的FO和FS机制的评估方法。当系统不能按预期运行时,ADS将使用FO和FS机制。这些机制使ADS能够在最大程度上达到使车辆及其乘员脱离危险的MRC。定义、测试和验证实现MRC的FO和FS策略是确保ADS安全运行和部署的重要步骤。 MRC在SAE J3016中被定义为: 用户或ADS在…...

kafka入门到精通

文章目录一、kafka概述&#xff1f;1.定义1.2消息队列1.2.1 传统消息队列的使用场景1.2.2 消息队列好处1.2.3 消息队列两种模式1.3 kafka基础架构二、kafka快速入门1.1使用docker-compose安装kafka1.2测试访问kafka-manager1.3 查看kafka版本号1.4 查看zookeeper版本号1.5 扩展…...

es-09模糊查询

模糊查询 前缀搜索&#xff1a;prefix 概念&#xff1a;以xx开头的搜索&#xff0c;不计算相关度评分。 注意&#xff1a; 前缀搜索匹配的是term&#xff0c;而不是field。前缀搜索的性能很差前缀搜索没有缓存前缀搜索尽可能把前缀长度设置的更长 语法&#xff1a; GET <ind…...

DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径

目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...

3.3.1_1 检错编码(奇偶校验码)

从这节课开始&#xff0c;我们会探讨数据链路层的差错控制功能&#xff0c;差错控制功能的主要目标是要发现并且解决一个帧内部的位错误&#xff0c;我们需要使用特殊的编码技术去发现帧内部的位错误&#xff0c;当我们发现位错误之后&#xff0c;通常来说有两种解决方案。第一…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

是否存在路径(FIFOBB算法)

题目描述 一个具有 n 个顶点e条边的无向图&#xff0c;该图顶点的编号依次为0到n-1且不存在顶点与自身相连的边。请使用FIFOBB算法编写程序&#xff0c;确定是否存在从顶点 source到顶点 destination的路径。 输入 第一行两个整数&#xff0c;分别表示n 和 e 的值&#xff08;1…...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

《C++ 模板》

目录 函数模板 类模板 非类型模板参数 模板特化 函数模板特化 类模板的特化 模板&#xff0c;就像一个模具&#xff0c;里面可以将不同类型的材料做成一个形状&#xff0c;其分为函数模板和类模板。 函数模板 函数模板可以简化函数重载的代码。格式&#xff1a;templa…...

2025年渗透测试面试题总结-腾讯[实习]科恩实验室-安全工程师(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 腾讯[实习]科恩实验室-安全工程师 一、网络与协议 1. TCP三次握手 2. SYN扫描原理 3. HTTPS证书机制 二…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...