在人工智能日益发达,python大行其道的今天,一些OCR(光学字符识别,其实就是识别图片中的文字)工具居然还要收费,叔可忍,婶不可忍,于是做了一个个人OCR工具。使用这个工具,在利用snipaste或类似软件进行屏幕截图后,将截图保存到系统剪贴板,再按下工具提示的热键,可以将剪贴板中的图形数据识别为文本后复制到剪贴板,之后在需要识别出的文字的地方就能直接ctrl+v完成粘贴;也可以识别成excel文件保存,并将保存的excel文件单元格的宽度、高度和对齐方式调整为比较合适的状态。如果图片已经保存在电脑上,则可以利用工具的另一个功能,将文件夹下所有的图片全部识别为文本文件保存。
工具用到了百度人工智能接口,操作excel文件使用的xlwings,因此,使用此工具要求电脑已联网,并且安装了excel软件。这个工具一方面有一定的实用性,另一方面也演示了tinker、xlwings、keyboard等程序包的部分用途,逻辑上并不复杂,代码有详尽注释,可供参考。
from aip import AipOcr # 导入AipOcr模块,用于做文字识别,pip install baidu-aip, pip install chardet
import time # 时间模块
import requests # 用于下载,pip install requests
import io # 用于创建写入剪贴板中的图像数据的字节数组
from PIL import ImageGrab # 用于获取剪贴板上的图像数据,pip install pillow
import keyboard # 用于注册热键,pip install keyboard
import pyperclip # 用于将识别的文本内容复制到剪贴板,pip install pyperclip
import xlwings as excel # 用于操作excel文件,pip install xlwingsfrom aip import AipOcr # 导入AipOcr模块,用于做文字识别,pip install baidu_aip, pip install chardet
import time # 时间模块
import requests # 用于下载,pip install requests
import io # 用于创建写入剪贴板中的图像数据的字节数组
from PIL import ImageGrab # 用于获取剪贴板上的图像数据,pip install pillow
import keyboard # 用于注册热键,pip install keyboard
import pyperclip # 用于将识别的文本内容复制到剪贴板,pip install pyperclip
import xlwings as excel # 用于操作excel文件,pip install xlwings
APP_ID = baidu_keys.APP_ID
API_KEY = baidu_keys.API_KEY
SECRET_KEY = baidu_keys.SECRET_KEY
client = AipOcr(APP_ID, API_KEY, SECRET_KEY)
# 工具用法
usage = '\n用法:\n 截图复制到剪贴板后按“shift+esc”识别表格,按“alt+esc”识别文本。\n ' + \
'按“alt+shift+f”识别指定文件夹中的图片。\n 按“cmd+esc”退出程序。\n'
"""识别表格"""
def table_ocr():
try:
print('开始识别...')
file = None
image = ImageGrab.grabclipboard()
if image != None:
img_bytes = io.BytesIO()
image.save(img_bytes, format='png')
table = client.tableRecognitionAsync(img_bytes.getvalue())
request_id = table['result'][0]['request_id']
# 判断识别是否完成,直到完成才根据请求ID获取Excel下载路径
result = client.getTableRecognitionResult(request_id) # 通过ID获取识别结果
while result['result']['ret_msg'] != '已完成': # 如果状态是“已完成”,才能获取下载地址
time.sleep(2) # 暂停2秒再刷新
result = client.getTableRecognitionResult(
request_id) # 持续刷新,直到满足条件
download_path = result['result']['result_data']
# 下载Excel文件
excel = requests.get(download_path) # 抓取下载链接
# win32ui.MessageBox('请指定识别文件保存位置及文件名...')
print('请指定识别文件保存位置及文件名...')
"""win32ui打开保存文件对话框,但win32ui目前尚没有打开文件夹对话框,故改用tinker
flags = win32con.OFN_OVERWRITEPROMPT
dlg = win32ui.CreateFileDialog(0, 'xls', '识别结果', flags, 'Excel Files (*.xlsx)|*.xls;*.xlsx||', None) #0表示保存文件对话框;1表示打开文件对话框
dlg.DoModal()
file = dlg.GetPathName()"""
# tinker打开保存文件对话框
root = tk.Tk()
# root.withdraw() # 隐藏tk主窗口。因为随即销毁tk,所以并无必要
file = filedialog.asksaveasfilename(title='保存文件', filetypes=(("excel files", "*.xls"),),
defaultextension='xls', initialfile='识别结果', initialdir='/')
root.destroy()
with open(file, 'wb') as f: # 新建excel文件
f.write(excel.content) # 写入excel文件并保存
# 调整保存的excel文件格式
adjust_table(file, 'body')
else:
print('系统剪贴板中没有读取到图形数据。')
print(
f'{["识别已完成,结果保存为文件:" + file if file != None else "剪贴板中没有图形数据,无识别结果。"][0]}')
print(usage)
except:
print('识别出错,请重新启动程序识别!')
sys.exit()
"""修改excel文件单元格的边框,宽度,高度,对齐方式"""
def adjust_table(file, sheet_name):
char_width = 2.13 # 字符宽度,这里用的经验值,12号汉字宽度大约2.13mm,字号变大或缩小可以调整这个值
excel_app = excel.App(visible=False, add_book=False) # 打开excel但隐藏软件界面
try:
excel_workbook = excel_app.books.open(file)
excel_sheet = excel_workbook.sheets[sheet_name]
rows, cols = excel_sheet.used_range.shape # 表格中被编辑过的单元格最大行号和列号
u_range = excel_sheet.range(excel_sheet.range('A1'), excel_sheet.range(rows, cols))
u_range.api.Font.Size = 12 # 设置字体的大小。
u_range.api.Borders(9).LineStyle = 1 # 底边框
u_range.api.Borders(9).Weight = 4 # 边框粗细,最大值为4
u_range.api.Borders(7).LineStyle = 1 # 左边框
u_range.api.Borders(7).Weight = 4
u_range.api.Borders(8).LineStyle = 1 # 上边框
u_range.api.Borders(8).Weight = 4
u_range.api.Borders(10).LineStyle = 1 # 右边框
u_range.api.Borders(10).Weight = 4
u_range.api.Borders(11).LineStyle = 1 # 内部垂直边框
u_range.api.Borders(11).Weight = 2
u_range.api.Borders(12).LineStyle = 1 # 内部水平边框
u_range.api.Borders(12).Weight = 2
"""调整完字体大小和边框后,使单元格自适应宽度和高度,也可以用
range.columns.autofit()
range.rows.autofit()
"""
excel_sheet.autofit()
for col in range(1, cols + 1):
for row in range(1, rows + 1):
# cell_name = chr(64 + col) + str(row) #求出‘A1’形式的单元格名称,由于索引从1开始,转换为字符只加64
# cell = excel_sheet[cell_name] #当列名字母数量超过1个时,列的数字序号转换为列名比较复杂,因此应该用下面的方法构造cell
cell = excel_sheet.range(row, col)
if cell.value != None: # 被合并的单元格值为None,这种单元格直接跳过
value = str(cell.value) # 取得单元格的内容,用于估算单元格内容的宽度
# 对自适应的宽度做一个调整,最大宽度设为50,最小宽度设为10
if cell.column_width >= 50:
cell.column_width = 50
elif cell.column_width <= 10:
cell.column_width = 10
column_width = cell.column_width
# 计算合并单元格的宽度,为合并区域所有列的宽度
if cell.merge_cells: # 当前单元格为合并单元格
for i in range(col + 1, cell.merge_area.columns.count + 1):
r = excel_sheet.range(row, i)
column_width += r.column_width
cell.api.VerticalAlignment = -4108 # 单元格纵向居中
# 调整单元格水平对齐方式
if column_width >= len(value)*char_width: # 估算文字宽度小于单元格宽度,单元格水平居中
cell.api.HorizontalAlignment = -4108
else: # 估算文字宽度大于单元格宽度,单元格自动换行靠左对齐
cell.api.HorizontalAlignment = -4130
# 调整各行高度,单元格纵向留空白
for row in range(1, rows + 1):
excel_sheet.range(row, 1).row_height += 8
# 保存修改,关闭文件,退出excel
excel_workbook.save()
excel_workbook.close()
excel_app.quit()
except: # 万一出错关闭文件退出excel
excel_workbook.close()
excel_app.quit()
"""识别文本,识别结果在控制台输出,并直接拷贝到剪贴板"""
def text_ocr():
try:
image = ImageGrab.grabclipboard()
if image != None:
img_bytes = io.BytesIO()
image.save(img_bytes, format="png")
text = client.basicAccurate(img_bytes.getvalue())
result = text['words_result']
txt = str('')
for i in result:
txt = txt + i['words'] + '\n'
print('\n-----------\n', txt)
pyperclip.copy(txt)
else:
print('系统剪贴板中没有读取到图形数据。')
print(usage)
except:
print('识别出错,请重新启动程序识别!')
sys.exit()
"""将指定文件夹中的图片识别为文本文件"""
def img_ocr():
try:
root = tk.Tk()
# root.withdraw() # 隐藏tk主窗口。因为随即销毁tk,所以并无必要
# 获取图片所在文件夹
path = filedialog.askdirectory(title='打开文件夹', initialdir='/')
root.destroy()
# 识别完成图片数量计数
pages = 0
path_name = ''
# 遍历图片文件夹,将所有png、jpg、jpeg、bmp文件扫描为同名txt文件保存在同一目录下
for file in os.scandir(path):
(path_name, ext) = os.path.splitext(file)
ext = ext.lower() # 文件扩展名转换为小写字母
if(ext == '.png' or ext == '.jpg' or ext == '.jpeg' or ext == '.bmp'):
with open(file, 'rb') as f:
# 调用百度人工智能识别
text = client.basicAccurate(f.read())
result = text["words_result"]
# 将识别出的文本逐行写入文本文件
with open(path_name + '.txt', 'a', encoding='utf-8') as txt:
for i in result:
txt.write(i["words"] + '\n')
pages += 1
print('识别完成了%d页!' % (pages))
if pages == 0:
print('文件夹中没有支持的图片。')
else:
print(f'文件夹“{os.path.dirname(path_name)}”识别完毕!共识别了{pages}个图片。')
print(usage)
except:
print('识别出错,请重新启动程序识别!')
sys.exit()
if __name__ == '__main__':
print(usage)
keyboard.add_hotkey('alt+esc', text_ocr)
keyboard.add_hotkey('shift+esc', table_ocr)
keyboard.add_hotkey('shift+alt+f', img_ocr)
keyboard.wait('cmd+esc')
print('Bye.')
sys.exit()