欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Tesseract OCR iOS 教程

程序员文章站 2023-12-27 10:22:27
...

原文:Tesseract OCR Tutorial for iOS
作者:Lyndsey Scott
译者:kmyhy

更新说明:本教程由 Lyndsey Scott 更新为 Swift 4、iOS 11 和 Xcode 9。原文作者是 Lyndsey Scott。

你肯定知道 OCR……它通常用于处理扫描文档,手写文稿,以及在 Google 的 Translate app 所用的实景翻译技术。今天你将学习如何在你自己的 app 中利用 Tesseract 来实现它。听起来很不错,是吗?

但是……什么是 OCR?

光学文字识别(OCR)是一种从图片中抽取数字化字符的过程。当抽取完成后,用户就可以将这些文字用于编辑文档、文字搜索、压缩等等。

在本教程中,你将用 OCR 去追求你的真爱。你将使用一个由 Google 维护的开源 OCR 引擎 Tesseract 创建一个名为 Love In A Snap 的 app。这个 app 允许你用一首爱情诗的图片作为素材,将原作者的女神/男神替换为你想追求的对象。好棒!准备让人们大吃一惊吧。

开始

这里下载开始项目,并解压缩。

这里面有几个文件夹:

  • LoveInASnap: 开始项目。
  • Images:爱情诗图片。
  • tessdata: Tesseract 的语言包。

打开 LoveInASnap\LoveinASnap.xcodeproj,build & run,随意点点,感受一下 UI。目前的 app 很简单,但你会在选中或反选文本框时看到会上移下移。这是为了防止键盘遮住文字框和按钮。

Tesseract OCR iOS 教程

开始编写代码

打开 ViewController.swift 看一下代码。你会看到几个 @IBOutlet 属性和 @IBAction 方法已经连接到了 Main.storyboard。在这些 @IBAction 中,view.endEditing(true) 用于释放键盘。在 sharePoen(_:) 方法中这样做是因为当键盘弹出时,分享按钮会被遮挡住。

在这些 @IBAction 之后,你会看到一个 performImageRecognition(_:)。这是 Tesseract 进行图片识别的地方。

下面两个函数用于将视图上移、下移:

func moveViewUp() {
  if topMarginConstraint.constant != originalTopMargin {
    return
  }  
  topMarginConstraint.constant -= 135
  UIView.animate(withDuration: 0.3) {
    self.view.layoutIfNeeded()
  }
}

func moveViewDown() {
  if topMarginConstraint.constant == originalTopMargin {
    return
  }
  topMarginConstraint.constant = originalTopMargin
  UIView.animate(withDuration: 0.3) {
    self.view.layoutIfNeeded()
  }
}

当键盘弹出时,moveViewUp 将 View controller 的 view 的 top 约束向上移。当键盘收起,moveViewDown 将控制器视图的 top 约束设置回原来的值。

在故事板中,UITextField 的委托设置为 ViewController。在 UITextFieldDelegate 扩展中有这几个方法:

// MARK: - UITextFieldDelegate
extension ViewController: UITextFieldDelegate {
  func textFieldDidBeginEditing(_ textField: UITextField) {
    moveViewUp()
  }

  func textFieldDidEndEditing(_ textField: UITextField) {
    moveViewDown()
  }
}

当用户开始编辑 text field 时,调用 moveViewUp。当用户结束编辑 text field时,调用 moveViewDown。

尽管上述函数对于 app UX 来说必不可少,但跟本文毫不相关。因为它们是已经写好的,我们可以直接从真正感兴趣的代码入手。

Tesseract 的限制

Tesseract OCR 非常强大,但也有一些限制:

  • 和别的 OCR 引擎不同(比如美国邮政服务用于整理邮件的 OCR),Tesseract 无法识别手写体。实际上,它总共只支持 64 中字体。
  • 可以通过对图像进行预处理来提升 Tesseract 的性能。你必须通过对图片进行缩放、增加颜色对比度、对文本水平对齐来优化处理结果。
  • 最后,Tesseract OCR 只支持 Linux、Windows、和 Mac OS X。

呃?有 Linux、Windows 和 Mac OS X,没有 iOS?幸运的是,gali8 对 Tesseract OCR 进行了一个 O-C 的封装,你可以在 Swift 和 iOS 中使用。

嘁!:]

安装 Tesseract

根据 Joshua Greene 写的一篇教程如何在 Swift 中使用 CocoaPods 所描述的,你可以用以下步骤安装 CocoaPods 和 Tesseract 框架。

要安装 CocoaPods,可以在终端中使用命令:

sudo gem install cocoapods

当问到计算机密码时,请输入正确的密码。

要在项目中安装 Tesseract,用 cd 命令转到 LoveInASnap 项目所在的目录。例如,如果你的开始项目位于桌面,请使用:

cd ~/Desktop/OCR_Tutorial_Resources/LoveInASnap

然后,用下列命令在这个文件夹下生成一个 Podfile 文件:

pod init

用文本编辑器打开 Podfile 文件,编辑内容为:

use_frameworks!
platform :ios, '11.0'

target 'LoveInASnap' do
  use_frameworks!
  pod 'TesseractOCRiOS'
end

这会告诉 CocoaPods 你想在项目中使用 TesseractOCRiOS 框架。最后,保存、关闭 Podfiel,进入终端,保持之前的工作目录不变,输入命令:

pod install

就是这样!当一段长长的输出之后,然后你会看到 “Please close any current Xcode sessions and use ‘LoveInASnap.xcworkspace’ for this project from now on.” 。关闭 LoveinASnap.xcodeproj,在 Xcode 中打开OCR_Tutorial_Resources\LoveInASnap\LoveinASnap.xcworkspace 。

在 Xcode 中设置 Tesseract

将 tessdata 文件夹,也就是 Tesseract 的语言包,从 Finder 中拖进 Xcode 项目的 Supporting Files 文件夹下。确认勾选 Copy items 选项,和 Create folder 选项,然后勾上 LoveInASnap,点击 Finish。

Tesseract OCR iOS 教程

注意:确认在 Build Phases 的 Copy Bundlle Resources 下面有 tessdata 一项,否者运行时会报错,说在 tessdata 的父目录中未设置 TESSDATA_PREFIX 环境变量。

返回项目导航器,点击 LoveInASnap 项目文件,在 Targets 下面,选择 LoveInASnap,打开 General 标签页,找到 Linked Frameworkds and Libraries 选项。

这里只应该有一个文件存在:Pods_LoveInASnap.framework,也就是你刚刚添加的那个 pod。点击 + 按钮,添加 libstadc++.dylib、CoreImage.framework 和 TesseractOCR.framework。

Tesseract OCR iOS 教程

之后,你的 Linked Frameworks and Libraries 应该变成:

Tesseract OCR iOS 教程

差不多了!还剩一个步骤,我们就可以开始编写代码了……

在 LoveInASnap target 的 Build Settings 中,找到 C++ Standard Library,将它设置为 Compiler Default。然后找到 Enable Bitcode,将它设置为 NO。

类似地,回到左边的项目导航器中,选择 Pods 项目,找到 TesseractOCRiOS target 的 Build Settings,找到 C++ Standard Library 将它设置为 Compiler Default。然后找到 Enable Bitcoe 将它设置为 NO。

就是这样了!Build & run,确保能够编译。你会在左边的 issue 导航器中看到一些警告,但不要理它们。

好了没有?现在你终于可以开始有意思的部分了!

创建 Image Picker

打开 ViewController.swift 在类定义之后添加扩展:

// 1
// MARK: - UINavigationControllerDelegate
extension ViewController: UINavigationControllerDelegate {
}

// MARK: - UIImagePickerControllerDelegate
extension ViewController: UIImagePickerControllerDelegate {
  func presentImagePicker() {
    // 2
    let imagePickerActionSheet = UIAlertController(title: "Snap/Upload Image",
                                                   message: nil, preferredStyle: .actionSheet)
    // 3
    if UIImagePickerController.isSourceTypeAvailable(.camera) {
      let cameraButton = UIAlertAction(title: "Take Photo",
                                       style: .default) { (alert) -> Void in
                                        let imagePicker = UIImagePickerController()
                                        imagePicker.delegate = self
                                        imagePicker.sourceType = .camera
                                        self.present(imagePicker, animated: true)
      }
      imagePickerActionSheet.addAction(cameraButton)
    }
    // Insert here
  }
}

代码解释如下:

  1. 将 ViewController 声明为实现 UINavigationControllerDelegate 和 UIImagePickerController 协议,这是使用 UIImagePickerController 时必须实现的两个协议。
  2. 在 presentImagePicker() 方法中,创建一个 UIAlertController 用于向用户显示一个 action sheet 以便获取用户的选择。
  3. 如果设备拥有摄像头,在 imagePickerActionSheet 中添加一个 Take Photo 按钮。这个按钮会用 .camera 作为 sourceType 来创建和呈现 UIImagePickerController。

为了完成这个函数,请将 // Insert here 替换为:

// 1
let libraryButton = UIAlertAction(title: "Choose Existing",
  style: .default) { (alert) -> Void in
    let imagePicker = UIImagePickerController()
    imagePicker.delegate = self
    imagePicker.sourceType = .photoLibrary
    self.present(imagePicker, animated: true)
}
imagePickerActionSheet.addAction(libraryButton)
// 2
let cancelButton = UIAlertAction(title: "Cancel", style: .cancel)
imagePickerActionSheet.addAction(cancelButton)
// 3
present(imagePickerActionSheet, animated: true)

代码解释:

  1. 在 imagePickerActionSheet 中添加 Choose Existing 按钮。这个按钮用 .photoLibrary 作为 sourceType 来创建和呈现 UIImagePickerController。
  2. 添加一个 Cancel 按钮。
  3. 呈现 UIAlertController。

然后,在 takePhoto(_:) 方法中添加:

presentImagePicker()

当你点击 Snap/Upload Image 时,这会显示一个 Image picker。

如果你用真机编译,并企图拍照,app 会崩溃。因为 app 没有向用户获取到相机访问权限,因此你还需要添加必要的权限声明字段。

声明访问相册权限

在项目导航器中,找到 LoveInASnap 的 Info.plist 文件。在 Information Property List 上面点击 + 按钮,添加 Privacy – Photo Library Usage Description 和 Privacy – Camera Usage Description 两个 key。将它们的值填写为要显示给用户的内容。

Tesseract OCR iOS 教程

Build & run。点击 Snap/Upload Image,你将看到 UIAlertController 显示出来:

Tesseract OCR iOS 教程

注意:如果你使用模拟器,因为没有真实摄像头,你将无法看到 Take Photo 这个选项。

如果你点击 Take Photo,然后授权 app 访问相机,你就可以进行拍照。如果你选择 Choose Existing 然后授权 app 访问相册,你就可以从中选择一张图片。

选择图片之后,app 目前是不会做任何动作的。你还需要在 Tesseract 处理图片之前做一些准备工作。

在 Tesseract 的限制中提到,为了优化 OCR 的结果,你必须将图片尺寸限制在一定大小。如果图片太大或者太小,Tesseract 可能返回错误结果或者出现 EXC_BAD_ACCESS 而崩溃。

因此你必须写一个修改图片大小但宽高比保持不变的方法。

维持纵横比缩放图片

图片的纵横比是指它的宽度和高度的比例。因此,在减少图片的尺寸同时不改变纵横比,你必须将这个宽高比作为一个常量。

如果你知道原始图片的宽和高,那么只要你知道最终图片的宽或高中的任意一个,就能够应用下面的纵横比公式:

Tesseract OCR iOS 教程

因此,height2 = height1/width1 * width2,width2 = width1/height1 * height2。你可以在缩放方法中用这两个公式来保持图片的纵横比不变。

打开 ViewController.swift 在 UIImage 扩展中添加方法:

// MARK: - UIImage extension
extension UIImage {
  func scaleImage(_ maxDimension: CGFloat) -> UIImage? {

    var scaledSize = CGSize(width: maxDimension, height: maxDimension)

    if size.width > size.height {
      let scaleFactor = size.height / size.width
      scaledSize.height = scaledSize.width * scaleFactor
    } else {
      let scaleFactor = size.width / size.height
      scaledSize.width = scaledSize.height * scaleFactor
    }

    UIGraphicsBeginImageContext(scaledSize)
    draw(in: CGRect(origin: .zero, size: scaledSize))
    let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return scaledImage
  }
}

scaleImage(_:) 方法会获取图片的高或者宽——比较两者的较大者为准——然后将它的大小设置为 maxDimension 参数。然后,为了维持图片的纵横比,根据需要缩放另一边即可。然后将原图重新在新 frame 中重绘。最后,返回缩放后的图片。

现在,你必须写一个方法获取用户选择的图片。

获取图片

在 UIImagePickerControllerDelegate 扩展中在 presentImagePicker() 下面添加方法:

// 1
func imagePickerController(_ picker: UIImagePickerController,
  didFinishPickingMediaWithInfo info: [String : Any]) {
  // 2
  if let selectedPhoto = info[UIImagePickerControllerOriginalImage] as? UIImage, 
    let scaledImage = selectedPhoto.scaleImage(640) {
    // 3
    activityIndicator.startAnimating()
    // 4
    dismiss(animated: true, completion: {
      self.performImageRecognition(scaledImage)
    })
  }
}

这个方法解释如下:

  1. imagePickerController(_:didFinishPickingMediaWithInfo:) 是 UIImagePickerControllerDelegate 协议中的方法。当用户选择好图片,这个方法会在一个 info 字典中返回这张图片的信息。
  2. 将图片通过 UIImagePickerControllerOriginalImage 键从 info 字典中取出。将图片缩放至宽高小于 640。(根据经验,640 的识别结果最佳)同时对缩放后的图片进行解包操作。
  3. 让 activity indicator 开始显示,表示 Tesseract 正在工作。
  4. 解散 UIImagePicker,将图片传递给 performImageRecognition 方法处理。

Build & run,点击 Snap/upload Image,选择一张图片。activity indicator 将开始旋转。

别被它迷花了眼!还有更多代码要写。

我们显示了 activity indicator,但它到底代表什么意思?闲话少说(请来点掌声),你终于可以开始使用 Tesseract OCR 了!

使用 Tesseracdt OCR

打开 ViewController.swift 在 import UIKit 下添加:

import TesseractOCR

这将导入 Tesseract 框架,并允许你在这个文件中使用它。

然后,在 performImageRecognition(_:) 方法一开始添加:

// 1
if let tesseract = G8Tesseract(language: "eng+fra") {
  // 2
  tesseract.engineMode = .tesseractCubeCombined
  // 3
  tesseract.pageSegmentationMode = .auto
  // 4
  tesseract.image = image.g8_blackAndWhite()
  // 5
  tesseract.recognize()
  // 6
  textView.text = tesseract.recognizedText
}
// 7
activityIndicator.stopAnimating()

OCR 开始发挥作用了!整个方法分为以下几个部分:

  1. 创建一个 G8Tesseract 对象,传入 eng+fra 参数,即英语和法语语言包。本教程中所用的诗中用到了一些法语(罗曼蒂克),因此添加法语能让 Tesseract 认识其中的法语单词,并形成合体的字符。
  2. 有 3 个 OCR 模式:.tesseractOnly 最快,但准确率是最差的。.cubeOnly,稍慢但准确率更高,因为它使用了更多的人工智能。.tesseractdCubeCombined 集合了 .tesseractOnly 和 .cubeOnly,这也是其中最慢的一种模式。在本教程中,使用了.tesseractCubeCombined,因为它的准确率最高。
  3. Tesseract 默认要处理的文本处于同一文本块中。因为例子中使用的诗包含了段落换行符,它不是同一文本块。将 pageSegmentationMode 设置为 .auto 允许 Tesseract 自动识别出段落之间的分隔。
  4. 当文本和背景之间的对比度越高,识别的结果越好。用 Tesseract 内置的 g8_blackAndWhite 滤镜降低颜色饱和度,增加对比度,降低曝光度。
  5. 进行光学文字识别。
  6. 将识别出的文字放到 textview 里。
  7. 移除 activity indicator,表示 OCR 过程结束。

是时候测试一下代码,看看什么结果了!

处理第一张图片

在示例图片中有这样一张图片 OCR_Tutorial_Resources\Images\Lenore.png:

Tesseract OCR iOS 教程

Lenore.png 包含了一首爱情诗,是寄给 “Lenore” 的,但只需要稍微编辑下,就能用于送给你的女神/男神!:]

如果在有相机的设备上运行 app,你可以拍下这首诗,然后进行 OCR。但出于本文演示目的,将图片添加到设备的相机胶卷中,你就能够上传它了。这样,你可以避免光源不均匀、文字倾斜、打印不清晰等问题。

如果你使用模拟器,将图片文件拖进模拟器,即可将它添加到你的相机胶卷。

Build & run,选择 Snap/Upload Image,然后选择 Choose Existing。同意 app 访问你的相册,然后选择这张图片。

然后……看到了吗!几秒钟之后,文字就识别出来并显示到了 text view 中。

只不过,如果你的女神/男神名字并不叫做 Lenore,他或者她并不会买账。因为在诗中,Lenore 的使用十分频繁,要将它替换成你的心上人是一个不小的工作。

你说什么?是的,你可以写一个函数,查找并替换这个词。这想法太妙了!下一节将告诉你怎么做。

查找替换文本

现在 OCR 引擎已经把图片转换成文字,你可以把它看成普通的字符串对待。

还记得吗?ViewController.swift 中有一个 swapText 函数,当 swap 按钮被点时会触发这个函数。这就简单了,是不?

找到 swapText(_:),在 view.endEditing(true) 一句下面添加:

// 1
guard let text = textView.text,
  let findText = findTextField.text,
  let replaceText = replaceTextField.text else {
    return
}

// 2
textView.text =
  text.replacingOccurrences(of: findText, with: replaceText)
// 3
findTextField.text = nil
replaceTextField.text = nil

这段代码很简单,让我们来简单过一下:

  1. 判断 textView、findTextField 和 replaceTextField 中内容不为空时,才调用交换方法。
  2. 在 text view 中,将 findTextField 中指定的文本替换为 replaceTextField 中的内容。
  3. 替换完成,清除 findTextField 和 replaceTextField 中的内容。

Build & run,再次上传示例图片,让 Tesseract 开始工作。当文字显示出来后,在 Find this … 中输入 Lenore ,在 Repace with … 中输入你的男神/女神的名字(注意,查找替换是大小写敏感的)。点击 swap 按钮,完成替换。

Tesseract OCR iOS 教程

变,变,变-你创作了一首为情人量身定制的爱情诗!

你还可以替换其它单词,以迸发出你自己的艺术火花!

太好了!这么有诗意和勇气的作品不应该只呆在你的手机里。你还需要一个方法将你的大作分享给全世界。

分享成果

要分享你的诗,请在 sharePeom() 中编写代码:

// 1
if textView.text.isEmpty {
  return
}
// 2
let activityViewController = UIActivityViewController(activityItems:
  [textView.text], applicationActivities: nil)
// 3
let excludeActivities:[UIActivityType] = [
  .assignToContact,
  .saveToCameraRoll,
  .addToReadingList,
  .postToFlickr,
  .postToVimeo]
activityViewController.excludedActivityTypes = excludeActivities
// 4
present(activityViewController, animated: true)

分别解释如下:

  1. 如果 text view 是空的,返回。
  2. 否则,用 text view 中的文本初始化一个 UIActivityViewController。
  3. UIActivityViewController 的 activity 类型默认是一个很长的数组。这里我们将不相关的类型全部排除。
  4. 呈现 UIActivityViewController 允许用户将他们的创作按照希望的方式进行分析。

再次 build & run。上传示例图片,查找替换文字。然后欣赏完你的诗作之后,点击信封,会显示分享选项,然后将你的诗歌按照你想要的方式分享出去。

这样你的 Love In A Snap 就完成了——你肯定能够获得对方的青睐。

你可以像我一样,将 Lenore 换掉,将诗歌发送到你的收件箱,然后独自饮下一杯葡萄酒,带着惺忪的眼神,假装这封 email 是来自于女王陛下,为了一次特别大气的、充满浪漫、舒适、美妙和神秘的夜晚……但还是只有我一个……

接下来做什么

这里下载完成后的开始项目。

你可以看看 GitHub 上的 Tesseract 的 iOS 封装:https://github.com/gali8/Tesseract-OCR-iOS。在 Google 的 Tesseract OCR 网站可以下载其他语言包(请使用 3.03 版本以上的语言包,以便和当前框架兼容)。

Tesseract OCR iOS 教程

在后续研究 OCR 的时候,记住这点:“输入的质量越差,输出的结果就越差。”最简单的提升输出质量的方法是改善输入的质量,比如:

  • 对图片进行预处理。
  • 对图片反复进行滤镜处理,比较结果上的差异,然后得到最准确的输出。
  • 创建自己的 AI 逻辑,比如神经网络。
  • 用 Tesseract 自己的训练工具帮助你的程序从错误中的学习,并即时改进成功率。

通过联合多种策略,你会得到最好的结果,因此请尝试各种手段并找出最佳工作方式。

最后,如果你对本文、Tesseract 或者 OCR 有任何问题或建议,请在下面留言。

相关标签: OCR Tesseract

上一篇:

下一篇: