OCR-驗證碼識別理論與實作

Cinnamon AI Taiwan
8 min readMar 24, 2020

OCR全名為Optical Character Recognition,也就是光學字元識別,其應用廣泛出現於我們日常生活中,而OCR也是Cinnamon AI的重點技術之一,今天就讓我們藉由驗證碼識別,帶大家來認識OCR的魅力。

OCR簡介

OCR的本質就是將圖片轉為文字,關於這樣的一個功能可以是很多應用方式的起手式,為了應對資訊化大數據社會,過去的紙本保存方式已經不夠有效率,OCR可以將各式各樣的契約、收據轉換成一定的格式,儲存在資料庫內,也就是有效率地將『 Unstructured Data 』轉換為『 Structured Data 』,其他常見應用如車牌辨識、街景文字辨識等等。

OCR實作

1.本次實作主要使用PyTorch框架

2.讀者們需要具備CNN、遞迴神經網路概念,還不熟悉CNN的讀者們可以參考: CNN原理與實作

CRNN模型

CRNN(Convolutional Recurrent Neural Network,卷積循環神經網絡)為本次實作模型的Backbone,概念來自此篇論文:An End-to-End Trainable Neural Network for Image-based Sequence Recognition and Its Application to Scene Text Recognition (論文連結)。

CRNN 及為 CNN + RNN 的結構,我們藉由 CNN Backbone 提取圖像特徵,並將提出之特徵依序輸入RNN網路層(此處RNN泛指遞迴神經網路結構ex. GRU、LSTM)。

下圖很好的解釋了CRNN的用意,在深度學習的領域中,當輸入為影像時,我們自然而然會想到使用CNN來獲得圖像特徵,而RNN在這裡扮演的角色,就是讓我們的模型『有順序的』學到圖像特徵,常見的方式為由左至右(依照我們書寫的習慣)。

CRNN概念示意[Source]

搭建CRNN模型

下方程式碼為大家展示如何使用PyTorch 框架搭建一個CRNN結構

  • make_layers : 搭建一個CNN Backbone
  • BiRNN_GRU : 搭建 Bidirectional-GRU 層,Bidirectional與一般RNN網路差異在於雙向提取特徵與單向提取特徵,有興趣可參考:神經網路
  • CRNN: 結合CNN+RNN網路。
General RNN [Source]
Bidirectional-RNN[Source]
CRNN模型

結合CNN與RNN

在 PyTorch中,輸入影像格式為(batch, channels, height, width),我們這邊以(1, 3, 64, 128)為例,也就是輸入一張RGB影像,高寬為(64,128),我們藉由Strides、Pooling等方式將輸出CNN時的特徵圖尺寸控制為(1, 512, 1, 15),直觀理解就是將高度上的訊息壓在一起,接著我們squeeze 第二維度並將格式permute成(timesteps, batch_size, features),以便輸入RNN做後續處理。

輸入影像尺寸

GRU輸出

GRU的輸入特徵尺寸為(15, 1, 512) 輸出為 (15, 1, 37),我們可以理解為模型將原圖由左至右分為15等分,每一個等分都要預測一個類別,37個類別包含10個數字(0–9)+26個英文字母+空格,要分為幾等分並沒有嚴格限制,但要符合一個原則,也就是等分≥2*最長字串+1,這個部分就牽扯到了我們使用的Loss function ,也就是CTC Loss(Connectionist temporal classification),類別之所以要有空格也與CTC Loss相關。

CTC Loss

CTC Loss 的核心概念在於『優化整條序列』,而不是如一般的Loss只考慮當下的狀況,以上方GRU 輸出為例,我們也可以為每一個等分接上Cross Entropy預測其最有可能類別,然而此時就會出現兩個問題:

  1. 在這種模型結構下,Cross Entropy無法應付不定字串長度的情況(不然就要搭配Attention結構)。
  2. Cross Entropy Loss 只針對各別等分優化,並沒有考慮前後等分的可能性。

針對上述兩種情況,CTC Loss給出了很好的解答:

  1. 透過預測空格解決不定字串長度的問題。
  2. 優化整條序列而不是只考慮個別等分類別。
CTC Loss概念[Source]

這就是為什麼預測類別要加上『空格』,其主要目的就是要解決不定長度字串的問題,而上方我們也提到切割等分要≥2*最長字串+1,這主要是因為CTC Loss前題假設為每個字串之間都至少存在一個空格,並且預測頭尾也都是空格,因此假設最長字串是4個字元,符合上述假設的輸出就至少要是9等分,至於大於9等分的部分模型就會學習運用更多空格或重複字元來補齊。

以下圖為例,輸出為15等分,模型輸出看起來會像是[空格,P,P,P,空格,空格,4,4,空格,L,空格,K,K,空格,空格],在Decode時我們會忽視空格與空格間字元重複次數,如P,P,P,由於為連續預測因此只取一個,如果下方字元變為PPLK,那PP之間就會出現空格如[空格,P,P,P,空格,空格,P,P,空格,L,空格,K,K,空格,空格]。由於CTC Loss 的數學推導較為複雜,在這就不先深究,有興趣讀者可以參考:深度理解CTC

完整步驟

接下來我們就將完整程式碼串上,為大家示範如何破解驗證碼

  • 首先我們import 需要套件並制定產生驗證碼尺寸與字元

width, height, n_len, n_class = 128, 64, 4, len(characters) 訂定影像寬、高、長度、類別,在這我們先示範固定長度預測

random_str = ‘’.join([random.choice(characters) for j in range(n_len)]) 從characters中隨機取出字元,而這裡的characters包含10個數字+26個英文字母。

  • 定義Data Loader來輸出x_train, y_train

這邊的y_train為一個list,在我們的範例裡,由於batch_size = 8,每一個字串長度為4,因此看起來就會像是[14,21,2,4,23,12,……..]共32個編號,今天如果是不定長度也是相同格式,將所有編號放入一個list中,而在CTC Loss中會需要輸入另一項參數告訴CTC Loss此字串長度,如[4,4,4,4,4,4,4,4]。

  • 模型搭建(參考上方)與基本設定

input_lengths = 模型輸出等分*batch_size,在我們範例中就是[15,15,15,15,15,15,15,15,]

target_lengths = 這個部分是用來告訴模型字串長度,在我們範例中就是[4,4,4,4,4,4,4,4],如果是不定長度字串就可能是[4,2,1,5,3,2…..]。所以在訓練時我們是需要知道字串長度才能訓練不定長度字串預測的模型。

  • Train
  • 預測

Decode時我們會先判定預測是否為0 (代表空格),不是的話查看預測是否與前次預測相同,如果也不同,我們就將這個預測放入輸出字串中。

完整程式代碼如下

結論

今天帶大家認識並實作CRNN+CTC模型,同樣模型可以用於預測任何不定字串的問題,如車牌、文本等等,讀者們也可以自己嘗試修改Loader與target_lengths來訓練不定長度的模型,如有任何疑問或衍生討論也都歡迎留言。

Bootcamp招募中

【Cinnamon — 2020 Summer Student Bootcamp AI產品實作營 開放招募中!】
Bootcamp 亮點:
- 實作:以AI技術為主題,實際發想並做出一個小產品!
- 指導:每位學員配對一位職場導師,實際給予指導與建議!
- 認證:確實完成作品者將獲得實作經驗證書!
- 機會:展現的舞台,表現優秀者將有機會成為我們的新夥伴!歡迎至Yourator查閱更詳細的實作營資訊:Bootcamp報名連結

--

--