《深度学习》【项目】OpenCV 答题卡识别 项目流程详解

news2024/11/29 10:50:38

目录

一、上半部分

1、定义展示图像函数

2、预处理

运行结果:

3、轮廓检测并绘制

运行结果:

4、排序轮廓

5、定义排序点函数

6、透视变换

1)定义透视变换处理函数

2)执行透视变换

运行结果:

7、二值化处理

运行结果:

8、绘制圆圈的轮廓

运行结果:

二、下半部分

1、筛选选项的圆圈并排序

2、标记正误

运行结果:

3、打印正确率并批注

运行结果:


一、项目上半部分

1、定义展示图像函数

def cv_show(name,img):
    cv2.imshow(name,img)
    cv2.waitKey(0)

2、预处理

# 预处理
image = cv2.imread(r'./images/test_01.png')   # 读取待识别答题卡图片
contours_img = image.copy()   # 生成图片副本
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)  # 灰度图
blurred = cv2.GaussianBlur(gray,(5,5),0)  # 使用高斯滤波对灰度图进行处理,减小图像噪声和细节,高斯核大小为5*5,0表示自动计算标准差
cv_show('blurred',blurred)   # 展示
edged = cv2.Canny(blurred,75,200)  # 对处理完的图像进行边缘检测,灰度低于75的将其更改为0,高于200的设置为255
cv_show('edged',edged)
        运行结果:

3、轮廓检测并绘制

cnts = cv2.findContours(edged.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]   # 对副本图像进行轮廓检测,cv2.RETR_EXTERNAL只检测最外层,cv2.CHAIN_APPROX_SIMPLE只保留轮廓端点
# cv2.findContours返回3个值,原图、轮廓信息、层次结构,其受OpenCV版本影响
cv2.drawContours(contours_img,cnts,-1,(0,0,255),4)  # 在原图的副本上绘制轮廓cnts,-1表示绘制所有轮廓
cv_show('contours',contours_img)  # 展示
        运行结果:

4、排序轮廓

docCnt = None
cnts = sorted(cnts,key = cv2.contourArea,reverse=True)  # 对检测到的轮廓进行排序,排序方式为轮廓面积,降序形式
for c in cnts:  # 遍历排序后的每一个轮廓
    peri = cv2.arcLength(c,True)   # 计算每一个轮廓的周长,True表示闭合
    approx = cv2.approxPolyDP(c,0.002*peri,True)  # 多边形逼近,逼近的精度为周长的0.2%,返回逼近后的多边形轮廓
    if len(approx)==4:  # 判断多边形是否是4边的
        docCnt = approx  # 如果是将其传入docCnt,然后中断循环,因为精度很高,所以认定这个多边形为检测到的轮廓外接多边形
        break

5、定义排序点函数

        输入轮廓外接四边形的四个顶点,对其进行排序,返回排序后的点坐标

def order_points(pts):   # 对输入的四个点按照左上、右上、右下、左下进行排序
    rect = np.zeros((4,2),dtype='float32')   # 创建一个4*2的数组,用来存储排序之后的坐标位置
    # 按顺序找到对应坐标0123分别是左上、右上、右下、左下
    s = pts.sum(axis=1)   # 对pts矩阵的每个点的x y相加
    rect[0] = pts[np.argmin(s)]    # np.argmin(s)表示数组s中最小值的索引,表示左上的点的坐标
    rect[2] = pts[np.argmax(s)]    # 返回最大值索引,即右下角的点坐标
    diff = np.diff(pts,axis=1)   # 对pts矩阵的每一行的点求差值
    rect[1] = pts[np.argmin(diff)]   # 差值最小的点为右上角点
    rect[3] = pts[np.argmax(diff)]   # 差值最大表示左下角点
    return rect   # 返回排序好的四个点的坐标

6、透视变换

        1)定义透视变换处理函数
def four_point_transform(image,pts):  # 对图像进行透视变换,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')
    # 图像透视变换 cv2.getPerspectiveTransform(src,dst[,solveMethod])→ M获得转换之间的关系
    # cv2.warpPerspective(src, Mp, dsizel, dstl, flagsl, borderModel, borderValue]]1])- dst
    # #参数说明:
    # src:变换前图像四边形顶点坐标/第2个是原图
    # MP:透视变换矩阵,3行3列
    # dsize:输出图像的大小,二元元组(width,heiqht)
    M = cv2.getPerspectiveTransform(rect,dst)  # 根据原始点和变换后的点计算透视变换矩阵 M
    warped = cv2.warpPerspective(image,M,(maxWidth,maxHeight))  # 对原始图像,针推变换矩阵和输出图像大小进行透视变换,返回变换后的图片
    # 返回变换后的结果
    return warped
        2)执行透视变换
warped_t = four_point_transform(image,docCnt.reshape(4,2))  # 传入原图,docCnt.reshape(4,2)表示将轮廓的4个点排成一列,进行透视变换,返回透视变换后的图片
warped_new = warped_t.copy()
cv_show('wraped',warped_t)   # 展示透视变换后的图片
                运行结果:

7、二值化处理

warped = cv2.cvtColor(warped_t,cv2.COLOR_BGR2GRAY)  # 将经过透视变换的图像转换为灰度图
# 阈值处理
thresh = cv2.threshold(warped,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)[1]  # 对灰度图做二值化,THRESH_BINARY_INV反二值化处理,较亮的区域变为黑,较暗的区域变为白,THRESH_OTSU自动计算阈值,返回两个值,一个是阈值,一个是处理后的图像,
cv_show('thresh',thresh)  # 展示二值化处理后的图像
        运行结果:

8、绘制圆圈的轮廓

thresh_Contours = thresh.copy()
# 找到每一个圆圈轮廓
cnts = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[1]   # 对二值化处理后的图像进行轮廓检测,检测最外层轮廓,只返回轮廓端点信息,返回值有三个见上文
warped_Contours = cv2.drawContours(warped_t,cnts,-1,(0,255,0),3)  # 绘制轮廓
cv_show('wraped_Contours',warped_Contours)
        运行结果:

二、项目下半部分

1、筛选选项的圆圈并排序

questionCnts=[]
for c in cnts:  # 遍历绘制的每一个轮廓
    (x,y,w,h) = cv2.boundingRect(c)   # 计算轮廓矩形,返回边界矩形的坐标x,y,宽高
    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]

2、标记正误

for (q,i) in enumerate(np.arange(0,len(questionCnts),5)):   # 生成一个0-25不包括25,步长为5的数组,即0,4,8,16,24五个值,enumerate生成可迭代对象,返回索引和值
    cnts = sort_contours(questionCnts[i:i+5])[0]  # 遍历每五个轮廓传入函数进行从左往右排序,索引0返回排序后的端点信息
    bubbled = None
    # 遍历每一个结果
    for (j,c) in enumerate(cnts):  # 遍历排序后的一行轮廓圆的索引和值
        # 创建掩膜mask
        mask = np.zeros(thresh.shape,dtype='uint8')   # 创建和thresh一样布局的0矩阵,thresh是二值化处理完的图像
        cv2.drawContours(mask,[c],-1,255,-1)   # 绘制掩膜,[c]为单个轮廓的列表,第一个-1表示绘制轮廓列表中的所有轮廓,轮廓为单通道,所以255表示白色,第二个-1表示全部填充
        cv_show('mask',mask)   # 绘制掩膜
        # 通过计算非零点数量来算是否选择这个答案
        # 利用掩膜(mask)进行“与”操作,只保留mask位置中的内容
        thresh_mask_and = cv2.bitwise_and(thresh,thresh,mask=mask)  # 按位与操作,按位与两张图片相同的地方,这里是thresh图像本身,返回结果为掩膜中为白色的区域
        cv_show('thresh_mask_and',thresh_mask_and)   # 展示保留掩膜白色区域的thresh图
        total = cv2.countNonZero(thresh_mask_and)   # 计算非0像素的数量,因为遍历的是一排选项轮廓的每一个轮廓,所以计算出非0像素值数量后再判断对应的选项
        if bubbled is None or total > bubbled[0]:
            bubbled = (total,j)  # 将轮廓的值和索引以元组的形式保存的bubbled,通过遍历不停更新筛选
        # 对比正确答案
    color = (0,0,255)   # 初始化颜色为红色
    k = ANSWER_KEY[q]   # q为上述遍历出来的每一个选项的索引,筛选出来对应答案k

    if k == bubbled[1]:  # 判断正确
        color = (0,255,0)   # 如果正确则为绿色
        correct += 1   # 正确就+1,总数为5,最后求正确率

    cv2.drawContours(warped_new,[cnts[k]],-1,color,3)   # 在warped_new为原图副本上绘制轮廓,[cnts[k]]为轮廓列表,-1表示绘制所有轮廓
    cv_show('warpeding',warped_new)
        运行结果:

3、打印正确率并批注

# 此处代码无缩进
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)   # 在原图副本上绘制文本,文本内容为正确率百分比,绘制的位置坐标为(10,30),FONT_HERSHEY_SIMPLEX为文本字体类型,0.9为缩放因子
cv2.imshow('Original',image)   # 展示原图
cv2.imshow('Exam',warped_new)   # 展示绘制完的图
cv2.waitKey(0)
        运行结果:

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

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

相关文章

python 边际分布图

import seaborn as snspenguins sns.load_dataset("penguins") colors {"Gentoo": #AE5259, "Adelie": #CF992C, "Chinstrap": #6B9DAA}# 分类散点图 sns.jointplot(datapenguins, x"bill_length_mm", y"bill_depth_…

Linux的kafka安装部署

1.kafka是一个分布式的,去中心化的,高吞吐低延迟,订阅模式的消息队列系统 确保要有jdk与zookeeper安装配置 2.下载kafka安装包 http://archive.apache.org/dist/kafka/2.4.1/kafka_2.12-2.4.1.tgz 此时可以wget http://archive.apache.org/dist/kafka/2.4.1/kafka_2.12-2.4.…

数字后端零基础入门系列 | Innovus零基础LAB学习Day1

一 Floorplan 数字IC后端设计如何从零基础快速入门&#xff1f;(内附数字IC后端学习视频&#xff09; Lab5-1这个lab学习目标很明确——启动Innovus工具并完成设计的导入。 在进入lab之前&#xff0c;我们需要进入我们的FPR工作目录。 其中ic062为个人服务器账户。比如你端…

Graph Contrastive Learningwith Reinforcement Augmentation

IJCAI24 推荐指数&#xff1a; #paper/⭐⭐⭐ 领域&#xff1a;图增强强化学习 胡言乱语&#xff1a; 不愧是清华组的论文&#xff0c;这个实验的目的是利用强化学习去生成对比学习的增强视图。但是&#xff0c;其仍然有一些小问题&#xff1a;其本质实际是对以往的图增强方法…

vrrp实验

配置Trunk和Access [SW3]int e0/0/1 [SW3-Ethernet0/0/1]p l a [SW3-Ethernet0/0/1]p d v 10 [SW3-Ethernet0/0/1]int e0/0/2 [SW3-Ethernet0/0/2]p l a [SW3-Ethernet0/0/2]p d v 10 [SW3-Ethernet0/0/2]int e0/0/3 [SW3-Ethernet0/0/3]p l a [SW3-Ethernet0/0/3]p d v 20 [S…

postgresql 安装

一、下载 PostgreSQL: File Browser 下载地址 PostgreSQL: File Browser 上传到服务器,并解压 二、安装依赖 yum install -y perl-ExtUtils-Embed readline-devel zlib-devel pam-devel libxml2-devel libxslt-devel openldap-devel 创建postgresql 和目录 useradd …

位运算 -- 力扣

1486. 数组异或操作 1486. 数组异或操作 根据题意&#xff0c;使用参数 n 和 start 生成一个数组&#xff0c;最后返回数组中所有元素按位异或&#xff08;XOR&#xff09;后得到的结果。 首先&#xff0c;异或运算的规则是&#xff0c;当同一位的二进制数不同时结果为 1&…

【Golang】Go 语言中的 time 包详解:全面掌握时间处理与应用

在 Go 语言中&#xff0c;time 包提供了强大的时间处理功能&#xff0c;适用于各种场景&#xff1a;获取当前时间、格式化和解析时间、计算时间间隔、设置定时器、处理超时等。在开发过程中&#xff0c;熟练掌握 time 包能够帮助我们轻松处理时间相关的操作&#xff0c;尤其是定…

小程序之获取用户头像与昵称

html 这个是通过一个点击事件进行显示的,下面是效果图,获取头像最关键的是 open-type"chooseAvatar" ,获取昵称最关键的是 type"nickname" ,大家多加注意i哦 <!-- 大的输入调取 --><view class"myis_box" wx:if"{{showMyisBox}}…

做ppt用什么软件好?5个办公必备的ppt工具推荐!

ppt用什么软件做&#xff1f; 相信很多人看到这个&#xff0c;会第一时间想到Microsoft Office套件包含的幻灯片软件Powerpoint&#xff0c;它的名声甚广&#xff0c;以至于某种程度上成了PPT的代名词。 在Powerpoint之外&#xff0c;这些年也陆续诞生了各式各样的PPT软件&am…

CocosCreator 快速部署 TON 游戏:Web2 游戏如何使用 Ton支付

在本篇文章中&#xff0c;我们将继续探讨如何使用 Cocos Creator 开发 Telegram 游戏&#xff0c;重点介绍如何集成 TON 支付功能。通过这一教程&#xff0c;开发者将学会如何在游戏中接入 TON Connect&#xff0c;实现钱包连接、支付以及支付后的校验流程&#xff0c;最终为 W…

YOLO11改进|SPPF篇|引入SPPFCSPC金字塔结构

目录 一、【SPPFCSPC】金字塔结构1.1【SPPFCSPC】金字塔结构介绍1.2【SPPFCSPC】核心代码 二、添加【SPPFCSPC】金字塔结构2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【SPPFCSPC】金字塔结构 1.1【SPPFCSPC】金字塔结构介绍 下图是…

vue后台管理系统从0到1(1)

文章目录 vue后台管理系统从0到1&#xff08;1&#xff09;nvm 下载安装1.卸载nodejs环境2.安装nvm 安装nrm vue后台管理系统从0到1&#xff08;1&#xff09; 第一节主要是先安装我们的工具nvm nodejs版本管理工具&#xff0c;和nrm镜像管理工具 nvm 下载安装 nvm是一款管理…

网络流量预测的学习——持续更新ing

文章目录 前情提要何为网络流量网络流量分析&#xff08;NTA&#xff09;网络流量组成网络流量处理过程 预测网络流量的工具wiresharkbrim&#xff08;zui&#xff09; 机器学习中的网络流量预测参考文章 前情提要 记录一些有关网络流量的学习 何为网络流量 网络流量是指在计…

【D3.js in Action 3 精译_033】4.1.0 DIY 实战:如何通过学习 d3.autoType 函数深度参与 D3 生态建设

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可…

又被特斯拉演了?继续“画饼式”发布Robotaxi,产业链静观其变

9月底的暴涨后&#xff0c;A股资产正经历回调&#xff0c;科技板块变现参差。不过&#xff0c;无人驾驶领域的预期依然很强。 10月10日科技股全线调整之际&#xff0c;无人驾驶板块盘中的巨幅震荡拉升就是典型的预热动作。东箭科技、天龙股份等多只智能驾驶个股涨停。核心驱动…

ACR、PZ、AMC仪表接线说明及通讯协议解析

1.ACR/PZ/AMC多功能表接线说明 三相三线接线说明 使用场合负载是平衡系统&#xff0c;并且没有零线的场合。 1. 端子号1&#xff0c;2为辅助电源&#xff1a; 如上图&#xff0c;接入相电压220V输入。其中辅助电源的火线加装5A保险丝&#xff0c;零线直接接到零排上。 2&am…

iPhone使用指南:如何在没有备份的情况下从 iPhone 恢复已删除的照片

本指南将向您展示如何在没有备份的情况下从 iPhone 恢复已删除的照片。我们所有人在生活中的某个时刻都一定做过一些愚蠢的事情&#xff0c;例如从手机或电脑中删除一些重要的东西。这是很自然的&#xff0c;没有什么可羞耻的。您可能在辛苦工作一天后回来。当突然想看一些照片…

C++开发五子棋游戏案例详解

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

JavaSE——集合3:ArrayList、Vector

目录 一、ArrayList的注意事项 二、ArrayList的扩容机制(重要) 三、Vector底层结构和源码剖析 1.Vector类的定义说明 2.Vector底层也是一个对象数组 3.Vector是线程同步的&#xff0c;即线程安全&#xff0c;Vector类的操作方法带有synchronized 4.在开发中&#xff0c…