【进阶OpenCV】 (9)--摄像头操作--->答题卡识别改分项目

news2024/11/25 10:58:29

文章目录

  • 项目:答题卡识别改分
    • 1. 图片预处理
    • 2. 描绘轮廓
    • 3. 轮廓近似
    • 4. 透视变换
    • 5. 阈值处理
    • 6. 找每一个圆圈轮廓
    • 7. 将每一个圆圈轮廓排序
    • 8. 找寻所填答案,比对正确答案
      • 8.1 思路
      • 8.2 图解
      • 8.3 代码体现
    • 9. 计算正确率
  • 总结

项目:答题卡识别改分

本篇我们来介绍,如何识别一张答题卡,并为其答案验证对错,进行打分。

在这里插入图片描述

思路

  1. 图片预处理
  2. 边缘检测
  3. 描绘轮廓
  4. 找到每一个圆圈轮廓
  5. 比对答案
  6. 计算正确率

1. 图片预处理

  1. 对识别的图片进行灰度处理
  2. 使用高斯滤波cv2.GaussianBlur()函数平滑处理去噪
  3. 接着使用cv2.Canny()函数再进行边缘检测,检测图片中的边缘。
def cv_show(name,img):
    cv2.imshow(name,img)
    cv2.waitKey(0)
"""-----预处理-----"""
image = cv2.imread(r'./images/test_01.png')
contours_img = image.copy()
gray = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)
blurred = cv2.GaussianBlur(gray,(5,5),0)
cv_show('blurred',blurred)
edged = cv2.Canny(blurred,75,200)
cv_show('edged',edged)

在这里插入图片描述

2. 描绘轮廓

使用cv2.findContours()函数查找图像的轮廓,再通过cv2.drawContours()函数将其绘制出来:

"""-----轮廓检测-----"""
cnts = cv2.findContours(edged.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
cv2.drawContours(contours_img,cnts,-1,(0,0,255),3)
cv_show('contours_img',contours_img)

在这里插入图片描述

3. 轮廓近似

通过轮廓近似找到答题卡,为接下来的透视变换做准备:

判断条件:当近似轮廓可以用四个点绘制出时(因为答题卡是长方形的),将其保留。

"""-----根据轮廓大小进行排序,准备透视变换-----"""
cnts = sorted(cnts,key=cv2.contourArea,reverse=True)
docCnt = None
for c in cnts: # 遍历每一个轮廓
    peri = cv2.arcLength(c,True)
    approx = cv2.approxPolyDP(c,0.02 * peri,True) #轮廓近似

    if len(approx) == 4:
        docCnt = approx
        break

通过以上代码,得到答题卡的四个角点坐标docCnt

4. 透视变换

定义两个函数:

  • order_point(pts):将给定的四个点(通常是从图像中检测到的轮廓点或角点)按照特定的顺序排列:左上角(tl)、右上角(tr)、右下角(br)和左下角(bl)。
  • four_point_transform(image,pts):这个函数使用 order_point 函数得到的点来对输入图像进行透视变换,使得这四个点映射到一个矩形上。
def order_point(pts):
    rect = np.zeros((4,2),dtype="float32")
    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_point(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
"""-----执行透视变换-----"""
warped_t = four_point_transform(image,docCnt.reshape(4,2))
warped_new = warped_t.copy()
cv_show('warped',warped_t)
warped = cv2.cvtColor(warped_t,cv2.COLOR_BGR2GRAY)

在这里插入图片描述

这样就将答题卡完整的取出来了。

5. 阈值处理

非黑即白,使得到的答题卡更清晰。

"""-----阈值处理-----"""
thresh = cv2.threshold(warped,0,255,
                       cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)
thresh_Contours = thresh.copy()

在这里插入图片描述

6. 找每一个圆圈轮廓

  1. 通过cv2.findContours()函数,查找轮廓,并将其绘制出来。
  2. 遍历每一个轮廓,将符合条件的圆圈轮廓存放进questionCnts列表中。
"""-----找到每一个圆圈轮廓-----"""
cnts = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]
warped_Contours = cv2.drawContours(warped_t,cnts,-1,(0,255,0),1)
cv_show('warped_Contours',warped_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)

在这里插入图片描述

7. 将每一个圆圈轮廓排序

定义了一个名为 sort_contours的函数,用于根据指定的方法对轮廓(contours)进行排序:

功能:这个函数可以用于在图像处理中,根据对象的水平或垂直位置对检测到的对象进行排序,这在处理图像中的多个对象时非常有用。

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))#zip(*...)使用星号操作符解包排序后的元组列表,并将其重新组合成两个列表:一个包含所有轮廓,另一个包含所有边界框。
    return cnts,boundingBoxes
questionCnts = sort_contours(questionCnts,method = "top-to-bottom")[0]

8. 找寻所填答案,比对正确答案

8.1 思路

  • 思路:我们发现,答题卡中,每一行共五个圆圈代表一道题的答案,我们要找到填涂的圆圈,然后再找到正确答案的圆圈,比对二者是不是同一个。

    所以我们要从左到右每五个圆圈轮廓寻找一次填涂答案的位置,然后比较一次。那么,我们怎么从五个轮廓中找到填涂的那个呢?

    通过cv2.countNonZero()函数方法,作用是计算数组(通常是图像或图像的一部分)中非零元素(像素点)的数量。填涂后的轮廓位置轮廓内都是有颜色的,通过掩膜(对每一个轮廓进行掩膜)方法与每个轮廓做”与“运算,然后计算非零元素的数量,五个中,谁最多,谁就是填涂的。

    接着通过它在五个中的位置,与正确答案的位置作比较,看看一不一致,判断对错。

8.2 图解

  • 图解
  1. 第一个轮廓的掩膜”与“运算

在这里插入图片描述

得到一个清晰的轮廓,计算它的非零像素点的数量。

  1. 填涂点的掩膜”与”运算

在这里插入图片描述

对比上下两个轮廓的非零像素点的数量就可以明显锁定填涂位置。

8.3 代码体现

"""-----比对答案-----"""
ANSWER_KEY = {0:1,1:4,2:0,3:3,4:1} # 正确答案
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)
        cv_show('mask',mask)
        # 通过计算非零点数量来算是否选择这个答案
        # 利用掩膜进行‘与’操作,只保留mask位置中的内容
        thresh_mask_and = cv2.bitwise_and(thresh,thresh,mask=mask)
        cv_show('thresh_mask_and',thresh_mask_and)

        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,3)
    cv_show("warpeding",warped_new)

判断结果:

在这里插入图片描述

9. 计算正确率

"""-----计算分数-----"""
score = (correct / 5.0) * 100
print("[INFO] score:{:.2f}%".format(score))
cv2.putText(warped_new,"{:.2f}%".format(score),(10,30),
            cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,0,255),2)
cv2.imshow("origimal",image)
cv2.imshow('Exam',warped_new)
cv2.waitKey(0)
----------------
[INFO] score:80.00%

在这里插入图片描述

总结

本篇介绍了:如何对答题卡进行识别并计算准确率。

要点知识:边缘检测、轮廓近似、透视变换以及掩膜。

过程:1. 图片预处理 -----> 2. 描绘轮廓 -----> 3. 轮廓近似 -----> 4. 透视变换 -----> 5. 阈值处理 -----> 6. 找每一个圆圈轮廓 -----> 7. 将每一个圆圈轮廓排序 -----> 8. 比对正确答案 -----> 9. 计算正确率.

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数论与同余 - 离散数学系列(七)

目录 1. 整数的性质 整除与因数 最大公约数与最小公倍数 2. 欧几里得算法 算法步骤 3. 模运算与同余 模运算 同余关系 同余的性质 4. 数论在密码学中的应用 RSA 加密算法 5. 实际应用场景 1. 数字签名 2. 哈希函数与数据完整性 3. 密钥交换 6. 例题与练习 例题…

Java:方法详解

目录 一.什么是方法(method) 二.方法定义 三.方法中实参和形参的关系 四.方法重载 五.递归 一.什么是方法(method) 方法就是一个代码片段&#xff0c;再C语言中我们曾经学过一个类似的方式——函数&#xff0c;他们都是将具有独立功能的代码组织成一个整体&#xff0c;形成…

论文阅读 BLIP-2

Bootstrapping Language-Image Pre-training with Frozen Image Encoders and Large Language Models 使用冻结的图像编码器和大型语言模型进行语言-图像预训练的引导 BLIP-2 通过一个轻量级的查询变换器弥合了模态之间的差距。 Querying Transformer 第一阶段通过冻结的图像编…

高纯PGME和PGMEA市场规模:2023年全球市场规模为6.43亿美元

研究对象&#xff1a;高纯PGME和PGMEA行业 高纯PGME&#xff08;丙二醇甲醚&#xff09;和PGMEA&#xff08;丙二醇甲醚醋酸酯&#xff09;是重要的有机溶剂&#xff0c;广泛应用于半导体和面板制造领域&#xff0c;尤其是在光刻胶和清洗剂中。高纯度的PGME和PGMEA对于确保电子…

软件测试面试题600多条及答案

这些问题都是软件测试领域常见的面试问题&#xff0c;以下是一些可能的答案&#xff1a; 什么是软件测试&#xff1f; 软件测试是一系列活动&#xff0c;旨在评估软件产品的质量和性能&#xff0c;以确保它符合规定的需求和标准。它包括执行程序或系统以验证其满足规定需求的过…

javaWeb-Mybatis操作

1.删除 2.新增 注意&#xff1a; 1. 使用对象来接收 2.插入的数据的名字&#xff0c;要和对象的属性名一致 3.新增(主键返回) 4.更新 5.查询&#xff08;根据id查询&#xff09; 会把查询到的数据映射到字段上。 6.查询(条件查询) %张%-->%${name}%

前端必知必会-Bootstrap 5 工具提示Tooltip

文章目录 Bootstrap 5 工具提示如何创建工具提示定位工具提示 总结 Bootstrap 5 工具提示 工具提示组件是一个小的弹出框&#xff0c;当用户将鼠标指针移到元素上时会出现&#xff1a; 如何创建工具提示 要创建工具提示&#xff0c;请将 data-bs-toggle“tooltip” 属性添加…

人工智能长期记忆的新突破:HippoRAG的创新框架

人工智能咨询培训老师叶梓 转载标明出处 大模型&#xff08;LLMs&#xff09;在预训练后&#xff0c;如何有效地整合大量新经验&#xff0c;同时避免灾难性遗忘&#xff0c;一直是人工智能领域的难题。尽管已有的检索增强生成&#xff08;RAG&#xff09;方法为LLMs提供了长期…

云轴科技ZStack入选信通院《高质量数字化转型产品及服务全景图》AI大模型图谱

近日&#xff0c;由中国互联网协会中小企业发展工作委员会主办的“2024大模型数字生态发展大会暨铸基计划年中会议”在北京成功召开。会上发布了中国信通院在大模型数字化等领域的多项工作成果&#xff0c;其中重点发布了《高质量数字化转型产品及服务全景图&#xff08;2024上…

解决重写QSilder::sliderPress后点击位置与滑块显示位置不一样的问题

如下代码所示&#xff0c;我是用的是事件过滤器&#xff0c;也可以重写QSlider。 bool KuGouApp::eventFilter(QObject *watched, QEvent *event) {if(watched ui->progressSlider) {if (event->type()QEvent::MouseButtonPress) //判断类型{auto mouseEvent…

【封装案例】点和圆的关系

文章目录 前言1、point.h文件2、circle.h文件3、point.cpp文件4、circle.cpp文件5、主函数和全局函数6、测试 前言 本篇主要是用C实现一个判断点和圆关系的程序。 思路&#xff1a;已知点和圆心的坐标&#xff0c;通过比较两点间的距离和圆半径的大小&#xff0c;判断两者的关…

ubuntu下实时查看CPU,内存(Mem)和GPU的利用率

一、实时查看CPU和内存&#xff08;Mem&#xff09;利用率 htop官网&#xff1a;htop - an interactive process viewer sudo apt-get install htop htop ①. 顶部状态栏&#xff08;System Metrics Overview&#xff09; 这个区域显示系统的全局资源使用情况&#xff0c;包括…

18770 差值最大

### 思路 为了找到两个数x和y使得x - y的值最大&#xff0c;并且x在y的右侧&#xff0c;我们可以使用以下方法&#xff1a; 1. 从右向左遍历数组&#xff0c;记录当前遍历到的最大值max_right。 2. 对于每个元素a[i]&#xff0c;计算max_right - a[i]&#xff0c;并更新最大差…

我的研究生周报模板

2022.12.12——2022.12.18周报 本周计划 文献阅读&#xff1a;&#xff08;说明是打算读哪方面的论文&#xff09; 实验&#xff1a;&#xff08;打算做什么实验&#xff0c;目的是什么&#xff09; 项目&#xff1a;&#xff08;打算完成项目中哪一部分&#xff09; 本周进…

fastadmin 多商户模式下侧边栏跳转路径BUG

记录&#xff1a;仅作自己项目记录&#xff0c;在一个域名下部署多套项目时&#xff0c;若是多商户模式项目会出现跳转路径问题。 修改 \manystore\library\Auth.php 文件的 getSidebar 方法 // 1 改为&#xff1a; $v[url] isset($v[url]) && $v[url] ? $v[url] :…

Reqable抓包演示

1.Reqable下载 官网&#xff1a;Reqable 下载对应平台版本即可。 多个客户端。 2.使用教程 打开后如下图所示 根据提示安装完证书后即可使用。 这里以小猿口算的练习模式的抓包为例 1.打开雷电模拟器 在模拟器上安装Reqable&#xff0c;并安装证书&#xff0c;一定要安装…

【C++】基于红黑树封装set和map

&#x1f680;个人主页&#xff1a;小羊 &#x1f680;所属专栏&#xff1a;C 很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~ 目录 前言一、更高维度的泛型二、模版参数三、比较逻辑的重写四、迭代器4.1 const迭代器4.2 重载4.3 - -重载 五、完整代…

EXCEL SUM系列求和函数大全

大家好&#xff0c;这里是效率办公指南&#xff01; &#x1f4ca; 在数据分析中&#xff0c;求和是一项基础而频繁的操作。Excel 提供了一系列求和函数&#xff0c;可以帮助我们快速汇总数据。今天&#xff0c;我们将介绍 Excel 中常用的求和函数&#xff0c;包括它们的语法和…

java 自定义填充excel并导出

首先在resources下面放一个excel模板 1. 方法签名和请求映射 RequestMapping(value "/ExportXls") public ResponseEntity<byte[]> rwzcExportXls(HttpServletRequest request, RequestBody JSONArray jsonArray) throws IOException { RequestMapping(val…

YOLO11改进 | 注意力机制 | 结合静态和动态上下文信息的注意力机制

秋招面试专栏推荐 &#xff1a;深度学习算法工程师面试问题总结【百面算法工程师】——点击即可跳转 &#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 上下文Transformer&#xff08;CoT&…