一款
截圖識别文字的OCR工具主要涉及2個環境:
- 截圖
- OCR識别
前要
OCR的應用場景
根據OCR的應用場景而言,我們可以大緻分成識别特定場景下的專用OCR以及識别多種場景下的通用OCR。就前者而言,證件識别以及車牌識别就是專用OCR的典型案例。針對特定場景進行設計、優化以達到最好的特定場景下的效果展示。那通用的OCR就是使用在更多、更複雜的場景下,擁有比較好的泛性。在這個過程中由于場景的不确定性,比如:圖檔背景極其豐富、亮度不均衡、光照不均衡、殘缺遮擋、文字扭曲、字型多樣等等問題,會帶來極大的挑戰。
OCR的技術路線
典型的OCR技術路線如下圖所示:
其中OCR識别的關鍵路徑在于文字檢測和文本識别部分,這也是深度學習技術可以充分發揮功效的地方。PaddleHub為大家開源的預訓練模型的網絡結構是Differentiable Binarization+ CRNN,基于icdar2015資料集下進行的訓練。
首先,DB是一種基于分割的文本檢測算法。在各種文本檢測算法中,基于分割的檢測算法可以更好地處理彎曲等不規則形狀文本,是以往往能取得更好的檢測效果。但分割法後處理步驟中将分割結果轉化為檢測框的流程複雜,耗時嚴重。是以作者提出一個可微的二值化子產品(Differentiable Binarization,簡稱DB),将二值化門檻值加入訓練中學習,可以獲得更準确的檢測邊界,進而簡化後處理流程。DB算法最終在5個資料集上達到了state-of-art的效果和性能。參考論文:Real-time Scene Text Detection with Differentiable Binarization
下圖是DB算法的結構圖:
接着,我們使用 CRNN(Convolutional Recurrent Neural Network)即卷積遞歸神經網絡,是DCNN和RNN的組合,專門用于識别圖像中的序列式對象。與CTC loss配合使用,進行文字識别,可以直接從文本詞級或行級的标注中學習,不需要詳細的字元級的标注。參考論文:An end-to-end trainable neural network for image-based sequence recognition and its application to scene text recognition
下圖是CRNN的網絡結構圖:
截圖工具
很多人會把它想的非常複雜,其實,Python中有很多可以實作截圖的庫或者函數,最常見的有三種方法。
一、Python調用windows API實作螢幕截圖
二、使用PIL的ImageGrab子產品
三、使用Selenium截圖
而我們需要做到的事滑鼠框選範圍截圖,是以我們采用PyQt5和PIL實作截圖功能。
我們隻需要把
滑鼠框選
的
起點
和
終點
坐标傳給grab方法就可以實作截圖功能。
那麼,現在問題就轉化為
如何擷取滑鼠框選的起點和終點?Textshot通過調用PyQt5并繼承QWidget來實作滑鼠框選過程中的一些方法來擷取框選的起點和終點。
Textshot繼承和重寫QWidget方法主要包括如下幾個,
-
:鍵盤響應函數keyPressEvent(self, event)
-
:UI繪制函數paintEvent(self, event)
-
:滑鼠點選事件mousePressEvent(self, event)
-
:滑鼠移動事件mouseMoveEvent(self, event)
-
:滑鼠釋放事件mouseReleaseEvent(self, event)
可以看出,上面重寫的方法以及囊括了截圖過程中涉及的各個動作,
- 點選滑鼠
- 拖動、繪制截圖框
- 釋放滑鼠
當然了,這一部分有現成的輪子
可以直接使用:
class Snipper(QtWidgets.QWidget):
def __init__(self, parent=None, flags=Qt.WindowFlags()):
super().__init__(parent=parent, flags=flags)
self.setWindowTitle("TextShot")
self.setWindowFlags(
Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Dialog
)
self.setWindowState(self.windowState() | Qt.WindowFullScreen)
self.screen = QtGui.QScreen.grabWindow(
QtWidgets.QApplication.primaryScreen(),
QtWidgets.QApplication.desktop().winId(),
)
palette = QtGui.QPalette()
palette.setBrush(self.backgroundRole(), QtGui.QBrush(self.screen))
self.setPalette(palette)
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CrossCursor))
self.start, self.end = QtCore.QPoint(), QtCore.QPoint()
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape:
QtWidgets.QApplication.quit()
return super().keyPressEvent(event)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setPen(Qt.NoPen)
painter.setBrush(QtGui.QColor(0, 0, 0, 100))
painter.drawRect(0, 0, self.width(), self.height())
if self.start == self.end:
return super().paintEvent(event)
painter.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255), 3))
painter.setBrush(painter.background())
painter.drawRect(QtCore.QRect(self.start, self.end))
return super().paintEvent(event)
def mousePressEvent(self, event):
self.start = self.end = QtGui.QCursor.pos()
self.update()
return super().mousePressEvent(event)
def mouseMoveEvent(self, event):
self.end = QtGui.QCursor.pos()
self.update()
return super().mousePressEvent(event)
def mouseReleaseEvent(self, event):
if self.start == self.end:
return super().mouseReleaseEvent(event)
self.hide()
QtWidgets.QApplication.processEvents()
shot = self.screen.copy(QtCore.QRect(self.start, self.end))
processImage(shot)
QtWidgets.QApplication.quit()
def processImage(img):
buffer = QtCore.QBuffer()
buffer.open(QtCore.QBuffer.ReadWrite)
img.save(buffer, "PNG")
pil_img = Image.open(io.BytesIO(buffer.data()))
buffer.close()
if __name__ == '__main__':
QtCore.QCoreApplication.setAttribute(Qt.AA_DisableHighDpiScaling)
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
snipper = Snipper(window)
snipper.show()
sys.exit(app.exec_())
OCR文字識别
那麼我們的文字識别模型選擇了Paddle最新推出的OCR識别模型。改模型同時支援中英文識别;支援傾斜、豎排等多種方向文字識别。
識别文字算法采用CRNN (Convolutional Recurrent Neural Network)即卷積遞歸神經網絡。其是DCNN和RNN的組合,專門用于識别圖像中的序列式對象。與CTC loss配合使用,進行文字識别,可以直接從文本詞級或行級的标注中學習,不需要詳細的字元級的标注。該Module是一個通用的OCR模型,支援直接預測。
這一步我們就要做的是将截取的圖檔傳入文字識别模型即可。
import os
os.environ['HUB_HOME'] = "./modules"
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import Qt
from PIL import Image
import io
import sys
import numpy as np
import paddlehub as hub
class Snipper(QtWidgets.QWidget):
def __init__(self, parent=None, flags=Qt.WindowFlags()):
super().__init__(parent=parent, flags=flags)
self.setWindowTitle("TextShot")
self.setWindowFlags(
Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Dialog
)
self.setWindowState(self.windowState() | Qt.WindowFullScreen)
self.screen = QtGui.QScreen.grabWindow(
QtWidgets.QApplication.primaryScreen(),
QtWidgets.QApplication.desktop().winId(),
)
palette = QtGui.QPalette()
palette.setBrush(self.backgroundRole(), QtGui.QBrush(self.screen))
self.setPalette(palette)
QtWidgets.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.CrossCursor))
self.start, self.end = QtCore.QPoint(), QtCore.QPoint()
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape:
QtWidgets.QApplication.quit()
return super().keyPressEvent(event)
def paintEvent(self, event):
painter = QtGui.QPainter(self)
painter.setPen(Qt.NoPen)
painter.setBrush(QtGui.QColor(0, 0, 0, 100))
painter.drawRect(0, 0, self.width(), self.height())
if self.start == self.end:
return super().paintEvent(event)
painter.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255), 3))
painter.setBrush(painter.background())
painter.drawRect(QtCore.QRect(self.start, self.end))
return super().paintEvent(event)
def mousePressEvent(self, event):
self.start = self.end = QtGui.QCursor.pos()
self.update()
return super().mousePressEvent(event)
def mouseMoveEvent(self, event):
self.end = QtGui.QCursor.pos()
self.update()
return super().mousePressEvent(event)
def mouseReleaseEvent(self, event):
if self.start == self.end:
return super().mouseReleaseEvent(event)
self.hide()
QtWidgets.QApplication.processEvents()
shot = self.screen.copy(QtCore.QRect(self.start, self.end))
processImage(shot)
QtWidgets.QApplication.quit()
def processImage(img):
buffer = QtCore.QBuffer()
buffer.open(QtCore.QBuffer.ReadWrite)
img.save(buffer, "PNG")
pil_img = Image.open(io.BytesIO(buffer.data()))
buffer.close()
np_images = [np.array(pil_img)]
results = ocr.recognize_text(
images=np_images, # 圖檔資料,ndarray.shape 為 [H, W, C],BGR格式;
use_gpu=False, # 是否使用 GPU;若使用GPU,請先設定CUDA_VISIBLE_DEVICES環境變量
output_dir='ocr_result', # 圖檔的儲存路徑,預設設為 ocr_result;
visualization=True, # 是否将識别結果儲存為圖檔檔案;
box_thresh=0.5, # 檢測文本框置信度的門檻值;
text_thresh=0.5) # 識别中文文本置信度的門檻值;
text = []
for result in results:
data = result['data']
save_path = result['save_path']
for infomation in data:
print('text: ', infomation['text'], 'nconfidence: ', infomation['confidence'], 'ntext_box_position: ',
infomation['text_box_position'])
text.append(str(infomation['text']) + 'n')
print(text)
with open('data.txt', 'w') as f:
for i in text:
f.write(str(i))
os.system(r'data.txt')
if __name__ == '__main__':
# 加載移動端預訓練模型
# ocr = hub.Module(name="chinese_ocr_db_crnn_mobile")
# 服務端可以加載大模型,效果更好
ocr = hub.Module(name="chinese_ocr_db_crnn_server")
QtCore.QCoreApplication.setAttribute(Qt.AA_DisableHighDpiScaling)
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
snipper = Snipper(window)
snipper.show()
sys.exit(app.exec_())
那麼我們可以測試一下它的效果:
那麼再看一些模型的其他應用吧:
更多詳情可以檢視:https://www.paddlepaddle.org.cn/hub/scene/ocr
截圖&OCR項目位址:https://github.com/chenqianhe/-screenshot_and_ocr-