JS限制网页复制?OCR软件精度低?不如自己做个超好用的OCR截屏识别助手
日期: 2020-11-12 分类: 跨站数据 449次阅读
点击左上方蓝字关注我们
【飞桨开发者说】邵绅宸,北华航天工业学院2018级计算机科学与技术专业,飞桨开发者技术专家PPDE,2020年国家级大学生创新创业训练计划省级立项,研究方向为计算机视觉。
项目背景
Windows操作系统有着庞大的用户数量,凭借优异的人机操作、较好的软硬件支持以及早期占据的市场,至2020年,其全球用户数超过10亿。
为了实现高效办公,勤劳的开发者们创作了许多软件满足大家需求。比如某些网页的文字被JavaScript限制无法copy,百度半天也解决不了(没错,是我太菜)。要不试试暴力方法,直接把电脑上文字当成图片来识别?但许多软件都得要钱,免费的效果又一般,或者每天给你几次体验的机会。最重要的一点,上面的软件都是识别本地图片。就像共享单车解决了城市最后一公里,截屏识别节省了保存图片再上传识别这一过程。
本项目AI Studio地址:
https://aistudio.baidu.com/aistudio/projectdetail/1150443
识别模型就不得不提在GitHub霸占Trending榜数日的PaddleOCR。PaddleOCR是百度开源的超轻量级OCR模型库,提供了数十种文本检测、识别模型,旨在打造一套丰富、领先、实用的文字检测、识别模型/工具库,助力使用者训练出更好的模型,并应用落地。值得注意的是,使用DB文本检测、CRNN文本识别的预训练模型chinese_ocr_db_crnn_mobile已加入PaddleHub,因此可以用hub在代码中便捷地获取和使用该模型。
图1-某一免费OCR软件部分界面
整体思路
既然想做一个截屏识别软件,首先需要捕获截屏,而内容的识别交给模型,最后把识别的结果输出到文本框即可。截屏捕获和结果输出都很简单,主要问题就在于用什么模型来检测和识别文字?
由于分割网络的结果可以准确描述诸如扭曲文本的场景,因而基于分割的自然场景文本检测方法变得流行起来。基于分割的方法其中关键的步骤是其后处理部分,这步中将分割的结果转换为文本框或是文本区域。DB文本检测算法也是基于分割,但是通过提出Differentiable Binarization(DB)来简化分割后的处理步骤,并且可以设定自适应阈值来提升网络性能。
图2-DB网络架构
模型的网络架构见图2,输入图像经过不同stage的采样之后得到不同的特征图,之后用这些特征图构建特征金字塔,最终输出原图1/4尺寸的特征图F,并用它来同时预测概率图P和阈值图T,由P和T计算后得到近似二值图B。前面说到DB可以对每一个像素点进行自适应二值化,阈值由网络学习得到,彻底将二值化这一步骤加入到网络中一起训练,二值化公式如下,k为放大因子,依经验设定为50。
图3-二值化公式和sigmoid对比
损失函数由概率图损失、二值图损失和阈值图损失构成。其中,α和β分别设置为1和10。和使用二值交叉熵损失函数,使用L1距离损失函数。
标签的生成按照PSENet方式,收缩比例r设置为0.4,将文本框分别向内向外收缩和扩张D个像素,然后计算收缩框和扩张框之间差集部分里每个像素点到原始图像边界的归一化距离。下面公式里的S和C代表面积和周长。
说完了检测算法,就该讲讲识别算法了。一种流行且简单的识别算法是CRNN(An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition),它的网络架构如下:
图4-CRNN网络架构
从下往上看,第一部分是卷积层,使用VGG提取输入图像的特征。VGG是经典的卷积神经网络,它的规则十分简单:连续使用数个填充为1、窗口形状为的3×3卷积层后接上一个步幅为2、窗口形状为2×2的最大池化层。卷积层保持输入的高和宽不变,通道数翻倍,池化层减半高和宽,最后接上全连接和激活函数softmax实现分类。
图5-VGG模型
VGG跟之前网络不同的一点在于它用连续的3×3卷积核代替较大的卷积核(11×11,7×7,5×5),这不仅减少了参数量,还能带来和大感受野相同的效果。
第二部分用LSTM进一步提取图像卷积特征中的序列特征。文字识别主要有两种方法,一种是基于字符/单词的识别,一种是基于序列的识别。基于字符/单词的识别通过切割将文本段中的每一个字符/单词单独提取然后识别,本质上就是图像分类。但是切割的好坏直接影响识别结果,如果无法准确切割出字符/单词,是难以识别出结果的。基于序列的识别将文字识别看成是序列识别,这保证了算法不会漏掉字符/单词,并且还能够结合上下文,更好地处理序列信息。
图6-基于字符/单词的识别
图7-基于序列的识别
不过RNN的输出序列X和真实的标签序列很难做到严格对齐,所以引入了CTC解决训练时字符无法对齐的问题。为了更好理解CTC对齐方法,这里举个简单的例子。假设我们使用CNN+RNN(GRU)来预测图5中的文字,输出的结果并不是我们期望的“飞桨”,有一些部分重合,也有一些部分由于文字之间的间隔得到的空白部分。虽然CRNN由不同的网络架构组成,但可以通过一个损失函数进行联合训练。模型的更多细节可参考原论文:https://arxiv.org/pdf/1507.05717.pdf。
我们利用PyQt的可视化功能,将截屏读取、文字识别集成到pushButton控件中,将输出结果放到textEdit控件中,就能够做一个简单的GUI。源代码及其使用方法可以在 https://aistudio.baidu.com/aistudio/datasetdetail/57331 中找到。
代码实现
项目的代码结构如下,requirements.txt为需要安装的模块。
图8-代码结构
PyQt的用户界面代码,这里并不是一行一行敲出来的,而是用Qt Designer生成.ui文件,然后在命令行中用 python -m pyuic5 -o interface.py interface.ui 将其转换成.py文件。
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(500, 500)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setObjectName("pushButton")
self.verticalLayout.addWidget(self.pushButton)
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit.setObjectName("textEdit")
self.verticalLayout.addWidget(self.textEdit)
self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 273, 22))
self.menubar.setObjectName("menubar")
self.menufile = QtWidgets.QMenu(self.menubar)
self.menufile.setObjectName("menufile")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.menubar.addAction(self.menufile.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "OCR截屏文字识别助手"))
self.pushButton.setText(_translate("MainWindow", "识别文字"))
self.menufile.setTitle(_translate("MainWindow", "file"))
截屏识别的过程分为三步:
读取截屏;
使用PaddleOCR识别截屏;
将识别结果打印到textEdit控件中。
Python自带的ImageGrab库提供截屏读取的方法,一行代码即可搞定。在MAC系统中也有截屏功能,但是不能被函数读取,也就无法实现完整的识别功能。
为了缩减代码量,这里直接使用PaddleHub提供的OCR模型。需要注意的是,该模型依赖第三方库shapely和pyclipper,使用前请先安装这两个库。hub提供了两种方法运行模型——命令行和API。命令行操作简单,只需一行代码:$ hub run chinese_ocr_db_crnn_mobile --input_path "/PATH/TO/IMAGE" 。不过我们在Python代码中使用hub还是以API方式为主:
def recognize_text(images=[],
paths=[],
use_gpu=False,
output_dir='ocr_result',
visualization=False,
box_thresh=0.5,
text_thresh=0.5)
参数
paths (list[str]): 图片的路径;
images (list[numpy.ndarray]): 图片数据,ndarray.shape 为 [H, W, C],BGR格式;
use_gpu (bool): 是否使用 GPU;若使用GPU,请先设置CUDA_VISIBLE_DEVICES环境变量
box_thresh (float): 检测文本框置信度的阈值;
text_thresh (float): 识别中文文本置信度的阈值;
visualization (bool): 是否将识别结果保存为图片文件;
output_dir (str): 图片的保存路径,默认设为 ocr_result;
paths是图片的路径,值为字符;images是图片读取后的格式,值为ndarray。由于我们的截屏是通过ImageGrab.grabclipboard()函数直接读取得到的ndarray格式,所以需要用images参数。如果是本地的图片则可以通过paths参数指定,不需要先用诸如cv2.imread的方式读取再用images。
预测的结果以JSON格式保存:
res (list[dict]): 识别结果的列表,列表中每一个元素为 dict,各字段为:
text(str): 识别得到的文本
confidence(float): 识别文本结果置信度
text_box_position(list): 文本框在原图中的像素坐标,4*2的矩阵,依次表示文本框左下、右下、右上、左上顶点的坐标 如果无识别结果则data为[]
data (list[dict]): 识别文本结果,列表中每一个元素为 dict,各字段为:
save_path (str, optional): 识别结果的保存路径,如不保存图片则save_path为''
我们只需要识别的文本内容,所以记录data中的text值即可。
from interface import Ui_MainWindow
from PIL import Image, ImageGrab
import cv2
import numpy as np
from PyQt5.QtWidgets import QApplication, QMainWindow
import paddlehub as hub
class run(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
# 读取截图,返回截图
def imread(self, ui):
img = ImageGrab.grabclipboard()
try:
img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
except TypeError:
ui.textEdit.setText("请先截图再点击按钮!")
return
else:
return img
# 使用PPOCR识别截图,返回识别结果
def imrec(self, img):
ocr = hub.Module(name="chinese_ocr_db_crnn_mobile")
result = ocr.recognize_text(images=[img])
res = str()
for data in result[0]["data"]:
res += data["text"]
return res
# 将识别结果打印到文本框中
def print_res(self, ui, res):
ui.textEdit.setText(res)
def all(self, ui):
img = self.imread(ui)
if not isinstance(img, np.ndarray):
return
res = self.imrec(img)
self.print_res(ui, res)
main.py作为整个PyQt5的入口,通过信号与槽的方式连接识别算法。
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow
import interface
import screen_rec
if __name__ == '__main__':
app = QApplication(sys.argv)
# 生成主框口
MainWindow = QMainWindow()
# 定义自己设计的ui
ui = interface.Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
# 向槽函数传递ui即可修改textEdit控件
ui.pushButton.clicked.connect(lambda: screen_rec.run().all(ui))
sys.exit(app.exec_())
演示
软件请在Windows 10操作系统下运行。截图功能MAC虽然 Command + Shift + 4 也能用,但得到的图片无法粘贴保存,因此无法通过函数读取。
建议使用Miniconda管理你的包和环境,防止出现奇怪的版本兼容问题。
Win + Shift + S 弹出截屏徽标;
成功截取图片后点击软件的 识别文字 开始识别;
识别完成后在下方文本框中输出可编辑的识别结果。
图9-动画演示
总结与展望
经测试,在AMD 锐龙2500U处理器中,识别200字符需要9秒,1000字符需要25秒,而在AI Studio提供的Intel(R) Xeon(R) Gold 6148处理器分别为2秒和12秒,这得益于其小巧的模型尺寸。
有时候照着敲费时费力,截屏识别能够将截屏翻译成可编辑的文本,极大提升了生产力速度。值得注意的是,项目还处于迭代中,还有一些地方可以优化。比如内存泄漏问题,在识别完成后,内存并不会全部释放,考虑到PyQt主界面未关闭,加载进来的模型没有删除导致内存占用。而对于本地机器CPU性能较弱,识别时间较久的,可以考虑接入百度的OCR文字识别API,实现准确、快速识别。
如在使用过程中有问题,可加入飞桨官方QQ群进行交流:1108045677。
如果您想详细了解更多飞桨的相关内容,请参阅以下文档。
·飞桨PaddleOCR项目地址·
GitHub:
https://github.com/PaddlePaddle/PaddleOCR
Gitee:
https://Gitee.com/PaddlePaddle/PaddleOCR
·飞桨官网地址·
https://www.paddlepaddle.org.cn/
飞桨(PaddlePaddle)以百度多年的深度学习技术研究和业务应用为基础,是中国首个开源开放、技术领先、功能完备的产业级深度学习平台,包括飞桨开源平台和飞桨企业版。飞桨开源平台包含核心框架、基础模型库、端到端开发套件与工具组件,持续开源核心能力,为产业、学术、科研创新提供基础底座。飞桨企业版基于飞桨开源平台,针对企业级需求增强了相应特性,包含零门槛AI开发平台EasyDL和全功能AI开发平台BML。EasyDL主要面向中小企业,提供零门槛、预置丰富网络和模型、便捷高效的开发平台;BML是为大型企业提供的功能全面、可灵活定制和被深度集成的开发平台。
扫描二维码 | 关注我们
微信号 : PaddleOpenSource
END
精彩活动
除特别声明,本站所有文章均为原创,如需转载请以超级链接形式注明出处:SmartCat's Blog
精华推荐