python:Opencv4答题卡检测实例练习

2023-10-17 08:30

本文主要是介绍python:Opencv4答题卡检测实例练习,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

python:Opencv4答题卡检测1实例练习

利用python+opencv对答题卡进行检测,圈出正确的答案,并打印出得分。
原始图像:
在这里插入图片描述
最终结果:
在这里插入图片描述

实现过程

读入图像并转化为灰度图

# 读入图像
img_org = cv2.imread(img_path)
cv_show('img_org', img_org)
img = cv2.cvtColor(img_org, cv2.COLOR_BGR2GRAY)

1.图像预处理

主要对图像进行去噪和透视变换

首先对图像降噪

'''图像预处理'''
# 高斯滤波除去噪点
img = cv2.GaussianBlur(img, (3,3), 0, 0)
cv_show('img', img)

其次是透视变换矫正答题卡部分的图像:做此变化是为了后续的坐标统计

然后为透视变换做些准备
首先是边缘和轮廓检测

# 边缘检测
img = cv2.Canny(img, 20, 200)
cv_show('img_canny', img)# 轮廓检测
contours, __  = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# img_copy = img.copy()
# img_copy = cv2.drawContours(img_copy, contours, -1, (0,255,0), 2)
# cv_show('img_con', img_copy)
'''我的opencv版本是4.1.0轮廓检测的返回值是二元组,opencv3则返回三元组'''

我们需要对找到的轮廓进行筛选,找到答题纸部分的轮廓
一般最大的面积的轮廓就是需要的答题纸部分
对答题纸部分的轮廓进行近似,将不太规整的轮廓转化为四边形

# 遍历所有轮廓找出面积最大的轮廓
if len(contours) > 0:# 根据cv2.contourArea函数进行降序排序cnt = sorted(contours, key= cv2.contourArea, reverse= True)# 遍历轮廓for i in cnt:# 计算周长long = cv2.arcLength(i, closed= True)# 近似轮廓为折线approx = cv2.approxPolyDP(i, 0.02 * long, closed= True)# 检测返回的折线坐标if len(approx) == 4:docCnt = approxprint(docCnt.shape) # >>>(4, 1, 2)break

在进行透视变换前,还需要计算些参数
这里稍微介绍下opencv4中做透视变换的两个函数
1.cv2.getPerspectiveTransform(src,M)
src表示原图像的四边顶点的坐标,M表示为要求变换的四边顶点的坐标,最后得到一个3x3的变换矩阵
2.cv2.warpPerspective(src,M,dsize(height,width)
src为输入图像,M为cv2.getperpectiveTransform()函数的到的变换矩阵),dsize为输出图像的大小
学习笔记4(opencv and python 透视变换(鸟瞰))

按照这两个函数需要的参数,我们需要通过原图的四个顶点坐标、待变换图像四个顶点坐标放入cv2.getPerspectiveTransform(src,M)中得到3x3的变换矩阵。
然后我们需要得到输出图像的尺寸也就是变换后图像的长和宽,再加上上一个函数得到的变换矩阵就可以用cv2.warpPerspective(src,M,dsize(height,width)来得到透视变换后的图像。

'''按照上面的思路,写两个函数来实现透视变换'''
# 获取要变换图像的四点坐标
def get_point(pot):ret = np.zeros((4,2), dtype= 'float32')# 按列相加就是(x+y)横坐标与列坐标相加a = np.sum(pot, axis= 1)# 小的是左上坐标ret[0] = pot[np.argmin(a)]# 大的是右下坐标ret[2] = pot[np.argmax(a )]# 按列相减就是|x-y|横坐标和纵坐标相减a = np.diff(pot, axis= 1)# 小的是右上坐标ret[1] = pot[np.argmin(a)]# 大的是左下坐标ret[3] = pot[np.argmax(a)]return ret# 透视变换
def Perspective_transformation(img, pot):pot = get_point(pot)# 获取坐标p1, p2, p3, p4 = pot# 获取待处理图片的各个宽度和长度width2 = int(np.sqrt((p4[0] - p1[0]) ** 2 + (p4[1] - p1[1]) ** 2))width1 = int(np.sqrt((p3[0] - p2[0]) ** 2 + (p3[1] - p2[1]) ** 2))height1 = int(np.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] + p1[1]) ** 2))height2 = int(np.sqrt((p3[0] - p4[0]) ** 2 + (p3[1] - p4[1]) ** 2))# 得到最大宽度和长度width_max = int(max(width1, width2))height_max = int(max(height1, height2))# 定义处理后图像的坐标pot_aft = np.array(([0,0], [width_max - 1, 0],[width_max - 1, height_max - 1], [0, height_max - 1]),dtype= 'float32')# 获取变换矩阵m = cv2.getPerspectiveTransform(pot, pot_aft)# 透视变换warped = cv2.warpPerspective(img, m, (width_max, height_max))return warped# 进行透视变换修正图像
# 二值图
img_per = Perspective_transformation(img, docCnt.reshape((4,2)))
cv_show('img_per', img_per)
# 彩色图
img_color_per = Perspective_transformation(img_org, docCnt.reshape((4,2)))
cv_show('img_color_per', img_color_per)

得到透视变换后的二值图:
在这里插入图片描述
得到透视变换后的彩色图:
在这里插入图片描述

2.判断选项是否为正确答案

要判断是否为正确答案则需要判断选项,所以要获取选项的特征

在透视变换后的图像上,各个选项的形状大致是差不多的,所以只需要规定一些条件就可以把选项图像筛选出来,在此之前则需要得到选项的尺寸以及坐标,采取的方法为轮廓检测。

contours, __ = cv2.findContours(img_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
answer_pos = []
for i in contours:# 得到各个轮廓的外接矩形的特征x, y, w, h = cv2.boundingRect(i)a = w / h# 筛选条件为长和宽的大小以及比例if w >= 20 and h >= 20 and a >= 0.9 and a <= 1.1:answer_pos.append(i)
# print('answer_pos', answer_pos)

得到选项的图像轮廓之后,需要做一些排序,因为所获取的坐标顺序可能不符合实际选项的顺序。
观察图中选项的位置,同一道题目的不同选项,其纵坐标不同横坐标相同,不同题目同一列的选项纵坐标相同而横坐标不同。所以排序坐标也就可以排序选项的轮廓

# 轮廓排序
def sort_contours(cnt, 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# 获取轮廓信息bound = [cv2.boundingRect(c) for c in cnt]# print(cnt)print(bound)# 按照y纵坐标进行排序(cnt, bound) = zip(*sorted(zip(cnt, bound), key= lambda b: b[1][i], reverse= reverse))print(bound)return  cnt, boundanswer_pos, __ = sort_contours(answer_pos, method= "top-to-bottom")
# print(answer_pos)

我们得到了各个选项的轮廓,也就是位置,接下来要判断那个选项被选了出来,对比答案判断正确与否

在原图,被选出来的选项被涂黑,而在二值图下,被选出来的选项反而比较白(也可能白底黑选项,取决于阈值检测),所以可以通过判断白像素点的数目来得到被选出的那个选项,然后与正确选项对比就可以得到最终得分。

correct = 0
# 利用枚举获取每一行的选项
# np.arange返回的是一个序列
print('len(answer_pos)', len(answer_pos))
for i, j in enumerate(np.arange(0, len(answer_pos), 5)):print(i, j)# 获取每一行的轮廓ants = sort_contours(answer_pos[j: j+5])[0]bubbled = Nonefor q, j in enumerate(ants):print(q)# 制作掩膜mask = np.zeros(img_bin.shape, dtype= 'uint8')mask = cv2.drawContours(mask, [j], -1, 255, -1)# cv_show('mask', mask) 可以去掉注释观察下掩膜。就是选项所在的位置是白色其他为黑色# 保留答案部分img_mask = cv2.bitwise_and(img_bin, img_bin, mask= mask)# cv_show('img_mask', img_mask) 可以去掉注释观察下。就是只保留了掩膜选项部分的图像其他部分为黑# 返回灰度值不为0的像素数目total = cv2.countNonZero(img_mask)if bubbled is None or total > bubbled[0]:# 保存这个选项的白像素数目和在这道题目的选项索引bubbled = (total, q)# 得到正确答案代表的索引answer = ANSWER_KEY[i]if bubbled[1] == answer:correct += 1color = (0, 255, 0)# 标出正确的答案img_color_per = cv2.drawContours(img_color_per, ants, q, color, thickness=2)

得到的正确选项的图像
在这里插入图片描述

3.最后计算正确率,打印到图片上

all_answer = (correct / 5) * 100
print(all_answer)
cv_show('img_color_per', img_color_per)
# 打印最后的得分
img_color_per = cv2.putText(img_color_per, 'your grade:'+str(all_answer)+'%',(10,10), cv2.FONT_HERSHEY_COMPLEX, 0.6, (255,0,0), 1, bottomLeftOrigin= False)
cv_show('img_color_per_grade', img_color_per)

最终成果:
在这里插入图片描述

完整代码

import numpy as np
import cv2img_path = './test_01.png'# 正确答案
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}def cv_show(name, img):cv2.imshow(name, img)cv2.waitKey(0)cv2.destroyAllWindows()# 获取要变换图像的四点坐标
def get_point(pot):ret = np.zeros((4,2), dtype= 'float32')# 按列相加就是(x+y)横坐标与列坐标相加a = np.sum(pot, axis= 1)# 小的是左上坐标ret[0] = pot[np.argmin(a)]# 大的是右下坐标ret[2] = pot[np.argmax(a )]# 按列相减就是|x-y|横坐标和纵坐标相减a = np.diff(pot, axis= 1)# 小的是右上坐标ret[1] = pot[np.argmin(a)]# 大的是左下坐标ret[3] = pot[np.argmax(a)]return ret# 透视变换
def Perspective_transformation(img, pot):pot = get_point(pot)# 获取坐标p1, p2, p3, p4 = pot# 获取待处理图片的各个宽度和长度width2 = int(np.sqrt((p4[0] - p1[0]) ** 2 + (p4[1] - p1[1]) ** 2))width1 = int(np.sqrt((p3[0] - p2[0]) ** 2 + (p3[1] - p2[1]) ** 2))height1 = int(np.sqrt((p2[0] - p1[0]) ** 2 + (p2[1] + p1[1]) ** 2))height2 = int(np.sqrt((p3[0] - p4[0]) ** 2 + (p3[1] - p4[1]) ** 2))# 得到最大宽度和长度width_max = int(max(width1, width2))height_max = int(max(height1, height2))# 定义处理后图像的坐标pot_aft = np.array(([0,0], [width_max - 1, 0],[width_max - 1, height_max - 1], [0, height_max - 1]),dtype= 'float32')# 获取变换矩阵m = cv2.getPerspectiveTransform(pot, pot_aft)# 透视变换warped = cv2.warpPerspective(img, m, (width_max, height_max))return warped
# 轮廓排序
def sort_contours(cnt, 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# 获取轮廓信息bound = [cv2.boundingRect(c) for c in cnt]# print(cnt)print(bound)# 按照y纵坐标进行排序(cnt, bound) = zip(*sorted(zip(cnt, bound), key= lambda b: b[1][i], reverse= reverse))print(bound)return  cnt, bound# 读入图像
img_org = cv2.imread(img_path)
cv_show('img_org', img_org)
img = cv2.cvtColor(img_org, cv2.COLOR_BGR2GRAY)'''图像预处理'''
# 高斯滤波除去噪点
img = cv2.GaussianBlur(img, (3,3), 0, 0)
cv_show('img', img)# 边缘检测
img = cv2.Canny(img, 20, 200)
cv_show('img_canny', img)# 轮廓检测
contours, __  = cv2.findContours(img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# img_copy = img.copy()
# img_copy = cv2.drawContours(img_copy, contours, -1, (0,255,0), 2)
# cv_show('img_con', img_copy)# 遍历所有轮廓找出面积最大的轮廓
if len(contours) > 0:# 根据cv2.contourArea函数进行降序排序cnt = sorted(contours, key= cv2.contourArea, reverse= True)# 遍历轮廓for i in cnt:# 计算周长long = cv2.arcLength(i, closed= True)# 近似轮廓为折线approx = cv2.approxPolyDP(i, 0.02 * long, closed= True)if len(approx) == 4:docCnt = approxprint(docCnt.shape)break# 进行透视变换修正图像
# 二值图
img_per = Perspective_transformation(img, docCnt.reshape((4,2)))
cv_show('img_per', img_per)
# 彩色图
img_color_per = Perspective_transformation(img_org, docCnt.reshape((4,2)))
cv_show('img_color_per', img_color_per)# 转化为二值图
__, img_bin = cv2.threshold(img_per, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
cv_show('img_bin', img_bin)contours, __ = cv2.findContours(img_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
answer_pos = []
for i in contours:x, y, w, h = cv2.boundingRect(i)a = w / hif w >= 20 and h >= 20 and a >= 0.9 and a <= 1.1:answer_pos.append(i)
# print('answer_pos', answer_pos)answer_pos, __ = sort_contours(answer_pos, method= "top-to-bottom")
# print(answer_pos)correct = 0
# 利用枚举获取每一行的选项
# np.arange返回的是一个序列
print('len(answer_pos)', len(answer_pos))
for i, j in enumerate(np.arange(0, len(answer_pos), 5)):print(i, j)# 获取每一行的轮廓ants = sort_contours(answer_pos[j: j+5])[0]bubbled = Nonefor q, j in enumerate(ants):print(q)# 制作掩膜mask = np.zeros(img_bin.shape, dtype= 'uint8')mask = cv2.drawContours(mask, [j], -1, 255, -1)# cv_show('mask', mask)# 保留答案部分img_mask = cv2.bitwise_and(img_bin, img_bin, mask= mask)# cv_show('img_mask', img_mask)# 返回灰度值不为0的像素数目total = cv2.countNonZero(img_mask)if bubbled is None or total > bubbled[0]:# 保存这个选项的白像素数目和在这道题目的选项索引bubbled = (total, q)# 得到正确答案代表的索引answer = ANSWER_KEY[i]if bubbled[1] == answer:correct += 1color = (0, 255, 0)# 标出正确的答案img_color_per = cv2.drawContours(img_color_per, ants, q, color, thickness=2)all_answer = (correct / 5) * 100
print(all_answer)
cv_show('img_color_per', img_color_per)
# 打印最后的得分
img_color_per = cv2.putText(img_color_per, 'your grade:'+str(all_answer)+'%',(10,10), cv2.FONT_HERSHEY_COMPLEX, 0.6, (255,0,0), 1, bottomLeftOrigin= False)
cv_show('img_color_per_grade', img_color_per)

结语

若有什么错误,还请评论指出,十分感谢。
共同进步

这篇关于python:Opencv4答题卡检测实例练习的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/224186

相关文章

python: 多模块(.py)中全局变量的导入

文章目录 global关键字可变类型和不可变类型数据的内存地址单模块(单个py文件)的全局变量示例总结 多模块(多个py文件)的全局变量from x import x导入全局变量示例 import x导入全局变量示例 总结 global关键字 global 的作用范围是模块(.py)级别: 当你在一个模块(文件)中使用 global 声明变量时,这个变量只在该模块的全局命名空

综合安防管理平台LntonAIServer视频监控汇聚抖动检测算法优势

LntonAIServer视频质量诊断功能中的抖动检测是一个专门针对视频稳定性进行分析的功能。抖动通常是指视频帧之间的不必要运动,这种运动可能是由于摄像机的移动、传输中的错误或编解码问题导致的。抖动检测对于确保视频内容的平滑性和观看体验至关重要。 优势 1. 提高图像质量 - 清晰度提升:减少抖动,提高图像的清晰度和细节表现力,使得监控画面更加真实可信。 - 细节增强:在低光条件下,抖

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

【学习笔记】 陈强-机器学习-Python-Ch15 人工神经网络(1)sklearn

系列文章目录 监督学习:参数方法 【学习笔记】 陈强-机器学习-Python-Ch4 线性回归 【学习笔记】 陈强-机器学习-Python-Ch5 逻辑回归 【课后题练习】 陈强-机器学习-Python-Ch5 逻辑回归(SAheart.csv) 【学习笔记】 陈强-机器学习-Python-Ch6 多项逻辑回归 【学习笔记 及 课后题练习】 陈强-机器学习-Python-Ch7 判别分析 【学

nudepy,一个有趣的 Python 库!

更多资料获取 📚 个人网站:ipengtao.com 大家好,今天为大家分享一个有趣的 Python 库 - nudepy。 Github地址:https://github.com/hhatto/nude.py 在图像处理和计算机视觉应用中,检测图像中的不适当内容(例如裸露图像)是一个重要的任务。nudepy 是一个基于 Python 的库,专门用于检测图像中的不适当内容。该

基于 YOLOv5 的积水检测系统:打造高效智能的智慧城市应用

在城市发展中,积水问题日益严重,特别是在大雨过后,积水往往会影响交通甚至威胁人们的安全。通过现代计算机视觉技术,我们能够智能化地检测和识别积水区域,减少潜在危险。本文将介绍如何使用 YOLOv5 和 PyQt5 搭建一个积水检测系统,结合深度学习和直观的图形界面,为用户提供高效的解决方案。 源码地址: PyQt5+YoloV5 实现积水检测系统 预览: 项目背景

RabbitMQ练习(AMQP 0-9-1 Overview)

1、What is AMQP 0-9-1 AMQP 0-9-1(高级消息队列协议)是一种网络协议,它允许遵从该协议的客户端(Publisher或者Consumer)应用程序与遵从该协议的消息中间件代理(Broker,如RabbitMQ)进行通信。 AMQP 0-9-1模型的核心概念包括消息发布者(producers/publisher)、消息(messages)、交换机(exchanges)、

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip