天天看點

一文帶你讀懂 OCR

一文帶你讀懂 OCR

簡介

OCR,或光學字元識别,是最早的計算機視覺任務之一,因為在某些方面它不需要用到深度學習。是以,早在2012年深度學習熱潮之前,OCR就有了各種不同的應用,有些甚至可以追溯到1914年 。

這使得很多人認為OCR的挑戰已經“解決”,不再具有挑戰性。另一種來自相似來源的觀點認為OCR不需要深度學習,換句話說,對OCR使用深度學習是多餘的。

任何從事計算機視覺或機器學習的人都知道,沒有解決任務這回事,這種情況也沒有什麼不同。相反,OCR隻能在非常特定的用例中産生非常好的效果,但是一般來說,它仍然被認為是具有挑戰性的。

此外,對于某些不需要深度學習的OCR任務,确實有很好的解決方案。然而,要真正走向更好、更普遍的解決方案,深度學習将是必須的。

為什麼要寫關于OCR的文章

正如我的許多工作/寫作,這也是開始作為客戶做的項目。我被要求解決一個特定的OCR任務。

在完成這個任務的過程中以及之後,我得出了一些結論和見解,我很想和大家分享。另外,在對這個任務進行了密集的研究之後,我很難停下來把它扔掉,是以我繼續研究,希望能得到一個更好更一般化的解決方案。

你能從這篇文章中得到什麼

在這篇文章中,我将探索一些政策,方法和邏輯,用于處理不同的OCR任務,并将分享一些有用的方法。在最後一部分中,我們将使用代碼解決一個實際問題。不幸的是,這不應該被認為是一篇詳盡的論文,因為對于這種部落格文章來說,方法的深度、曆史和廣度都太寬泛了。

然而,和往常一樣,我不會介意您對文章、資料集、資料庫和其他相關博文的引用。

OCR的類型

正如我之前提到的,OCR的含義不止一個。在最一般的意義上,它指的是從所有可能的圖像中提取文本,無論是一本書的标準列印頁面,還是一幅随意有塗鴉的圖像(“ 自然環境下 ”)。在這期間,你可能會發現很多其他的任務,比如識别車牌,驗證碼,街道标志等等。

雖然每一種選擇都有其自身的困難,但顯然“自然環境下”的任務是最困難的。

一文帶你讀懂 OCR

左側:印刷的文本。右側:戶外的文本

從這些例子中,可以得出OCR任務的一些屬性:

  • 文本密度:在列印/書寫的頁面上,文本密度很大。然而,給定一個街道标志的圖像,文本是稀疏的。
  • 文本結構:頁面上的文本是有結構的,大部分是嚴格的行,而文本在野外可能分散在各處,以不同的角度旋轉。
  • 字型:印刷字型更容易,因為它們比那些繁雜的手寫字型更有結構。
  • 字元類型:文本可能來自不同的語言,它們之間可能有很大的不同。此外,文本的結構可能不同于數字,例如房屋編号等。
  • 人工制品:顯然,戶外照片比舒适的掃描器噪音大得多。
  • 位置:一些任務包括裁剪/居中文本,而在其他任務中,文本可能位于圖像中的随機位置。
一文帶你讀懂 OCR

License plates

另一個在實踐中不是很難并有用的普通挑戰是車牌識别。與大多數OCR任務一樣,此任務需要檢測牌照,然後識别它的字元。由于闆的形狀相對恒定,一些方法在實際識别數字之前使用簡單的整形方法。以下是網絡上的一些示例:

一文帶你讀懂 OCR

OpenALPR示例。汽車類型s abonus

  1. OpenALPR是一個非常強大的工具,沒有涉及深度學習,可以識别來自不同國家的車牌
  2. 該代碼倉庫提供了CRNN模型的實作(将進一步讨論)以識别南韓車牌。
  3. Supervise.ly,一家資料公用事業公司,撰寫了關于使用其工具生成的人工資料訓練車牌識别器的文章(人工資料也将進一步讨論)

CAPTCHA

由于網際網路上充滿了機器人,通常的做法是将它們與真人分開,這是視覺任務,特别是文本閱讀,即CAPTCHA。許多這些文本是随機的和扭曲的,這會使得計算機很難閱讀。我不确定開發CAPTCHA的人是否預測了計算機視覺的進步,但是今天的大多數文本CAPTCHAs并不是很難解決,特别是如果我們不是一次同時解決所有這些問題。

一文帶你讀懂 OCR

Facebook知道如何制作富有挑戰性的CAPTCHA

Adam Geitgey提供了一個很好的教程來解決一些深度學習的CAPTCHA,其中包括再次合成人工資料。

PDF OCR

OCR最常見的場景是列印OCR或pdf OCR。列印文檔的結構化特性使得解析它們變得更加容易。大多數OCR工具(例如Tesseract)主要用于解決此任務,并取得了良好的效果。是以,我不會在這篇文章中詳細闡述這項任務。

OCR in the wild

這是最具挑戰性的OCR任務,因為它将所有一般計算機視覺挑戰(例如噪聲,照明和人工)引入OCR。此任務的一些相關資料集是coco-text和SVT資料集,它們再次從街景圖像中提取文本。

一文帶你讀懂 OCR

COCO text example

合成文本

SynthText不是資料集,也許甚至不是一項任務,但提高訓練效率的一個好主意是生成人工資料。由于文本的平坦性,在圖像上添加随機字元或單詞看起來比任何其他對象更自然。

我們之前已經看到一些資料生成用于更容易的任務,如CAPTCHA和車牌。在野外生成文本有點複雜。該任務包括考慮圖像的深度資訊。幸運的是,SynthText是一個很好的工作,它接收帶有上述注釋的圖像,并智能地生成單詞(來自新聞討論區資料集)。

一文帶你讀懂 OCR

SynthText流程圖:右上角是圖像的分割,右下角是深度資料。左下角是圖像的表面分析,根據文本将其分散在圖像上。

為了使添加的文本看起來真實有用,SynthText庫為每個圖像采用兩個掩模,一個是深度,另一個是分割。如果您想使用自己的圖像,也應添加此資料。

  • 建議您檢查這個代碼倉庫并自行生成一些圖像。您應該注意repo使用一些過時版本的opencv和maptlotlib,是以可能需要進行一些修改。
一文帶你讀懂 OCR

Mnist

雖然不是真正的OCR任務,但是不可能寫出OCR後不包括Mnist示例。最著名的計算機視覺挑戰并不會真正考慮OCR任務,因為它一次隻包含一個字元(數字),而且隻包含10位數字。但是,它可能暗示為什麼OCR被認為是容易的。另外,在某些方法中,每個字母将被單獨檢測,然後Mnist(分類)模型變得相關。

一文帶你讀懂 OCR

政策

正如我們所看到和暗示的那樣,文本識别主要是兩步任務。首先,您希望檢測圖像中的文本外觀,可能是密集的(如列印文檔中)或稀疏(如野外文本)。

在檢測到行/字級别之後,我們可以從大量解決方案中再次選擇,這些解決方案通常來自三種主要方法:

  1. 傳統計算機視覺技術。
  2. 專業的深度學習。
  3. 标準深度學習方法(檢測)。

讓我們來具體看看每一個:

1. 傳統計算機視覺技術

如前所述,計算機視覺很長一段時間内解決了各種文本識别問題。你可以在網上找到很多例子:

  • 偉大的阿德裡安·羅斯布魯克在他的網站上有大量的教程,比如這個,這個和更多。
  • Stack overflow還有一些這種類型的

經典CV方法通常聲稱:

  1. 應用濾波器以使字元從背景中脫穎而出。
  2. 應用輪廓檢測逐個識别字元。
  3. 應用圖像分類來識别字元。

顯然,如果第二部分做得好,第三部分很容易使用模式比對或機器學習(例如Mnist)。

然而,輪廓檢測對于通用性非常具有挑戰性。它需要大量的手動微調,是以在大多數問題中變得不可行。例如,讓我們從這裡應用一個簡單的計算機視覺腳本來處理來自SVHN資料集的一些圖像。首次嘗試我們可能會取得非常好的結果:

一文帶你讀懂 OCR
一文帶你讀懂 OCR

但是當字元彼此靠近時,事情開始失效:

一文帶你讀懂 OCR
一文帶你讀懂 OCR

我已經找到了困難的方法,當你開始調整這些參數時,你可以減少這些錯誤,但不幸的是會導緻其他錯誤。換句話說,如果你的任務不簡單,那麼這些方法就不适用。

2. 專業的深度學習

大多數成功的深度學習方法都具有普遍性。但是,考慮到上述屬性,專用網絡非常有用。

我将在這裡研究一些突出方法的無窮無盡的樣本,并将對提供它們的文章做一個非常快速的總結。與往常一樣,每篇文章都以“任務X(文本識别)最近引起關注”開始,并繼續較長的描述他們的方法。仔細閱讀這些文章将揭示這些方法是從以前的深度學習/文本識别工作中組合而成的。

結果也被全面描述,但由于設計上的許多差異(包括資料集中的微小差異),實際比較是不可能的。實際了解這些方法在您的任務中的表現的唯一方法是擷取他們的代碼(最好的情況是:找到官方repo,找到非官方但評價很高的repo,自己實施)并嘗試使用您的資料。

是以,我們總是更喜歡帶有較好文章的repo,如果可能的話甚至有demo。

EAST

EAST(高效準确的場景文本檢測器)是一種簡單而強大的文本檢測方法。使用特殊的網絡。

與我們将要讨論的其他方法不同,它僅限于文本檢測(不是實際識别),但它的穩健性值得一提。另一個優點是它也被添加到open-CV庫(從版本4開始),是以您可以輕松使用它(請參閱此處的教程)。

該網絡實際上是衆所周知的U-Net的一個版本,它有助于檢測大小不同的特征。該網絡的底層前饋主幹(如文章中所示,見下圖)可能不同,文中使用PVANet,但opencv實作使用Resnet。顯然,它也可以預先訓練(例如用imagenet)。與在U-Net中一樣,特征是從網絡中的不同級别提取的。

一文帶你讀懂 OCR

最後,網絡允許兩種類型的輸出旋轉邊界框:帶有旋轉角度(2X2 + 1個參數)的标準邊界框或“四邊形”,它隻是一個帶有所有頂點坐标的旋轉邊界框。

一文帶你讀懂 OCR

如果真實生活結果如上圖所示,識别文本将不會花費太多精力。然而,現實生活中的結果并不完美。

CRNN

卷積遞歸神經網絡是2015年的一篇文章,該文章提出了一種混合(或三重混合?)端到端架構,旨在通過三步法捕捉文字。

這個想法如下:第一級是标準的完全卷積網絡。網絡的最後一層被定義為要特征層,并分為“特征列”。參見下圖了解每個此類特征列如何表示文本中的某個部分。

一文帶你讀懂 OCR

然後,将特征列饋送到輸出序列的深雙向LSTM,用于查找字元之間的關系。

一文帶你讀懂 OCR

最後,第三部分是轉錄層。它的目标是采用淩亂的字元序列,其中一些字元是多餘的而其他字元是空白的,并使用機率方法來統一并了解它。

這種方法稱為CTC損失,可在此處閱讀。該層可以與具有或不具有預定義詞典一起使用,這可以促進單詞的預測。

本文使用固定文本詞典達到很高的準确率(> 95%),并且在沒有固定文本詞典的情況下成功率不同。

STN-net/SEE

SEE  - 半監督端到端場景文本識别,是Christian Bartzi的作品。他和他的同僚應用真正的端到端政策來檢測和識别文本。他們使用非常弱的監督(他們稱之為半監督,與通常的意思不同)。因為他們隻使用文本注釋訓練網絡(沒有邊界框)。這允許他們使用更多資料,但是使他們的訓練過程非常具有挑戰性,并且他們讨論了使其工作的不同技巧,例如不訓練具有兩行以上文本的圖像(至少在訓練的第一階段)。

該論文的早期版本稱為STN OCR。在最後的論文中,研究人員對他們的方法和表現進行了改進,并且他們在高品質的結果的基礎上更加強調他們的方法的通用性。

一文帶你讀懂 OCR

SEE strategy

STN-OCR提示使用空間變換器(= STN,與最近的谷歌變壓器無關)的政策。

他們訓練兩個級聯網絡,其中第一個網絡,即變換器,學習圖像上的變換,以輸出更容易解釋的子圖像。

然後,另一個頂部有LSTM的前饋網絡(嗯......似乎我們之前已經看過它)來識别文本。

研究強調了使用resnet(它們使用兩次)的重要性,因為它為早期層提供了“強大”的傳播。但是現在這種做法很受歡迎。

無論哪種方式,這都是一種有趣的嘗試方法。

3.标準的深度學習方法

如标題所示,在檢測到“單詞”之後,我們可以應用标準的深度學習檢測方法,例如SSD,YOLO和Mask RCNN。由于網上有大量資訊,我不打算詳細說明這些方法。

我必須說這是目前我最喜歡的方法,因為我喜歡深度學習的是“端到端”的哲學,你應用一個強大的模型,通過一些調整将解決幾乎所有問題。在本文的下一部分中,我們将看到它實際上是如何工作的。

然而,SSD和其他檢測模型在涉及密集的類似類時受到挑戰,如此處所述。事實上,深度學習模型識别數字和字母比識别更具挑戰性和精心設計的物體(如狗,貓或人類)要困難得多。它們達不到所需的準确度,是以,專業方法茁壯成長。

實際例子

是以說完之後,是時候動手了,自己嘗試一些模型。我們将嘗試解決SVHN任務。SVHN資料包含三種不同的資料集:train,test和extra。差異不是100%明确,但是最大的額外資料集(約500K樣本)包括以某種方式更容易識别的圖像。是以,為了這個目的,我們将使用它。

需要做以下準備任務:

  • 你需要一台的GPU機器,Tensorflow≥1.4,Keras≥2
  • 從這裡克隆SSD_Keras項目。
  • 從此處下載下傳coco資料集上預先教育訓練的SSD300模型。
  • 從這裡克隆項目.
  • 下載下傳extra.tar.gz檔案,其中包含SVHN資料集的額外圖像。
  • 更新此項目倉庫中json_config.json中的所有相關路徑。

要有效地遵循該過程,您應該閱讀以下說明以及從項目的repo運作ssd_OCR.ipynb。

準備開始了!

第1步:解析資料

喜歡與否,但在檢測任務中沒有“黃金”格式的資料表示。一些衆所周知的格式是:coco,via,pascal,xml。還有更多。例如,SVHN資料集用不明确的.mat格式注釋。對我們來說幸運的是,這個gist提供了一個靈活的read_process_h5腳本來将.mat檔案轉換為标準的json,你應該提前一步并将其轉換為pascal格式,如下所示:

def json_to_pascal(json, filename): #filename is the .mat file
    # convert json to pascal and save as csv
    pascal_list = []
    for i in json:
        for j in range(len(i['labels'])):
            pascal_list.append({'fname': i['filename'] 
            ,'xmin': int(i['left'][j]), 'xmax': int(i['left'][j]+i['width'][j])
            ,'ymin': int(i['top'][j]),  'ymax': int(i['top'][j]+i['height'][j])
            ,'class_id': int(i['labels'][j])})
    df_pascal = pd.DataFrame(pascal_list,dtype='str')
    df_pascal.to_csv(filename,index=False)
p = read_process_h5(file_path)
json_to_pascal(p, data_folder+'pascal.csv')           

複制

現在我們應該有一個更标準的pascal.csv檔案,它将允許我們進步。如果轉換速度很慢,您應該注意我們不需要所有資料樣本。 ~10K就足夠了。

第2步:檢視資料

在開始模組化過程之前,您最好對資料進行一些探索。我隻提供了一個快速的健全測試功能,但我建議你做一些進一步的分析:

def viz_random_image(df):
    file = np.random.choice(df.fname)
    im = skimage.io.imread(data_folder+file)
    annots =  df[df.fname==file].iterrows()
    plt.figure(figsize=(6,6))
    plt.imshow(im)
    current_axis = plt.gca()
    for box in annots:
        label = box[1]['class_id']
        current_axis.add_patch(plt.Rectangle(
            (box[1]['xmin'], box[1]['ymin']), box[1]['xmax']-box[1]['xmin'],
            box[1]['ymax']-box[1]['ymin'], color='blue', fill=False, linewidth=2))  
        current_axis.text(box[1]['xmin'], box[1]['ymin'], label, size='x-large', color='white', bbox={'facecolor':'blue', 'alpha':1.0})
        plt.show()

viz_random_image(df)           

複制

一文帶你讀懂 OCR

SVHN資料集的一個代表樣本

對于以下步驟,我在repo中提供了utils_ssd.py,便于訓練,加載權重等。一些代碼來自SSD_Keras repo,它也被廣泛使用。

第3步:選擇政策

如前所述,我們有許多可能的方法來解決這個問題。在本教程中,我将采用标準的深度學習檢測方法,并将使用SSD檢測模型。我們将從使用SSD keras的實作。這是PierreLuigi的一個很好的實作。雖然它比 rykov8實作的GitHub星更少,但它似乎更新,并且更容易內建。當您選擇要使用的項目時,這是一個非常重要的事情。其他不錯的選擇将是YOLO模型和Mask RCNN。

步驟4:加載并訓練SSD模型

要使用repo,您需要驗證您是否擁有SSD_keras repo,并填寫json_config.json檔案中的路徑,以允許notebook查找路徑。

從import開始:

import os
import sys
import skimage.io
import scipy
import json
with open('json_config.json') as f:     json_conf = json.load(f)
ROOT_DIR = os.path.abspath(json_conf['ssd_folder']) # add here mask RCNN path
sys.path.append(ROOT_DIR)

import cv2
from utils_ssd import *
import pandas as pd
from PIL import Image

from matplotlib import pyplot as plt

%matplotlib inline
%load_ext autoreload
% autoreload 2           

複制

還有一些定義:

task = 'svhn'
labels_path = f'{data_folder}pascal.csv'
input_format = ['class_id','image_name','xmax','xmin','ymax','ymin' ]
    
df = pd.read_csv(labels_path)           

複制

模型配置:

class SVHN_Config(Config):
    batch_size = 8
    
    dataset_folder = data_folder
    task = task
    
    labels_path = labels_path

    input_format = input_format

conf=SVHN_Config()

resize = Resize(height=conf.img_height, width=conf.img_width)
trans = [resize]           

複制

定義模型,加載權重

與大多數深度學習案例一樣,我們不會從頭開始訓練,但我們會加載預先訓練過的權重。在這種情況下,我們将加載SSD模型在COCO資料集上訓練的權重,該資料集有80個類。顯然,我們的任務隻有10個類,是以我們将在加載權重後重建頂層以獲得正确的輸出數。我們在init_weights函數中執行此操作。旁注:在這種情況下,每個類(邊界框坐标)的輸出數量為44:4,而背景/無類别的輸出數量為4。

learner = SSD_finetune(conf)
learner.get_data(create_subset=True)

weights_destination_path=learner.init_weights()

learner.get_model(mode='training', weights_path = weights_destination_path)
model = learner.model
learner.get_input_encoder()
ssd_input_encoder = learner.ssd_input_encoder

# Training schedule definitions
adam = Adam(lr=0.0002, beta_1=0.9, beta_2=0.999, epsilon=1e-08, decay=0.0) 
ssd_loss = SSDLoss(neg_pos_ratio=3, n_neg_min=0, alpha=1.0)
model.compile(optimizer=adam, loss=ssd_loss.compute_loss)           

複制

定義資料加載器

train_annotation_file=f'{conf.dataset_folder}train_pascal.csv'
val_annotation_file=f'{conf.dataset_folder}val_pascal.csv'
subset_annotation_file=f'{conf.dataset_folder}small_pascal.csv'
batch_size=4
ret_5_elements={'original_images','processed_images','processed_labels','filenames','inverse_transform'}
train_generator = learner.get_generator(batch_size, trans=trans, anot_file=train_annotation_file,
                  encoder=ssd_input_encoder)
val_generator = learner.get_generator(batch_size,trans=trans, anot_file=val_annotation_file,
                 returns={'processed_images','encoded_labels'}, encoder=ssd_input_encoder,val=True)           

複制

步驟五:訓練模型

現在模型準備就緒,我們将設定一些最後的訓練相關定義,并開始訓練

learner.init_training()
history = learner.train(train_generator, val_generator, steps=100,epochs=80)           

複制

作為回報,我在訓練腳本中包含了training_plot回調,以便在每個epoch後可視化随機圖像。例如,這是第六epoch後的預測快照:

一文帶你讀懂 OCR

SSD_Keras repo在每個epoch後儲存模型,是以您可以通過将weights_destination_path行更改為等于路徑來稍後加載模型

weights_destination_path = <path>           

複制

如果你按照我的訓示,你應該能夠訓練模型。ssd_keras提供了更多功能,例如資料擴充,不同的加載器和評估器。經過短暫的訓練,我達到了大于80 mAP。

你達到了多高?

一文帶你讀懂 OCR

從tensorboard訓練4X100X60樣本

總結

在這篇文章中,我們讨論了OCR領域的不同挑戰和方法。在深度學習/計算機視覺中存在許多問題,它比起初看起來要多得多。我們已經看到了它的許多子任務,以及一些不同的方法來解決它。另一方面,我們已經看到,在沒有太多麻煩的情況下達到初步結果并不是很難。

希望你喜歡!

想要繼續檢視該篇文章相關連結和參考文獻?

戳連結或點選底部【閱讀原文】:

http://ai.yanxishe.com/page/TextTranslation/1162

資源推薦

機器學習相關書籍有很多,然而符合現在的深度學習研究習慣和知識面的優秀的深度學習教科書就很少了。這本由Ian Goodfellow、Yoshua Bengio、Aaron Courville三位現代深度學習中流砥柱人物在2017年編著的深度學習教科書可以算是最适合深度學習學習者的課本,它涵蓋了主要的數學知識、深度學習的基礎知識和原理,以及最新的方法和認識,而且在技術的應用方面也有許多具體介紹。由于書籍封面是一幅令人迷幻的花園照片,它也被親切地稱為“花書”。

這裡打包了中英兩個版本的書、書籍封面,以及一個彩蛋。喜歡彩蛋的請在評論區給個贊哦 :-P