基於神經網路的OCR識別

gix發表於2019-03-04

Optical Character Recognition(OCR) 光學識別,通俗的講就是識別圖片內的文字,比如身份證上的身份證號,姓名,地址等,銀行卡上的卡號 等等.

Evil

Github repo

Evil是一個iOSmacOS平臺下簡單的識別框架。支援通過 CocoaPodsCarthageSwift Package Manager 安裝。底層的識別模型可以很容易的遷移到其他平臺。

demo

OCR識別的基本流程

  1. 從整張圖片擷取需要識別的區域 eg: 從一整張圖片中擷取身份證所在的矩形區域
  2. 擷取文字區域 eg: 通過一定的演算法擷取到身份證號碼所在的區域
  3. 對文字區域進行一系列的預處理方便下一步操作 eg: 高斯模糊、膨脹等等
  4. 分割文字,講文字區域分割為單個的
  5. 將單個丟進神經網路識別

Evil使用最新的Vision框架來實現。前4步 Apple 為我們提供了很好用系統方法來使用 例如: VNDetectTextRectanglesRequest。所以這裡我們不討論前4步的一些實現細節,如果大家想學習一下api的使用,可以看這裡

如何使用神經網路識別單個字

我個人認為對於少量的印刷體文字的識別可以用圖片分類模型來處理,如果大家有更好的解決方法歡迎交流。假如你是一名CNN調參俠那接下來的內容就很好理解了, 但是如果你沒有神經網路相關的基礎知識,接下來的內容可能有點晦澀,因為畢竟我也是隻懂個皮毛~。假如你之前沒有相關知識,可以瞭解一下Apple為我們提供的Turi Create,可以免去自己設計網路。

0x00 設計網路

首先我們需要設計一個CNN網路來輸入我們的單個字圖片讓它識別,因為我們的識別任務很簡單,所以網路架構會很簡單。 以下是 Keras (2.0.6) 程式碼:

model = Sequential()

model.add(Conv2D(32, (5, 5), input_shape=(28, 28, 1), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.5))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Conv2D(128, (1, 1), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.2))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
複製程式碼

0x01 生成訓練資料

我們知道要對網路進行訓練,必須需要大量的原始資料那我們沒有怎麼辦呢?有些訓練資源在網路上可以找到,但是像我們這裡要識別身份證號碼,怎麼辦呢?

當然是寫指令碼生成啦,比如我們先生成很多很多的身份證號碼區域。進行一些隨機變化增加資料的多樣性。

o_image = Image.open(BACKGROUND)
    draw_brush = ImageDraw.Draw(o_image)

    font_size = random.randint(-5, 5) + 35
    draw_brush.text((10 + random.randint(-10, 10), 15 + random.randint(-2, 2)), LABELS,
                    fill='black',
                    font=ImageFont.truetype(FONT, font_size))

    o_image = ImageEnhance.Color(o_image).enhance(
        random.uniform(0.5, 1.5))  # 著色
    o_image = ImageEnhance.Brightness(o_image).enhance(
        random.uniform(0.5, 1.5))  # 亮度
    o_image = ImageEnhance.Contrast(o_image).enhance(
        random.uniform(0.5, 1.5))  # 對比度
    o_image = ImageEnhance.Sharpness(o_image).enhance(
        random.uniform(0.5, 1.5))  # 旋轉
    o_image = o_image.rotate(random.randint(-2, 2))

    o_image.save(output + '/%d.png' % idx)
複製程式碼

有了文字區域以後就需要對文字區域進行分割為單字然後訓練網路,因為接下來的任務具有通用性,所以我索性寫了一個小工具叫做PrepareBot。具體程式碼在這裡,大家感興趣可以去看看。

0x02 訓練網路

有了資料,有了網路模型 訓練網路就很簡單了大概如下:

model.fit_generator(generator=train_data_generator)
複製程式碼

好了到這裡,大家觀察一下網路的收斂情況和識別精度,如果不是很糟糕的話,就可以把這個model儲存起來了,為以後的識別任務做準備啦。注意這一步生成的Keras model 是跨平臺,也就是說在windows,linux 甚至是android 上都可以用來識別哦。

0x03 轉換網路

前面幾步我們生成的是Keras 的網路模型,如何在Evil中使用這些模型呢?首先我們需要使用蘋果提供的工具coremltools,將keras模型轉換為CoreModel具體使用大概如下

# Prepare model for inference
for k in model.layers:
    if type(k) is keras.layers.Dropout:
        model.layers.remove(k)
        model.save("./temp.model")

core_ml_model = coremltools.converters.keras.convert("./temp.model",
                                                     input_names='image',
                                                     image_input_names='image',
                                                     output_names='output',
                                                     class_labels=list(labels),
                                                     image_scale=1 / 255.)

core_ml_model.author = 'gix.evil'
core_ml_model.license = 'MIT license'
core_ml_model.short_description = 'model to classify chinese IDCard numbers'

core_ml_model.input_description['image'] = 'Grayscale image of card number'
core_ml_model.output_description['output'] = 'Predicted digit'

core_ml_model.save('demo.mlmodel')
複製程式碼

儲存demo.mlmodel檔案備用。

0x04 匯入網路

我們有了模型檔案,怎麼匯入Evil框架呢?

直接拖入xcode

這種方式有個顯著的缺點就是會增大app的體積,所以我們不推薦使用這種方式。但是這回總給你方式在我們除錯期間是最簡單直接的。

執行時下載

這種方式對app的大小沒有任何影響,但是大家也注意到了需要在執行時下載模型檔案,程式碼複雜度就上來了,但是好訊息是Evil對此提供了非常友好的支援。你只需要將模型檔案儲存在自己的伺服器或者CDN然後在info.plist檔案中配置下載路徑Evil就可以自動配置你的網路模型。

0x05 使用網路

萬事具備,怎麼使用呢? 只要按照我們之前羅列出來的1-5步,依次呼叫Evil提供的介面就可以了。例如

// 1. 使用Evil內建模型識別身份證號碼

lazy var evil = try? Evil(recognizer: .chineseIDCard)
let image: Recognizable = ....
let cardNumber = self.evil?.recognize(image)
print(cardNumber)
複製程式碼

// 2. 使用自定義模型

let url: URL = ...
let evil = try? Evil(contentsOf: url, name: "demo")
let ciimage = CIImage(cvPixelBuffer: pixelBuffer).oriented(orientation)
            if let numbers = ciimage.preprocessor
                // 透視矯正
                .perspectiveCorrection(boundingBox: observation.boundingBox,
                                       topLeft: observation.topLeft,
                                       topRight: observation.topRight,
                                       bottomLeft: observation.bottomLeft,
                                       bottomRight: observation.bottomRight)
                .mapValue({Value($0.image.oriented(orientation), $0.bounds)})
                // 保證身份證頭像朝上
                .correctionByFace()
                // 擷取號碼區域
                .cropChineseIDCardNumberArea()
                // 預處理 高斯模糊等
                .process()
                // 分割文字
                .divideText()
                // 簡單校驗
                .value?.map({ $0.image }), numbers.count == 18 {
                if let result = try? self.evil?.prediction(numbers) {
                    if let cardnumber = result?.flatMap({ $0 }).joined() {
                        DispatchQueue.main.async {
                            self.tipLabel.text = cardnumber
                        }
                    }
                }
            }
複製程式碼

總結

好的、好的廣告就打到這裡了,歡迎大家吐槽,歡迎star fork, 歡迎各種pr,歡迎大家貢獻自己訓練的模型。第一次在掘金寫文章謝謝大家的支援。

相關文章