使用电脑的时候经常遇到图片上满是文字,想复制却有心无力的情况。又或者浏览器不允许复制这样的恶心设置。
什么?你说微信,QQ都有这样的工具?难道你要为了一个简单的文字提取而专门登录这样的重器?又或者网络被小偷偷走了呢?
其实这个功能能主要包括两个方面的内容
我们依次实现就好了
python自己准备,这里不做多余的讲述。
pyautogui,cnocr直接pip install pyautogui
pip install cnocr
即可
一定要找一个有免费网络的地方,不要用手机流量哦,后面有重磅炸弹
第一次安装cnocr后,win + r
打开cmd
,运行python
然后输入from cnocr import CnOcr
,第一次他会下载文字识别包。
这里有个提示,如果网络中断了,那么下一次再打开,他就会报错。
解决方法也很简单,只需要根据报错信息的第1行(注意去掉最后的文件名)找到下载路径删除下载了一半的zip文件,然后再重新下载就好了
屏幕截图的话,我们使用pyautogui.screenshot()
就好,可以传入左上角坐标和宽高控制区域
关键是获取用户选择的区域
一个可行的方法是用tkinter
创建透明窗口,让用户既可以看见桌面,我们也可以获取鼠标事件
因为要多次调用,方便起见,我们封装一个模块 Screencut.py
import tkinter as tk
import pyautogui
import time
#鼠标左键按下
def button_1(event):
global x, y ,xstart,ystart
x, y = event.x, event.y
xstart,ystart = event.x, event.y
xstart,ystart = event.x, event.y
cv.configure(height=1)
cv.configure(width=1)
cv.place(x=event.x, y=event.y)
#鼠标左键按下并移动
def b1_Motion(event):
global x, y
x, y = event.x, event.y
global cv
cv.configure(height = event.y - ystart)
cv.configure(width = event.x - xstart)
#鼠标左键松开
def buttonRelease_1(event):
global x, y,xstart,ystart
x, y = event.x, event.y
if x == xstart and y == ystart:
return
global cv
global root
Pstart=[0,0]
cv.place_forget()
root.attributes("-alpha", 0)
img = pyautogui.screenshot(region=[xstart,ystart,x-xstart,y-ystart]) # x,y,w,h
img.save('screenshot.png')
sys_out()
# 退出
def sys_out(even = True):
global root
#from tkinter import messagebox
#if messagebox.askokcancel('Exit','Confirm to exit?'):
root.destroy()
def GetPic():
global root
root = tk.Tk()
root.overrideredirect(True) # 隐藏窗口的标题栏
# root.attributes("-alpha", 0.3) # 窗口透明度70 %
root.attributes("-alpha", 0.3) # 窗口透明度60 %
root.attributes("-topmost", 1)
#root.geometry("300x200+10+10") # 设置窗口大小与位置
root.geometry("{0}x{1}+0+0".format(root.winfo_screenwidth(), root.winfo_screenheight()))
root.configure(bg="black")
# 再创建1个Canvas用于圈选
global cv, x, y, xstart, ystart
cv = tk.Canvas(root)
x, y = 0, 0
xstart,ystart = 0 ,0
# 绑定事件到Esc键,当按下Esc键就会调用sys_out函数,弹出对话框
root.bind('' ,sys_out)
root.bind("" , button_1)
root.bind("" , b1_Motion)
root.bind("" , buttonRelease_1)
root.mainloop()
思路难度不大,按部就班的走就好,自行看代码理解,每次调用GetPic()
会弹出窗口让用户选择,并且将截好的图片保存在screenshot.png
中
有了图片,下一步就是文字识别了。
模式很简单,传入screenshot.png
,返回一个识别结果的列表,不会使用cnocr的同学自行百度,也可以直接看代码,非常简单,因为是功能的主体,所以放到主程序中,就叫TextOCR.py
把
from cnocr import CnOcr
import screenshot # 自己封装的功能
def TextOCR():
ScreenCut.GetPic() # 首先调用GetPic获取截图
ocr = CnOcr() # 调用识别工具
result = ocr.ocr('screenshot.png') # 返回识别结果的列表
print(result) # 输出
咦?怎么输出了一堆奇怪的数组
有python经验的你看出来了吗?我们需要手动提取这里的结果
但是别慌,后面还有重头戏,这个暂且放一放
为了方便用户,我们还是用tkinter把它显示出来
还是先封装一个功能模块 ShowResult.py
import tkinter as tk
def show(text): # 给到每一行的文字列表,一行一行的显示到文本框
root = tk.Tk()
root.title('GWord')
root.attributes("-topmost", 1) # 这里给到一个置顶,方便用户查看+
txt_area = tk.Text(root,wrap="none") # 设置到文本框结尾不自动换行
for i in range(len(text)):
txt = text[i].strip("\n")
txt_area.insert(tk.END,txt)
if i != len(text) - 1:
txt_area.insert(tk.END,"\n")
txt_area.pack()
root.mainloop()
这是这里头的最有技术含量,也是最值得研究的地方了。希望本文抛砖引玉,有想法的朋友可以评论与我讨论哦
注意到result
中除了'text'
属性,还有一个坐标数组表示这个文字的四角坐标,我们通过这个来保留原格式。
这里有个疑问请朋友们知道的个我留言哦:为什么两个左右相差很远的文字,他们的横坐标几乎是相同的呢?且有的时候相较于左右更近的文字,离的远的文字反而差值更小呢?
先忽略这个情况,我们默认左右顺序在返回的数组中已保持相对顺序(即在左边的原本的index也更靠前)
先让一个文字的x坐标为四个x坐标中的最小值(当然这个不影响),y坐标为四个坐标的平均值
然后我们对y坐标排序,先给定一个阈值,经测试 15 15 15是被较好的,我们用DES
保存这个数值,方便调整,然后排序,TextOCR.py
对刚才的代码进行了改善
from cnocr import CnOcr
import ScreenCut
import ShowResult
from functools import cmp_to_key
import os
DES = 15
def Data(res):
px = 1e9
py = 0
for pos in res["position"]:
px = min(px,pos[0])
py += pos[1]
py /= len(res["position"])
return {"x":px,"y":py,"text":res["text"]}
def cmp(a,b):
global DES
if abs(a["y"] - b["y"]) < DES:
return 0
return a["y"] - b["y"]
def TextOCR():
global DES
ScreenCut.GetPic()
ocr = CnOcr()
result = ocr.ocr('screenshot.png')
text = []
positionlist = []
for i in range(len(result)):
positionlist.append(Data(result[i]))
positionlist.sort(key = cmp_to_key(cmp))
if len(positionlist) != 0:
text.append(positionlist[0]["text"])
for i in range(1,len(positionlist)):
if abs(positionlist[i]["y"] - positionlist[i - 1]["y"]) < DES: # 判断在同一行,追加到结尾
text[-1] += " " + positionlist[i]["text"]
else:
text.append(positionlist[i]["text"])
ShowResult.show(text)
os.remove('screenshot.png') # 我们把使用完的照片删除掉,保护隐私
然后我们同过keyboard库来绑定鼠标事件(这里绑定ctrl + alt
貌似这是几个为数不多的没有被各大应用程序占领的快捷键,好不容易试出来的),还是在TextOCR.py
中
from cnocr import CnOcr
import ScreenCut
import ShowResult
import keyboard
from functools import cmp_to_key
import os
DES = 15
def Data(res):
px = 1e9
py = 0
for pos in res["position"]:
px = min(px,pos[0])
py += pos[1]
py /= len(res["position"])
return {"x":px,"y":py,"text":res["text"]}
def cmp(a,b):
global DES
if abs(a["y"] - b["y"]) < DES:
return 0
return a["y"] - b["y"]
def TextOCR():
global DES
ScreenCut.GetPic()
ocr = CnOcr()
result = ocr.ocr('screenshot.png')
text = []
positionlist = []
for i in range(len(result)):
positionlist.append(Data(result[i]))
positionlist.sort(key = cmp_to_key(cmp))
if len(positionlist) != 0:
text.append(positionlist[0]["text"])
for i in range(1,len(positionlist)):
if abs(positionlist[i]["y"] - positionlist[i - 1]["y"]) < DES:
text[-1] += " " + positionlist[i]["text"]
else:
text.append(positionlist[i]["text"])
ShowResult.show(text)
os.remove('screenshot.png')
if __name__ == '__main__':
keyboard.add_hotkey('alt+ctrl',TextOCR)
keyboard.wait()
这里提出一个问题
有朋友知道cnocr怎么打包成exe,pyinstaller貌似不能把那个资源文件也打包进去,会出错
感谢评论区或私信回答
这里再补充一个问题(相当于一个提醒)
在复制窗口中的文字时一定要等到ctrl + c,然后到目标位置ctrl+v后再关闭窗口,不然其他程序会死机(死机后关闭这个程序,另外的死机窗口也好了)
有朋友知道问什么吗?
感谢评论区或私信回答