(注:部分代码参考作者skyblue NG博客修改,部分代码不适用于新版本,因此已修改为在3.7中正常工作。)。
上一节介绍了如何自动找到边缘并获取文档轮廓来执行透视转换。图像文件比较明确时,这种转换仍然简单,但也存在先天缺陷。也就是说,实际应用得到的图像对获得文档轮廓不是很好。因此,必须通过手动鼠标选择需要转换的区域,找到解决此问题的其他方法。(大卫亚设,Northern Exposure(美国电视剧),这可以适用于大部分情况。
整个想法是这样的。
第一步:必须使用tkinter图形界面构建用于导入图像等操作的GUI
第二步:定义鼠标事件并选择导入图像的ROI区域。其中,用鼠标显示ROI四个角的坐标。
步骤3:调用上一节中使用的转换和扫描程序,将扫描程序修改为函数
步骤4:显示结果
好像不难。当然,我觉得你应该对tkinter有所了解。但是参考文档并不难理解这些代码。而且我做了很多注释,最大限度地减少了理解代码的困难。关键是必须自己亲自验证。
现在转到第一步:填写GUI表格
#-*-代码3360 utf-8-*-
From tkinter import *
需要from tkinter . file dialog import ask open filename #文件对话框
Import cv2
Import numpy as NP
From tkinter import ttk
Import win32clipboard as wcld
Import操作系统
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) #
Ttk。Label(frm2,text=’座标:’)。pack(side=’left ‘,ipadx=0) #在第2帧的左侧加载另一个标记
Msg=StringVar()
Ttk.entry (frm2,width=60,textvariable=msg)。紧接在pack (side=’ left ‘,ipadx=0) #之后
Ttk。Button(frm2,text=’ copy ‘,command=send _ to _ clibboard)。pack (side=’ right ‘)
#进入消息循环
Root.mainloop() # tkinter通常需要运行mainloop()方法才能创建窗口
是这样制作的。
第二步:定义“打开文件”按钮事件,myopen()函数
Filename=’ ‘
Def myopen():
全局文件名、img、ROI
通过# askopenfilename()方法直接获取文档名称
filename=ask open filename(filetype s=(‘ template files ‘,’ *)。’ t plate ‘),(‘ html files ‘,)*。htm),(“所有文件”,* .* ‘))
打印(文件名)
Img=cv2.imread(文件名)
ROI=img.copy()
# setMouseCallback()创建鼠标回调函数,每次用鼠标左键单击图像并再次提起时,该函数都会调用三次鼠标响应函数
#此处调用的回调函数是上面定义的on_mouse()函数,当鼠标激活打开的图像时执行相应的操作。
Cv2。命名窗口(“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 #初始化选定的点数
Defon _ mouse(事件,x,y,flags,param)3360
全球img、point1、point2、count、points max
Global lspointschoose,tppointschoose #保存选定的点
Global pointsCount #鼠标点击点数
全球img2、ROI _ bymouse _ flag
Img2=img.copy() #这一行代码每次都要重画,以确保不要画太多
if event==cv2 . event _ lbuttondown : #单击左键
PointsCount=pointsCount 1
print(‘ pointscount 3360 ‘,points count)
Point1=(x,y)
打印(x,y)
#绘制单击点
Cv2.circle (img2、point1、10、(0,255,0)、2)
#将选定的点保存到列表中
用于转换为LsPointsChoose.append([x,y]) # array以提取多边形ROI
用于绘制TpPointsChoose.append((x,y)) #点
# –
#用鼠标用直线连接选定的点
打印(len (tppointschoose))
for I inrange(len(tppointschoose)-1):
打印(“I”,I)。
Cv2.line (img2、tppointschoose [I]、tppointschoose [i1]、(0,0,255)、2)
单击# – pointMax可以提取图形-
IF(points count==points max)3360
# -绘制感兴趣的区域-
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 _ rbut tondown 3360 #右键单击
打印(“right-mouse”)
PointsCount=0
TpPointsChoose=[]
LsPointsChoose=[]
CV2 . IMSHOW(‘SRC’,img2)。
重新定义ROI_byMouse()函数
def ROI _ by mouse()3360
全球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 . re shape(-1、1、2))
#print(pts)
#其中reshape的第一个参数是-1。这表示这个维度的长度是根据下一个维度的计算来计算的。
# OpenCV,在绘制之前,需要将多边形的顶点坐标更改为顶点数12维的矩阵
# -绘制多边形-
# mask=cv2.polylines (mask,[pts],true,(255,255,255))
# -填满的多边形-
Mask2=cv2.fillpoly (mask,[pts],(255,255,255))
#cv2.imshow(‘mask ‘,mask2)
cv2 . im write(“mask . BMP”,mask2)
ROI=cv2.bitwise_and(mask2,img)
Wrap_img=wrapped(ROI)
cv2 . im write(“ROI . BMP”,ROI)
cv 2 . im show(“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)3360
#第一阶段
#加载图像,计算新图像和新图像的高度比例,复制副本,修改大小。
#为了加快图像处理速度,使边缘检测阶段更加准确。
#已将扫描图像的高度调整为500像素。
#我们也特别注意跟踪图像的原始高度和新高度的比例。
#这样就可以扫描原始图像,而不是调整大小的图像。
# img=cv2.imread(图像)
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(灰色,70,250)
#显示原始图像和检测到的边缘图像。不需要显示
# print(“step 13360边缘检测”)
#cv2.imshow(‘Image ‘,img)
#cv2.imshow(‘Edged ‘,Edged)
#cv2.waitKey(0)
#cv2.destroyAllWindows()
#第二阶段
#根据边缘图像,找到轮廓保持最大的东西,并在图像中识别
CNTs=cv2 . find contours(edged . copy(),cv2.retr _ list,cv2.chain _ approx _ simple)
CNTs=im utils . grab _ contours(CNTs)
Cnts=sorted (CNTs,key=cv2.contourarea,reverse=true)[33605]
#循环处理
For c in cnts:
#大致轮廓
Peri=cv2.arcLength(c,True)
Approx=cv2.approxpolydp (c,0.02 * peri,true)
#如果要查找的大致轮廓有四个角,假设我们需要查找
IF LEN(Approx)==43360
屏幕CNT=approx
布雷克
#显示文档的轮廓在此模块中不需要显示
# print(“step 23360 find contours of paper”)
# cv2.drawcontours (img,[screencnt],-1,(0,255,0),2)
# cv2 . imshow(“Outline”,img)
#cv2.waitKey(0)
#cv2.destroyAllWindows()
#第三阶段
应用# 4点变换生成鸟瞰
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、文本识别和输出。这比较有挑战性。