1. 程式人生 > >深度學習實踐系列之--身份證上漢字及數字識別系統的實現(上)

深度學習實踐系列之--身份證上漢字及數字識別系統的實現(上)

手動 ear 常用 env 窗口 mic 文件下載 oot edr

前言:

本文章將記錄我利用深度學習方法實現身份證圖像的信息識別系統的實現過程,及學習到的心得與體會。本次實踐是我投身AI的初次系統化的付諸實踐,意義重大,讓自己成長許多。終於有空閑的時間,將其記錄,只為更好的分享與學習。

目錄:

1、本人的主要工作

2、關鍵技術

3、模型訓練

4、系統設計及實現

5、總結

正文:

一、本人的主要工作

深度學習技術與傳統模式識別技術相比,免去人工提取特征,識別率更高。我基於深度學習的技術背景,主要的研究內容如下:

1)身份證圖像涉及個人隱私,很難獲取其數據訓練集。針對此問題,我采用獲取身份證上印刷體漢字和數字的數據訓練集的方法,利用Python圖像庫(PIL)將13類漢字印刷體字體轉換成6492個類別,建立了較大的字符訓練集;

2)如何獲取身份證圖片上的字符是在設計中一個重要問題。我采用水平和垂直投影技術,首先對身份證圖像進行預處理,然後對圖片在水平和垂直方向上像素求和,區分字符與空白區域,完成了身份證圖像中字符定位與分割工作,有很好的切分效果;

3)在模型訓練中模型的選擇與設計是一個重要的環節,本文選擇Lenet模型,發現模型層次太淺,然後增加卷積層和池化層,設計出了改進的深層Lenet模型,然後采用Caffe深度學習工具對模型進行訓練,並在訓練好的模型上進行測試,實驗表明,模型的測試精度達到96.2%。

基於上述研究,本文設計並實現了身份證圖像自動識別系統,該系統先從移動端拍照獲取身份證圖片,然後在Flask輕量級web服務器上將身份證圖像輸入到模型中進行識別,並返回識別結果。設計的系統能準確識別出身份證上文字信息,具有較高的準確率,有一定的實用價值。

二、關鍵技術

2.1 深度學習介紹

深度學習技術被提出後,發展迅速,在人工智能領域取得了很好的成績,越來越多優秀的神經網絡也應運而生。深度學習通過建立多個隱層的深層次網絡結構,比如卷積神經網絡,可以用來研究並處理目前計算機視覺領域的一些熱門的問題,如圖像識別和圖像檢索。

深度學習建立從輸入數據層到高層輸出層語義的映射關系,免去了人工提取特征的步驟,建立了類似人腦神經網的分層模型結構。深度學習的示意圖如圖所示

技術分享

圖 有多個隱層的深度學習示意圖

深度學習的學習過程會分層進行,深度學習經過多層的學習,最終可以很好的表示數據特征。

2.2 卷積神經網絡(CNN)

卷積神經網絡有了很多年的發展,CNN在對圖像的識別上有很好的表現,利用權值共享的方式,降低了網絡的維度,也加快了訓練網絡的效率。

CNN具有多層的神經網絡,有卷積層、池化層和線性整流層等,具體CNN網絡結構示意圖如圖所示:

技術分享

圖 卷積神經網絡示意圖

如上圖所示,輸入數據圖像Input分別與三個濾波器做卷積運算。卷積完成後, 函數得到的特征映射圖,然後再將特征映射圖輸入到 層。S2層處理完後到達C3層,和 C1 的處理步驟一樣,以此類推,最終輸出結果。

卷積神經網絡的網絡結構如下:

1)卷積層(Convolutional layer)

卷積層由卷積單元組成,卷積單元進行著卷積運算,目的是提取出不同的形色各異的特征。

2)線性整流層(ReLU layer)

線性整流層使用線性整流(Rectified Linear Units, ReLU)作為這一層神經的激活函數(Activation function)。它可以增強判定函數和整個神經網絡的非線性特性,而本身並不會改變卷積層。ReLU可以將神經網絡的訓練速度提升數倍,而並不會對模型的泛化準確度造成產生顯著影響。

3)池化層(Pooling Layer)

池化(Pooling)是卷積神經網絡中另一個重要的概念。Pooling是一種向下采樣過程,它將劃定的區域輸出最大值。Pooling層的作用是通過減小數據參數數量有效的控制過擬合。在平時的使用中,Convolutional層之間都會按照一定的周期來插入Pooling層。

技術分享

圖 池化層下采樣過程圖

池化層通常會分別作用於每個輸入的特征並減小其大小。目前最常用形式的池化層是每隔2個元素從圖像劃分出2x2的區塊,然後對每個區塊中的4個數取最大值。這將會減少75%的數據量。過去,平均池化的使用曾經較為廣泛,但是最近由於最大池化在實踐中的表現更好,平均池化已經不太常用。

因為池化層太快地減小了數據量的大小,對與數據特征的學習不利,目前的趨勢是使用較小的池化濾鏡,甚至不再使用池化層。

4)損失函數層(loss layer)

損失函數層通常是網絡的最後一層,用於表示預測與測試結果之間的差別。在網絡中有不同類型的損失函數。

2.1.3 Lenet模型和Alexnet模型

1)Lenet模型

Lenet由Yan Le Cun等人於1998年提出,在手寫數字的識別上有極高的準確率。 輸入層大小為128×128,第一個卷積層窗口大小為 5×5,有 20 個過濾器共生成 20 張特征圖,第二個卷積層窗口大小為 5×5,50 個過濾器共生成 50 張特征圖,兩個池化層窗口大小均為 2,第一個全連接層共有 500 個神經元,如圖所示。

技術分享

圖 Lennet模型示意圖

2)Alexnet模型

Alexnet比Lenet的網絡結構更深,如圖 Alexnet示意圖所示。

技術分享

圖 Alexnet網絡模型示意圖

在Alexnet網絡模型中輸入的圖像規格是224*224三通道的RGB顏色圖像,特征的提取采用5*5的卷積核。在使用中Alexnet會輸入227*227三通道圖片,而不是它規格中的大小。在提取特征圖時采用公式,如下所示:

[img_size - filter_size]/stride +1 = new_feture_size                                  

其中[ ]表示向下取整,圖片並且是RGB通道的。

2.2 深度學習框架Caffe介紹

Caffe是目前深度學習領域主流的一個開源庫,采用C++和CUDA實現,支持MATLAB和Python接口,優點是速度快、開放性好、易於模塊化拓展。Caffe可以廣泛應用在多個領域,典型的如圖像分類、語音識別等。

Caffe框架可以從三方面來理解:1)數據存儲:Caffe通過四維數組(blobs)來保存模型計算上的數據,大型數據存儲在LevelDB數據庫中。2)層結構: Caffe支持完整的層類型,按照分層模型,可以在分層模型中保證了數據參數傳遞的準確性; 3)網絡和運行方式:一個深度神經網絡起始於數據層,數據層加載數據,中間每一層計算出梯度,最後一層完成分類重建,這樣一整套運行流程使得Caffe有很好的健壯性。

2.3 字符定位與切割

在對輸入的身份證圖片進行識別前,要進行必要的圖片預處理。在本文的系統中是基於單個字符的數據集訓練得到的模型,系統中輸入身份證圖片後要對文字區域進行定位與切割,以此來獲取身份證 圖片上的每一個字符。

本文的字符定位與切割采用水平和垂直投影分割法,具體流程如下:1)首先對圖像進行二值化處理,將圖像轉變成黑白的灰度圖;2)在字符區域圖像中呈現出白色,空隙區域為黑色,通過水平投影的方式將字符區域與空隙區域分割開,以此來確定字符所在行。3)使用垂直投影分割法,將字符從二值化圖像的每一行中分割出來,如此經過垂直與水平的切割,可以將每個字都分割出來。

三、模型訓練

3.1 獲取數據樣本庫

本系統中,要獲取的身份證上的字體為印刷體,目前有很多開源的手寫漢字數據庫,比如中科院的CASIA手寫中文字庫]中的 HWDB1.1 脫機字庫子集。如果利用手寫漢字庫進行本系統的模型訓練,會導致模型精度不高,而且適用情景也有偏差。所以在本系統中我將用Python生成印刷體漢字圖片字庫。

在日常使用中,共有3755個常用漢字,如何獲取這些數據訓練集成為一個要解決的問題。在Python圖像庫(PIL)是很好的工具,有很優秀的圖像處理功能,本文利用PIL對數據訓練集進行處理。PIL實現文字轉換成圖片的過程核心代碼如下所示:

 find_image_bbox = FindImageBBox()
 img = Image.new("RGB", (self.width, self.height), "black")
 draw = ImageDraw.Draw(img)

PIL將文字繪制到img上,之後把圖片保存成jpg格式。

從項目的font_path目錄下選擇字體格式文件,有ttf、ttc、otf等格式的文件,都通過ImageFont的truetype()方法設置選擇哪種字體文件

然後將文字繪制到圖片中,輸出文字圖片的列表:

draw.text((0, 0), char, (255, 255, 255),font=font)
data = list(img.getdata())

經過實驗,本文收集到一級常用和次常用的漢字共有6492個,將每個漢字寫入腳本的數據文件中,然後使用上述PIL方法依次讀取數據文件中的漢字並生成字體圖片輸出,並將字體圖片按順序分類存放,漢字的腳本見下面的鏈接:腳本鏈接GitHub

技術分享

字體文件下載地址:鏈接

這裏生成的字體圖片如圖所示,最後會組合類別:

技術分享

這裏附上執行獲取數據集的腳本代碼:

生成圖像數據庫GitHub

3.2 處理數據集圖片

本文采用Caffe框架在分割好的字符集上進行訓練。在訓練網絡前首先要準備好數據訓練集。在Caffe中直接使用的是lmdb或者leveldb文件,所以我們需要將圖片文件轉化成db文件,讓Caffe可識別。在Caffe的工程目錄下的tools裏很多工具類文件,其中的convert_imageset.cpp的作用就是將圖片數據訓練集轉換成db文件,讓Caffe能夠識別,在對Caffe編譯之後產生可執行文件,這裏convert_imageset的使用格式如下:

convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME

如上所示,convert_imageset方法需要帶四個參數,分別指定圖片參數、圖片存放的路徑、圖片清單和生成的db文件存放目錄。

之後創建一個sh腳本文件將各個函數與輸出進行整合,最終的目的是在相應目錄下生成train_lmdb和test_lmdb文件,如下為在腳本文件中生成train_lmdb的示例代碼:

GLOG_logtostderr=1 $TOOLS/convert_imageset 
                --resize_height=$RESIZE_HEIGHT 
                --resize_width=$RESIZE_WIDTH --shuffle  --gray 
                $TRAIN_DATA_ROOT  $DATA/train.txt $EXAMPLE/train_lmdb

最後在終端下運行這個腳本文件,生成了訓練用和測試用的lmdb文件,如圖所示:

技術分享技術分享

圖 lmdb文件目錄

同時會生成圖片清單文件列表,本文截取部分內容,如圖3.5所示:

技術分享

這裏附上數據集轉換的完整代碼:

#!/usr/bin/env sh
# Create the imagenet lmdb inputs
# N.B. set the path to the imagenet train + val data dirs


EXAMPLE=/workspace/caffe_dataset_lower_eng
mkdir -p $EXAMPLE
rm -rf $EXAMPLE/train_lmdb
rm -rf $EXAMPLE/val_lmdb


DATA=/workspace/caffe_dataset_lower_eng
TOOLS=$CAFFE_ROOT/build/tools

TRAIN_DATA_ROOT=${DATA}/
VAL_DATA_ROOT=${DATA}/

# Set RESIZE=true to resize the images to 256x256. Leave as false if images have
# already been resized using another tool.
RESIZE=true
if $RESIZE; then
  RESIZE_HEIGHT=28
  RESIZE_WIDTH=28
else
  RESIZE_HEIGHT=0
  RESIZE_WIDTH=0
fi

if [ ! -d "$TRAIN_DATA_ROOT" ]; then
  echo "Error: TRAIN_DATA_ROOT is not a path to a directory: $TRAIN_DATA_ROOT"
  echo "Set the TRAIN_DATA_ROOT variable in create_imagenet.sh to the path"        "where the ImageNet training data is stored."
  exit 1
fi


if [ ! -d "$VAL_DATA_ROOT" ]; then
  echo "Error: VAL_DATA_ROOT is not a path to a directory: $VAL_DATA_ROOT"
  echo "Set the VAL_DATA_ROOT variable in create_imagenet.sh to the path"        "where the ImageNet validation data is stored."
  exit 1
fi

echo "Creating train lmdb..."

GLOG_logtostderr=1 $TOOLS/convert_imageset     --resize_height=$RESIZE_HEIGHT     --resize_width=$RESIZE_WIDTH     --shuffle     --gray     $TRAIN_DATA_ROOT     $DATA/train.txt     $EXAMPLE/train_lmdb

GLOG_logtostderr=1 $TOOLS/convert_imageset     --resize_height=$RESIZE_HEIGHT     --resize_width=$RESIZE_WIDTH     --shuffle     --gray     $VAL_DATA_ROOT     $DATA/test.txt     $EXAMPLE/val_lmdb

echo "Done."

3.3 模型的選擇

在進行網絡訓練前另一項關鍵的任務是模型的選擇與配置,因為要保證模型的精度,要選一個適合本文身份證信息識別的網絡模型。

首先因為漢字識別相當於一個類別很多的圖片分類系統,所以先考慮深層的網絡模型,優先采用Alexnet網絡模型,對於漢字識別這種千分類的問題很合適,但是在具體實施時發現本文獲取到的數據訓練集每張圖片都是64*64大小的一通道的灰度圖,而Alexnet的輸入規格是224*224三通道的RGB圖像,在輸入上不匹配,並且Alexnet在處理像素較高的圖片時效果好,用在本文的訓練中顯然不合適。

其次是Lenet模型,沒有改進的Lenet是一個淺層網絡模型,如今利用這個模型對手寫數字識別精度達到99%以上,效果很好,在實驗時我利用在Caffe下的draw_net.py腳本並且用到pydot庫來繪制Lenet的網絡模型圖,實驗中繪制的原始Lenet網絡模型圖如圖所示,圖中有兩個卷積層和兩個池化層,網絡層次比較淺。

技術分享

圖 原始的Lenet網絡模型結構圖

前期實驗中將數據訓練集放入未改進的Lenet模型中進行訓練,訓練效果不理想,accurary值不是穩定上升,而是時常波動,並且出現了過擬合的現象,這裏截取了一張網絡叠代四千次的ROC曲線圖如圖所示:

技術分享

圖 叠代四千次的ROC曲線圖

圖中出現明顯的波動,並且出現accuracy=1的時候的過擬合現象,因為網絡層次比較淺,訓練效果不理想。這是因為在漢字識別系統上,漢字分類數量巨大,有幾千個分類,簡單的Lenet不足夠支持這麽多的分類。本文嘗試使用改進的Lenet網絡模型用在本文的訓練上,所以本文在訓練網絡前期實驗的基礎上,在Lenet-5的基礎上增加隱含層,包括卷積層和池化層,最後網絡結構達到了11層,修改完成後,為了查看網絡的結構組成,同樣使用draw_net.py腳本對改進的網絡模型進行繪制,具體結構如圖所示:

技術分享

圖 改進的Lenet網絡模型結構圖

在圖中可以明顯看出和原始的Lenet模型相比,改進後的模型層次明顯加深,多了一倍的卷積層和池化層。經過前期訓練實驗得出:深層網絡能更好的支持漢字的分類。

3.4 訓練模型

首先編輯solver.prototxt文件:

# The train/test net protocol buffer definition
net: "data/lenet_train_test.prototxt"
# test_iter specifies how many forward passes the test should carry out.
# In the case of MNIST, we have test batch size 100 and 100 test iterations,
# covering the full 10,000 testing images.
test_iter: 100
# Carry out testing every 500 training iterations.
test_interval: 500
# The base learning rate, momentum and the weight decay of the network.
base_lr: 0.01
momentum: 0.9
weight_decay: 0.0005
# The learning rate policy
lr_policy: "inv"
gamma: 0.0001
power: 0.75
# Display every 100 iterations
display: 100
# The maximum number of iterations
max_iter: 50000
# snapshot intermediate results
snapshot: 5000
snapshot_prefix: "data/lenet"
# solver mode: CPU or GPU
solver_mode: GPU

在Caffe的build文件夾下有tools文件夾,這個裏面有個可執行文件caffe,根據上文生成的Solver文件,可以在終端直接輸入命令開始訓練網絡:

build/tools/caffe train -solver data/deepocr/solver.prototxt --gpu=1

這裏在命令行的後面添加--gpu=1,表示選擇Tesla K40上GPU進行模型的訓練。因為數據量和叠代次數較大,盡管在GPU環境下訓練並利用cuDNN加速,但訓練模型還是花費了大約8個小時的時間。最後的訓練精度達到0.962,loss值較小,如圖所示,圖中達到預期的效果,訓練成功。

技術分享

圖 訓練並測試的結果圖

為了更加詳細的了解訓練的過程,本文使用Python的接口對訓練過程進行可視化,方便得出實驗的結論,所以本文使用jupyter notebook來繪制loss值和accuracy值變化的ROC曲線圖,在網頁的輸入框中配置相關信息即可繪制曲線圖,本文使用SGDSolver,即隨機梯度下降算法對曲線進行繪制:

solver = caffe.SGDSolver(data/deepocr/solver.prototxt)

然後設置叠代次數和手機數據的間隔等信息:

niter = 20000 display= 100

這裏叠代次數設置為20000次是因為訓練時發現叠代次數超過兩萬次後accuracy的值會趨於穩定,浮動很小,為了使用對訓練過程進行可視化,無法利用服務器GPU進行繪制,只能在本地CPU環境下繪制,所以耗時會非常長。

叠代並獲取loss和accuracy值後,初始化曲線圖,將值傳入,便可繪制出ROC曲線圖:

ax1.set_xlabel(iteration)
ax1.set_ylabel(train loss)
ax2.set_ylabel(test accuracy)

loss和accuracy曲線圖如圖所示:

技術分享

圖 loss和accuracy曲線圖

在圖中可以看出,在叠代兩萬次後,loss值逐漸減小並 趨於穩定,accuracy精度值逐漸穩定達到0.96左右,

最後訓練完模型後配置生成deploy.prototxt文件,這個文件會在模型的使用時用到。和訓練時的網絡模型文件不同,它沒有數據層,並且將最後的層替換成為概率層。deploy文件通過手動配置完成。

最後附上修改後的Lenet網絡:鏈接

其余代碼見GitHub:https://github.com/still-wait/deepLearning_OCR

由於篇幅原因,本文其余內容詳見 : 深度學習實踐系列之--身份證上漢字及數字識別系統的實現(下)

by still、

深度學習實踐系列之--身份證上漢字及數字識別系統的實現(上)