Optical Character Recognition(OCR) 光学识别,通俗的讲就是识别图片内的文字,比如身份证上的身份证号,姓名,地址等,银行卡上的卡号 等等.
Evil
Evil
是一个iOS
和macOS
平台下简单的识别框架。支持通过 CocoaPods、Carthage 和 Swift Package Manager 安装。底层的识别模型可以很容易的迁移到其他平台。
OCR识别的基本流程
- 从整张图片截取需要识别的区域
eg: 从一整张图片中截取身份证所在的矩形区域
- 截取文字区域
eg: 通过一定的算法截取到身份证号码所在的区域
- 对文字区域进行一系列的预处理方便下一步操作
eg: 高斯模糊、膨胀等等
- 分割文字,讲文字区域分割为单个的字
- 将单个
字
丢进神经网络识别
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
,欢迎大家贡献自己训练的模型。第一次在掘金写文章谢谢大家的支持。