Optical Character Recognition(OCR) 光學識別,通俗的講就是識別圖片內的文字,好比身份證上的身份證號,姓名,地址等,銀行卡上的卡號 等等.python
Github repolinux
Evil
是一個iOS
和macOS
平臺下簡單的識別框架。支持經過 CocoaPods、Carthage 和 Swift Package Manager 安裝。底層的識別模型能夠很容易的遷移到其餘平臺。android
eg: 從一整張圖片中截取身份證所在的矩形區域
eg: 經過必定的算法截取到身份證號碼所在的區域
eg: 高斯模糊、膨脹等等
字
丟進神經網絡識別Evil
使用最新的Vision框架來實現。前4步 Apple 爲咱們提供了很好用系統方法來使用 例如: VNDetectTextRectanglesRequest
。因此這裏咱們不討論前4步的一些實現細節,若是你們想學習一下api的使用,能夠看這裏。git
我我的認爲對於少許的印刷體文字的識別能夠用圖片分類模型來處理,若是你們有更好的解決方法歡迎交流。假如你是一名CNN調參俠
那接下來的內容就很好理解了, 可是若是你沒有神經網絡
相關的基礎知識,接下來的內容可能有點晦澀,由於畢竟我也是隻懂個皮毛~。假如你以前沒有相關知識,能夠了解一下Apple爲咱們提供的Turi Create,能夠免去本身設計網絡。github
首先咱們須要設計一個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'))
複製代碼
咱們知道要對網絡進行訓練,必須須要大量的原始數據那咱們沒有怎麼辦呢?有些訓練資源在網絡上能夠找到,可是像咱們這裏要識別身份證號碼,怎麼辦呢?swift
固然是寫腳本生成啦,好比咱們先生成不少不少的身份證號碼區域
。進行一些隨機變化增長數據的多樣性。windows
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
。具體代碼在這裏,你們感興趣能夠去看看。api
有了數據,有了網絡模型 訓練網絡就很簡單了大概以下:xcode
model.fit_generator(generator=train_data_generator)
複製代碼
好了到這裏,你們觀察一下網絡的收斂狀況和識別精度,若是不是很糟糕的話,就能夠把這個model
保存起來了,爲之後的識別任務作準備啦。注意這一步生成的Keras model
是跨平臺,也就是說在windows,linux 甚至是android 上均可以用來識別哦。
前面幾步咱們生成的是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
文件備用。
咱們有了模型文件,怎麼導入Evil
框架呢?
這種方式有個顯著的缺點就是會增大app的體積,因此咱們不推薦使用這種方式。可是這回總給你方式在咱們調試期間是最簡單直接的。
這種方式對app的大小沒有任何影響,可是你們也注意到了須要在運行時下載模型文件,代碼複雜度就上來了,可是好消息是Evil
對此提供了很是友好的支持。你只須要將模型文件保存在本身的服務器或者CDN而後在info.plist
文件中配置下載路徑Evil
就能夠自動配置你的網絡模型。
萬事具有,怎麼使用呢? 只要按照咱們以前羅列出來的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
,歡迎你們貢獻本身訓練的模型。第一次在掘金寫文章謝謝你們的支持。