百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

openCV 进阶之三:鼠标选取ROI进行文档透视转换

toyiye 2024-07-06 00:36 30 浏览 0 评论

(注:部分代码参考作者 skyblue NG 博客修改,部分代码在新的版本不适用,所以作了修改,在3.7上可以正常运行。)

上节谈到如何通过自动查找边缘,获取文档轮廓来进行透视转换的问题,在图像文件比较清晰的情况下,这个转换还是很简单的,但是也存在先天的缺陷,就是实际应用所获得的图像并不是那么好获取文档轮廓,所以我们得另辟蹊径来解决这个问题,方法就是通过人工鼠标选取需要转换的区域来实现。这样可以适用于大多数情况。

整体的思路是这样的:

第一步:需要用到tkinter图形界面,构建一个GUI用于图像导入等操作

第二步:定义鼠标事件,对导入的图像选取ROI区域,这里通过鼠标标注ROI的四个角点坐标

第三步:调用上一节使用到的transform and scan 程序,把scan程序修改成一个函数

第四步:显示结果

看起来也不是很难,当然你得对tkinter有所了解,不过参照文档理解这些代码不难,而且我做了大量的注释,尽量减少代码理解的难度。关键是一定要自己动手验证一下。

马上进入第一步:构建一个GUI窗体

# -*- coding: utf-8 -*-
from tkinter import *
from tkinter.filedialog import askopenfilename #需要用到文件对话框
import cv2
import numpy as np
from tkinter import ttk
import win32clipboard as wcld
import os
from scan_module import wrapped
root = Tk() # 创建根窗体
frm1 = Frame(root) # 把frm1加载到根窗体中
frm1.pack(side='top',anchor='e',ipadx=1,ipady=1) # 放置frm1框架的为止
frm2 = Frame(root) # 加载第二个框架到根窗体
frm2.pack(side='top',anchor='w',ipadx=1,ipady=3)
 
root.title("ROI截取")
Label(frm1,text="ROI截取 第一版",fg ="gray").pack(side="right") # 在框架1中加载一个标签
ttk.Button(frm2,text="打开文件",command=myopen).pack(side="left",ipadx=8) # 在框架2左边加载一个按钮并赋予按钮事件
ttk.Label(frm2,text=" 坐标:").pack(side="left",ipadx=0) # 在框架2左边再加载一个标签
msg = StringVar()
ttk.Entry(frm2,width=60, textvariable = msg).pack(side="left",ipadx=0) # 紧接着在框架2左边再加载一个文本输入框
ttk.Button(frm2,text="复制",command=send_to_clibboard).pack(side="right",ipadx=3) # 在框架2右边加载一个按钮并赋予按钮事件
 
# 进入消息循环
root.mainloop() # tkinter一般只有执行mainloop()方法才能运行,才能创建窗口

构建完是这个样子的:

第二步:定义按钮 ‘打开文件’ 事件,myopen() 函数

filename = ""
def myopen():
 global filename,img,ROI
 # 通过askopenfilename()方法直接获取文档名称
 filename = askopenfilename(filetypes=(("Template files", "*.tplate"), ("HTML files", "*.html;*.htm"), ("All files", "*.*") ))
 print (filename)
 img = cv2.imread(filename)
 ROI = img.copy()
 
 # setMouseCallback()创建了一个鼠标回调函数,每次在图像上单击鼠标左键再抬起的过程,都会分3次调用鼠标响应函数
 # 这里调用的回调函数就是上面定义的on_mouse()函数,当鼠标激活打开的图像时,执行相应的操作。
 cv2.namedWindow('src')
 cv2.setMouseCallback('src', on_mouse) 
 cv2.imshow('src', img)
 cv2.waitKey(0)
 cv2.destroyAllWindows()
定义on_mouse() 函数:
# -----------------------鼠标操作相关------------------------------------------
lsPointsChoose = [] #选取点的坐标列表
tpPointsChoose = [] 
pointsCount = 0 #鼠标点击的次数
count = 0 
pointsMax = 4 #初始化选取点的数量
def on_mouse(event, x, y, flags, param):
 global img, point1, point2, count, pointsMax
 global lsPointsChoose, tpPointsChoose # 存入选择的点
 global pointsCount # 对鼠标按下的点计数
 global img2, ROI_bymouse_flag
 img2 = img.copy() # 此行代码保证每次都重新再原图画 避免画多了
 
 if event == cv2.EVENT_LBUTTONDOWN: # 左键点击
 pointsCount = pointsCount + 1
 print('pointsCount:', pointsCount)
 point1 = (x, y)
 print (x, y)
 # 画出点击的点
 cv2.circle(img2, point1, 10, (0, 255, 0), 2)
 
 # 将选取的点保存到list列表里
 lsPointsChoose.append([x, y]) # 用于转化为array 提取多边形ROI
 tpPointsChoose.append((x, y)) # 用于画点
 # ----------------------------------------------------------------------
 # 将鼠标选的点用直线连起来
 print(len(tpPointsChoose))
 for i in range(len(tpPointsChoose) - 1):
 print('i', i)
 cv2.line(img2, tpPointsChoose[i], tpPointsChoose[i + 1], (0, 0, 255), 2)
 
 # ----------点击到pointMax时可以提取去绘图----------------
 if (pointsCount == pointsMax):
 # -----------绘制感兴趣区域-----------
 cv2.line(img2, tpPointsChoose[0], tpPointsChoose[pointsMax-1], (0, 0, 255), 2) # 把多边形封闭,最后一个点和起始点连接
 ROI_byMouse() # 调用绘制函数画出感兴趣区域
 ROI_bymouse_flag = 1
 
 i = 0
 pointsCount = 0
 tpPointsChoose = []
 lsPointsChoose = []
 
 cv2.imshow('src', img2)
 # -------------------------右键按下清除轨迹-----------------------------
 if event == cv2.EVENT_RBUTTONDOWN: # 右键点击
 print("right-mouse")
 pointsCount = 0
 tpPointsChoose = []
 lsPointsChoose = []
 cv2.imshow('src', img2)

再定义ROI_byMouse()函数

def ROI_byMouse():
 global src, ROI, ROI_flag, mask2, lsPointsChoose, msg
 mask = np.zeros(img.shape, np.uint8)
 #print(lsPointsChoose)
 
 msg.set(lsPointsChoose)# 储存选择点坐标的列表到msg变量
 #print(msg.get())
 pts = np.array([lsPointsChoose], np.int32) # pts是多边形的顶点列表(顶点集)
 pts = pts.reshape((-1, 1, 2))
 #print(pts)
 # 这里 reshape 的第一个参数为-1, 表明这一维的长度是根据后面的维度的计算出来的。
 # OpenCV中需要先将多边形的顶点坐标变成顶点数×1×2维的矩阵,再来绘制
 
 # --------------画多边形---------------------
 #mask = cv2.polylines(mask, [pts], True, (255, 255, 255))
 # -------------填充多边形---------------------
 mask2 = cv2.fillPoly(mask, [pts], (255, 255, 255))
 #cv2.imshow('mask', mask2)
 cv2.imwrite('mask.bmp', mask2)
 ROI = cv2.bitwise_and(mask2, img)
 wrap_img = wrapped(ROI)
 cv2.imwrite('ROI.bmp', ROI)
 cv2.imshow('ROI', ROI)
 cv2.imshow('wrap_image',wrap_img)

本例中还定义了一个 send_to_clibboard() 复制到剪切板函数用于查看选取的四个角点的坐标,主要用来验证效果,其实可以不用。不过你可以参考。

def send_to_clibboard():
 wcld.OpenClipboard()
 wcld.EmptyClipboard()
 wcld.SetClipboardData(wcld.CF_UNICODETEXT, msg.get()) # 此处使用.CF_UNICODETEXT方法才能正常解析
 wcld.CloseClipboard()

把上一篇的scan.py修改成scan_module()模块用来调用:

# 导入必要的库
#导入上一节构建的模块和函数
from transform import four_point_transform
#记得安装scikit-image包,threshold-local函数帮助我们处理黑白图像
from skimage.filters import threshold_local 
import numpy as np
import argparse
import cv2
#imutils是一个很实用的图像处理库,比如resize/cropping/rotate等图像基本编辑
import imutils
#这个module直接返回转换后的图像,用于其它程序调用
def wrapped(image):
	# 第一步
	# 加载图像并计算新旧图像高度的比例,并拷贝一份,修改大小。
	# 为了加快图像处理速度,同时使边缘检测步骤更加准确,
	# 我们将扫描图像的高度调整为500像素。
	# 我们还特别注意跟踪图像的原始高度与新高度的比值,
	# 这将允许我们对原始图像而不是调整大小的图像执行扫描。
	#img = cv2.imread(image)
	img = image
	ratio = img.shape[0] / 500.0
	orig = img.copy()
	img = imutils.resize(img, height = 500)
	 
	# 把图象转化为灰度, 并加模糊处理,然后查找边缘
	gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
	gray = cv2.GaussianBlur(gray, (5, 5), 0)
	edged = cv2.Canny(gray, 70, 250)
	 
	# 显示原始图像和检测到的边缘图像,不需要显示
	#print("STEP 1: Edge Detection")
	#cv2.imshow("Image", img)
	#cv2.imshow("Edged", edged)
	#cv2.waitKey(0)
	#cv2.destroyAllWindows()
	# 第二步
	# 在边缘图像的基础上查找轮廓保留最大的一个,并在图像中标识出来
	cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
	cnts = imutils.grab_contours(cnts)
	cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5]
	 
	# 循环处理
	for c in cnts:
		# 大致轮廓
		peri = cv2.arcLength(c, True)
		approx = cv2.approxPolyDP(c, 0.02 * peri, True)
	 
		# 如果查找的大致轮廓有四个角点,即假设为我们需要查找的
		if len(approx) == 4:
			screenCnt = approx
			break
	 
	# 显示文档的轮廓在这个模块中不需要显示
	#print("STEP 2: Find contours of paper")
	#cv2.drawContours(img, [screenCnt], -1, (0, 255, 0), 2)
	#cv2.imshow("Outline", img)
	#cv2.waitKey(0)
	#cv2.destroyAllWindows()
	# 第三步
	# 应用四点转换生成鸟瞰图
	warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)
	 
	# 把变形的图像转化成灰度
	warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
	T = threshold_local(warped, 11, offset = 10, method = "gaussian")
	warped = (warped > T).astype("uint8") * 255
	return warped

该函数返回转换后的图像。

好了,我们测试一下效果

查看一下视频,演示测试过程:

效果非常好。赶紧动起手来,设计一个属于自己的文档扫描仪吧。下一次尝试一下OCR,文本识别和输出。这个比较有挑战。

相关推荐

# Python 3 # Python 3字典Dictionary(1)

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值(key=>value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典包括在花括号({})中,格式如...

Python第八课:数据类型中的字典及其函数与方法

Python3字典字典是另一种可变容器模型,且可存储任意类型对象。字典的每个键值...

Python中字典详解(python 中字典)

字典是Python中使用键进行索引的重要数据结构。它们是无序的项序列(键值对),这意味着顺序不被保留。键是不可变的。与列表一样,字典的值可以保存异构数据,即整数、浮点、字符串、NaN、布尔值、列表、数...

Python3.9又更新了:dict内置新功能,正式版十月见面

机器之心报道参与:一鸣、JaminPython3.8的热乎劲还没过去,Python就又双叒叕要更新了。近日,3.9版本的第四个alpha版已经开源。从文档中,我们可以看到官方透露的对dic...

Python3 基本数据类型详解(python三种基本数据类型)

文章来源:加米谷大数据Python中的变量不需要声明。每个变量在使用前都必须赋值,变量赋值以后该变量才会被创建。在Python中,变量就是变量,它没有类型,我们所说的"类型"是变...

一文掌握Python的字典(python字典用法大全)

字典是Python中最强大、最灵活的内置数据结构之一。它们允许存储键值对,从而实现高效的数据检索、操作和组织。本文深入探讨了字典,涵盖了它们的创建、操作和高级用法,以帮助中级Python开发...

超级完整|Python字典详解(python字典的方法或操作)

一、字典概述01字典的格式Python字典是一种可变容器模型,且可存储任意类型对象,如字符串、数字、元组等其他容器模型。字典的每个键值key=>value对用冒号:分割,每个对之间用逗号,...

Python3.9版本新特性:字典合并操作的详细解读

处于测试阶段的Python3.9版本中有一个新特性:我们在使用Python字典时,将能够编写出更可读、更紧凑的代码啦!Python版本你现在使用哪种版本的Python?3.7分?3.5分?还是2.7...

python 自学,字典3(一些例子)(python字典有哪些基本操作)

例子11;如何批量复制字典里的内容2;如何批量修改字典的内容3;如何批量修改字典里某些指定的内容...

Python3.9中的字典合并和更新,几乎影响了所有Python程序员

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

Python3大字典:《Python3自学速查手册.pdf》限时下载中

最近有人会想了,2022了,想学Python晚不晚,学习python有前途吗?IT行业行业薪资高,发展前景好,是很多求职群里严重的香饽饽,而要进入这个高薪行业,也不是那么轻而易举的,拿信工专业的大学生...

python学习——字典(python字典基本操作)

字典Python的字典数据类型是基于hash散列算法实现的,采用键值对(key:value)的形式,根据key的值计算value的地址,具有非常快的查取和插入速度。但它是无序的,包含的元素个数不限,值...

324页清华教授撰写【Python 3 菜鸟查询手册】火了,小白入门字典

如何入门学习python...

Python3.9中的字典合并和更新,了解一下

全文共2837字,预计学习时长9分钟Python3.9正在积极开发,并计划于今年10月发布。2月26日,开发团队发布了alpha4版本。该版本引入了新的合并(|)和更新(|=)运算符,这个新特性几乎...

python3基础之字典(python中字典的基本操作)

字典和列表一样,也是python内置的一种数据结构。字典的结构如下图:列表用中括号[]把元素包起来,而字典是用大括号{}把元素包起来,只不过字典的每一个元素都包含键和值两部分。键和值是一一对应的...

取消回复欢迎 发表评论:

请填写验证码