opencv学习:使用OpenCV进行图像中四边形区域的透视变换和答案评分完整代码实现

embedded/2024/10/19 0:52:42/

简介

        使用OpenCV进行实时视频流中的四边形区域抠图主要涉及到图像处理和计算机视觉中的几个关键概念:轮廓检测、多边形近似、透视变换和图像掩码。这个算法的目标是从视频流中实时检测出四边形区域,并将该区域从背景中分离出来,以便进行进一步的处理或分析。

算法原理

1. 图像获取

首先,使用OpenCV的cv2.imread函数获取图片。

# 灰度图
image=cv2.imread(r'test_01.png')

2. 图像预处理

对于每一帧图像,首先将其从BGR颜色空间转换为灰度图,因为灰度图足以进行边缘检测,且计算量更小。然后,对灰度图像应用高斯模糊,以减少图像噪声和细节,这有助于后续的边缘检测。

contours_img=image.copy()
gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
# 高斯模糊,以减少图像噪声
blurred=cv2.GaussianBlur(gray,(5,5),0)

3. 边缘检测

使用Canny算法对模糊后的图像进行边缘检测。Canny算法是一种流行的边缘检测方法,它通过高斯滤波、梯度计算、非极大值抑制和双阈值检测来识别图像中的边缘。

# 边缘检测算法来检测图像中的边缘
edged=cv2.Canny(blurred,75,200)
cv_show('edged',edged)

4. 轮廓检测

使用cv2.findContours函数检测边缘图像中的轮廓。这个函数返回图像中的所有轮廓,每个轮廓都是一个点集,这些点大致描述了轮廓的形状。

#找到最大轮廓,进行绘制
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)

5. 轮廓筛选和排序

由于我们感兴趣的是四边形区域,因此需要对检测到的轮廓进行筛选。通常,这可以通过计算轮廓的面积并选择面积超过某个阈值的轮廓来实现。然后,根据面积对轮廓进行排序,选择最大的轮廓进行进一步处理。

doccnt=Nonecnts=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

6. 透视变换

一旦找到了四边形区域,就需要将其“平整”。这通过透视变换(又称为四点变换)实现,它是一种映射,可以将图像中的一个四边形区域映射到一个新的平面上。使用cv2.getPerspectiveTransformcv2.warpPerspective函数来计算透视变换矩阵,并将图像中的四边形区域变换为正面视角。

# 透视变换
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)
thresh=cv2.threshold(warped,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)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),1)
cv_show('warped_contours',warped_contours)

7. 在二值化图像中找到所有可能的问题轮廓,运用图像掩码和抠图

在透视变换后,我们得到了一个“平整”的四边形区域图像。为了得到这个区域的清晰图像,可以创建一个掩码,然后使用cv2.bitwise_and函数将原始图像与掩码进行与运算,从而只保留四边形区域内的像素。

correct=0
for (q,i) in enumerate(np.arange(0,len(question),5)):cnts=sort_contours(question[i:i+5])[0]bubbled=Nonefor (j,c) in enumerate(cnts):mask=np.zeros(thresh.shape,dtype='uint8')cv2.drawContours(mask,[c],-1,255,-1)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=amkey[q]if k==bubbled[1]:color=(0,255,0)correct+=1cv2.drawContours(warped_new,[cnts[k]],-1,color,3)cv_show('warpeding',warped_new)

8.显示结果

score=(correct/5.0)*100
print('{:.2f}%'.format(score))
cv2.putText(warped_new,'{:.2f}%'.format(score),(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,0,255),2)
cv2.imshow('exam',warped_new)
cv2.waitKey(0)

运行结果

完整代码

import numpy as np
import cv2
amkey={0:1,1:4,2:0,3:3,4:1}def cv_show(name,img):cv2.imshow(name,img)cv2.waitKey(0)#     # 按下ESC键(ASCII码为27),则跳出循环#     if k==27:#         breakdef order_points(pts):rect = np.zeros((4,2),dtype='float32')   # 创建一个4x2的数组,指定了数组中元素的数据类型为32位浮点数# 按顺序找到对应坐标0123分别是左上、右上、右下、左下s = pts.sum(axis=1)   # 对pts矩阵的每一行进行求和操作rect[0] = pts[np.argmin(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 rectdef four_point_transform(image,pts):# 获取输入坐标点rect = order_points(pts)(tl,tr,br,bl) = rect#tl 是左上角点,tr 是右上角点,br 是右下角点,bl 是左下角点。# 计算两个可能的宽度,取这两个宽度的最大值作为新图像的宽度。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])→ MP获得转换之间的关系# cy2.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 warpeddef 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=1boundingBoxes=[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# 灰度图
image=cv2.imread(r'test_01.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)
cv_show('edged',edged)
#找到最大轮廓,进行绘制
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)
doccnt=Nonecnts=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()
cv_show('warped',warped_t)#二值化处理
warped=cv2.cvtColor(warped_t,cv2.COLOR_BGR2GRAY)
thresh=cv2.threshold(warped,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)[1]
cv_show('thresh',thresh)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),1)
cv_show('warped_contours',warped_contours)question=[]
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.append(c)
question=sort_contours(question,method='top-to-bottom')[0]
correct=0
for (q,i) in enumerate(np.arange(0,len(question),5)):cnts=sort_contours(question[i:i+5])[0]bubbled=Nonefor (j,c) in enumerate(cnts):mask=np.zeros(thresh.shape,dtype='uint8')cv2.drawContours(mask,[c],-1,255,-1)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=amkey[q]if k==bubbled[1]:color=(0,255,0)correct+=1cv2.drawContours(warped_new,[cnts[k]],-1,color,3)cv_show('warpeding',warped_new)
score=(correct/5.0)*100
print('{:.2f}%'.format(score))
cv2.putText(warped_new,'{:.2f}%'.format(score),(10,30),cv2.FONT_HERSHEY_SIMPLEX,0.9,(0,0,255),2)
cv2.imshow('exam',warped_new)
cv2.waitKey(0)

结论

本实验展示了使用OpenCV进行图像中四边形区域的透视变换和答案评分的可行性。通过简单的图像处理和透视变换,可以有效地从复杂背景中提取目标区域,并对其进行分析和评分。该技术在自动化测试评分等领域具有广泛的应用前景。


http://www.ppmy.cn/embedded/128598.html

相关文章

C语言入门笔记:1.1 搭建开发环境

文章目录 一、C51与C251的区别二、安装Keil MDK三、C语言&#xff1a;菜鸟教程 一、C51与C251的区别 <1> 指令集数量不一样&#xff0c;C251有268条指令&#xff0c;C51有111条指令&#xff0c;前者可向下兼容后者的指令集&#xff0c;即Binary模式。 <2> 从指令种…

ssm基于VUE的图书馆管理系统的设计与实现+vue

系统包含&#xff1a;源码论文 所用技术&#xff1a;SpringBootVueSSMMybatisMysql 免费提供给大家参考或者学习&#xff0c;获取源码请私聊我 需要定制请私聊 目 录 目 录 III 第1章 绪论 1 1.1 课题背景 1 1.2 课题意义 1 1.3 研究内容 2 第2章 开发环境与技术 3 …

嵌入式C语言面试相关知识——结构体和联合体

嵌入式C语言面试相关知识——结构体和联合体 一、博客声明二、结构体1、数组概念2、如何声明定义数组3、数组特点 三、联合体1、联合体概念2、如何声明定义联合体3、联合体特点 四、两者区别 一、博客声明 又是一年一度的秋招&#xff0c;怎么能只刷笔试题目呢&#xff0c;面试…

商汤科技十周年公布新战略,将无缝集成算力、模型及应用

10月18日&#xff0c;恰逢商汤科技十周年庆典&#xff0c;“2024商汤十周年国际论坛&#xff1a;迈向AI 2.0共融新时代”在香港科学园成功举办。 据「TMT星球」了解&#xff0c;来自全球的行业领袖、政府代表、AI专家共聚于此&#xff0c;共同探讨AI行业的未来。 活动上&…

设计模式之委托模式

委托设计模式&#xff08;Delegate Pattern&#xff09;是一种行为设计模式&#xff0c;它允许一个对象将某些责任委托给另一个对象。在委托模式中&#xff0c;有两个主要角色&#xff1a;委托者&#xff08;Delegator&#xff09;和被委托者&#xff08;Delegate&#xff09;。…

【Java 并发编程】线程池理解与使用

前言 在进入本章的学习之前&#xff0c;先来回顾一下&#xff0c;在没有使用线程池一直是如何执行任务的&#xff1a; class Task implements Runnable{Overridepublic void run() {System.out.println(Thread.currentThread().getName());} }class Main{public static void ma…

利用编程思维做题之求节点 x 在二叉树中的双亲节点算法

在二叉树中查找某个节点 x 的双亲节点是一个典型的树操作问题。双亲节点的概念是指某个节点的直接上级节点&#xff0c;即它的父节点。我们将通过遍历树的结构&#xff0c;查找并返回 x 的双亲节点。 1. 理解问题 我们需要设计一个算法来查找给定二叉树中节点 x 的双亲节点。目…

spring Jdbc

--------------------------------SpringJdbc-------------------------------- 创建数据库 DROP DATABASE IF EXISTS studb; CREATE DATABASE studb; USE studb; CREATE TABLE student ( id INT PRIMARY KEY, NAME VARCHAR(50) NOT NULL, gender VARCHAR(10) NO…