自制 Windows 小工具 ———— 文字识别助手

自制 Windows 小工具 ———— 文字识别助手

  • 背景
  • 问题分析
  • 工具准备
  • 第一部分:屏幕截图的实现
  • 文字识别
  • 结果展示
  • 保持原格式
  • 事件绑定

背景

使用电脑的时候经常遇到图片上满是文字,想复制却有心无力的情况。又或者浏览器不允许复制这样的恶心设置。
什么?你说微信QQ都有这样的工具?难道你要为了一个简单的文字提取而专门登录这样的重器又或者网络被小偷偷走了呢?

问题分析

其实这个功能能主要包括两个方面的内容

  1. 屏幕截图(选择要复制的内容)
  2. 文字识别

我们依次实现就好了

工具准备

  • python(建议3.10.4,亲测这个版本匹配cnocr没有问题)
  • cnocr (python 第三方库)
  • pyautogui (python 第三方库)

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后再关闭窗口,不然其他程序会死机(死机后关闭这个程序,另外的死机窗口也好了)
有朋友知道问什么吗?
感谢评论区或私信回答

接下来给一张效果图
自制 Windows 小工具 ———— 文字识别助手_第1张图片

你可能感兴趣的:(python,实用编程,python,开发语言,windows)