【飛槳開發(fā)者說】邵紳宸,北華航天工業(yè)學(xué)院2018級計算機科學(xué)與技術(shù)專業(yè),飛槳開發(fā)者技術(shù)專家PPDE,2020年國家級大學(xué)生創(chuàng)新創(chuàng)業(yè)訓(xùn)練計劃省級立項,研究方向為計算機視覺。
項目背景
Windows操作系統(tǒng)有著龐大的用戶數(shù)量,憑借優(yōu)異的人機操作、較好的軟硬件支持以及早期占據(jù)的市場,至2020年,其全球用戶數(shù)超過10億。
為了實現(xiàn)高效辦公,勤勞的開發(fā)者們創(chuàng)作了許多軟件滿足大家需求。比如某些網(wǎng)頁的文字被限制無法copy,百度半天也解決不了(沒錯,是我太菜
)。要不試試暴力方法,直接把電腦上文字當(dāng)成圖片來識別?但許多軟件都得要錢,免費的效果又一般,或者每天給你幾次體驗的機會。最重要的一點,上面的軟件都是識別本地圖片。就像共享單車解決了城市最后一公里,截屏識別節(jié)省了保存圖片再上傳識別這一過程。
本項目AI Studio地址:
識別模型就不得不提在GitHub霸占榜數(shù)日的。是百度開源的超輕量級OCR模型庫,提供了數(shù)十種文本檢測、識別模型,旨在打造一套豐富、領(lǐng)先、實用的文字檢測、識別模型/工具庫,助力使用者訓(xùn)練出更好的模型,并應(yīng)用落地。值得注意的是,使用DB文本檢測、CRNN文本識別的預(yù)訓(xùn)練模型mobile已加入,因此可以用hub在代碼中便捷地獲取和使用該模型。
圖1-某一免費OCR軟件部分界面
整體思路
既然想做一個截屏識別軟件,首先需要捕獲截屏,而內(nèi)容的識別交給模型,最后把識別的結(jié)果輸出到文本框即可。截屏捕獲和結(jié)果輸出都很簡單,主要問題就在于用什么模型來檢測和識別文字?
由于分割網(wǎng)絡(luò)的結(jié)果可以準(zhǔn)確描述諸如扭曲文本的場景,因而基于分割的自然場景文本檢測方法變得流行起來。基于分割的方法其中關(guān)鍵的步驟是其后處理部分,這步中將分割的結(jié)果轉(zhuǎn)換為文本框或是文本區(qū)域。DB文本檢測算法也是基于分割,但是通過提出 (DB)來簡化分割后的處理步驟,并且可以設(shè)定自適應(yīng)閾值來提升網(wǎng)絡(luò)性能。
圖2-DB網(wǎng)絡(luò)架構(gòu)
模型的網(wǎng)絡(luò)架構(gòu)見圖2,輸入圖像經(jīng)過不同stage的采樣之后得到不同的特征圖,之后用這些特征圖構(gòu)建特征金字塔,最終輸出原圖1/4尺寸的特征圖F,并用它來同時預(yù)測概率圖P和閾值圖T,由P和T計算后得到近似二值圖B。前面說到DB可以對每一個像素點進(jìn)行自適應(yīng)二值化,閾值由網(wǎng)絡(luò)學(xué)習(xí)得到,徹底將二值化這一步驟加入到網(wǎng)絡(luò)中一起訓(xùn)練,二值化公式如下,k為放大因子,依經(jīng)驗設(shè)定為50。
圖3-二值化公式和sigmoid對比
損失函數(shù)由概率圖損失、二值圖損失和閾值圖損失構(gòu)成。其中,α和β分別設(shè)置為1和10。和使用二值交叉熵?fù)p失函數(shù),使用L1距離損失函數(shù)。
標(biāo)簽的生成按照PSENet方式,收縮比例r設(shè)置為0.4,將文本框分別向內(nèi)向外收縮和擴(kuò)張D個像素,然后計算收縮框和擴(kuò)張框之間差集部分里每個像素點到原始圖像邊界的歸一化距離。下面公式里的S和C代表面積和周長。
說完了檢測算法,就該講講識別算法了。一種流行且簡單的識別算法是CRNN(An End-to-End Neural Network for Image-based and Its to Scene Text ),它的網(wǎng)絡(luò)架構(gòu)如下:
圖4-CRNN網(wǎng)絡(luò)架構(gòu)
從下往上看,第一部分是卷積層,使用VGG提取輸入圖像的特征。VGG是經(jīng)典的卷積神經(jīng)網(wǎng)絡(luò),它的規(guī)則十分簡單:連續(xù)使用數(shù)個填充為1、窗口形狀為的3×3卷積層后接上一個步幅為2、窗口形狀為2×2的最大池化層。卷積層保持輸入的高和寬不變,通道數(shù)翻倍,池化層減半高和寬,最后接上全連接和激活函數(shù)softmax實現(xiàn)分類。
圖5-VGG模型
VGG跟之前網(wǎng)絡(luò)不同的一點在于它用連續(xù)的3×3卷積核代替較大的卷積核(11×11,7×7,5×5),這不僅減少了參數(shù)量,還能帶來和大感受野相同的效果。
第二部分用LSTM進(jìn)一步提取圖像卷積特征中的序列特征。文字識別主要有兩種方法,一種是基于字符/單詞的識別,一種是基于序列的識別。基于字符/單詞的識別通過切割將文本段中的每一個字符/單詞單獨提取然后識別,本質(zhì)上就是圖像分類。但是切割的好壞直接影響識別結(jié)果,如果無法準(zhǔn)確切割出字符/單詞,是難以識別出結(jié)果的。基于序列的識別將文字識別看成是序列識別,這保證了算法不會漏掉字符/單詞,并且還能夠結(jié)合上下文,更好地處理序列信息。
圖6-基于字符/單詞的識別
圖7-基于序列的識別
不過RNN的輸出序列X和真實的標(biāo)簽序列很難做到嚴(yán)格對齊,所以引入了CTC解決訓(xùn)練時字符無法對齊的問題。為了更好理解CTC對齊方法,這里舉個簡單的例子。假設(shè)我們使用CNN+RNN(GRU)來預(yù)測圖5中的文字,輸出的結(jié)果并不是我們期望的“飛槳”,有一些部分重合,也有一些部分由于文字之間的間隔得到的空白部分。雖然CRNN由不同的網(wǎng)絡(luò)架構(gòu)組成,但可以通過一個損失函數(shù)進(jìn)行聯(lián)合訓(xùn)練。模型的更多細(xì)節(jié)可參考原論文:。
我們利用PyQt的可視化功能,將截屏讀取、文字識別集成到控件中,將輸出結(jié)果放到控件中,就能夠做一個簡單的GUI。源代碼及其使用方法可以在 中找到。
代碼實現(xiàn)
項目的代碼結(jié)構(gòu)如下,.txt為需要安裝的模塊。
圖8-代碼結(jié)構(gòu)
PyQt的用戶界面代碼,這里并不是一行一行敲出來的,而是用Qt 生成.ui文件,然后在命令行中用 python -m pyuic5 -o .py .ui 將其轉(zhuǎn)換成.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"))??
截屏識別的過程分為三步:
讀取截屏;
使用識別截屏;
將識別結(jié)果打印到控件中。
Python自帶的庫提供截屏讀取的方法,一行代碼即可搞定。在MAC系統(tǒng)中也有截屏功能,但是不能被函數(shù)讀取,也就無法實現(xiàn)完整的識別功能。
為了縮減代碼量,這里直接使用提供的OCR模型。需要注意的是,該模型依賴第三方庫shapely和,使用前請先安裝這兩個庫。hub提供了兩種方法運行模型——命令行和API。命令行操作簡單,只需一行代碼:$ hub run mobile -- "/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)??
參數(shù)
paths是圖片的路徑,值為字符;images是圖片讀取后的格式,值為ndarray。由于我們的截屏是通過.()函數(shù)直接讀取得到的ndarray格式,所以需要用images參數(shù)。如果是本地的圖片則可以通過paths參數(shù)指定,不需要先用諸如cv2.imread的方式讀取再用images。
預(yù)測的結(jié)果以JSON格式保存:
我們只需要識別的文本內(nèi)容,所以記錄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識別截圖,返回識別結(jié)果??
????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??
????#?將識別結(jié)果打印到文本框中??
????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()??
????#?定義自己設(shè)計的ui??
????ui?=?interface.Ui_MainWindow()??
????ui.setupUi(MainWindow)??
????MainWindow.show()??
????#?向槽函數(shù)傳遞ui即可修改textEdit控件??
????ui.pushButton.clicked.connect(lambda:?screen_rec.run().all(ui))??
????sys.exit(app.exec_())?
演示
軟件請在Windows 10操作系統(tǒng)下運行。截圖功能MAC雖然 Command + Shift + 4 也能用,但得到的圖片無法粘貼保存,因此無法通過函數(shù)讀取。
建議使用管理你的包和環(huán)境,防止出現(xiàn)奇怪的版本兼容問題。
Win + Shift + S 彈出截屏徽標(biāo);
成功截取圖片后點擊軟件的 識別文字 開始識別;
識別完成后在下方文本框中輸出可編輯的識別結(jié)果。
圖9-動畫演示
總結(jié)與展望
經(jīng)測試,在AMD 銳龍2500U處理器中,識別200字符需要9秒,1000字符需要25秒,而在AI Studio提供的Intel(R) Xeon(R) Gold 6148處理器分別為2秒和12秒,這得益于其小巧的模型尺寸。
有時候照著敲費時費力,截屏識別能夠?qū)⒔仄练g成可編輯的文本,極大提升了生產(chǎn)力速度。值得注意的是,項目還處于迭代中,還有一些地方可以優(yōu)化。比如內(nèi)存泄漏問題,在識別完成后,內(nèi)存并不會全部釋放,考慮到PyQt主界面未關(guān)閉,加載進(jìn)來的模型沒有刪除導(dǎo)致內(nèi)存占用。而對于本地機器CPU性能較弱,識別時間較久的,可以考慮接入百度的OCR文字識別API,實現(xiàn)準(zhǔn)確、快速識別。
如在使用過程中有問題,可加入飛槳官方QQ群進(jìn)行交流:。
*請認(rèn)真填寫需求信息,我們會在24小時內(nèi)與您取得聯(lián)系。