iOS机器学习:识别图像中的文本

372 阅读14分钟

机器学习和工具

就像 iOS 一样,ML 是关于的。你不会开发自己的 UITableView 工具,或者至少不应该;你会使用一个框架,比如 UIKit。

ML 也是如此。ML也是如此 TensorFlow Lite为 iOS 和 Android 设备带来了模型支持。

这些工具中的每一个都需要一些 ML 经验。如果您不是 ML 专家,但想解决特定问题怎么办?对于这种情况,有ML Kit

机器学习套件

ML Kit 是移动的一种 SDK,可将 Google 的 ML 专业知识带入您的应用程序。ML Kit 的 API 有两个部分,常用示例和自定义模型,无论经验如何都可以轻松使用。

机器学习套件

现有API当前支持:

示例中的每一个预训练好的模型,都在使用一些 API 的时候开始制造!

入门

在本教程中,您将制作下一个标题的应用程序的应用程序,以便写下一个内容和拍摄标志或照片? ,比如你的赞许了!

这个项目使用 CocoaPods 来管理依赖。

设置机器学习套件

ML Kit API 有多种不同的 CocoaPods 依赖项。这很有用,因为您只需要捆绑应用所需的依赖项。例如,您如果不识别地标,则应用中不需要该模型。在 Extractor中,您将使用文本识别 API

如果您将文本识别 API 添加到应用程序中,您需要将项目添加到的 Pod 文件中,但此时您必须为启动执行此操作,因为这些行在 Pod 文件中 - 您可以检查。

吊舱'Firebase/Core' => '5.5.0'
吊舱'Firebase/MLVision' => '5.5.0'
吊舱'Firebase/MLVisionTextModel' => '5.5.0'

您必须打开终端终端程序,切换到项目文件夹并运行以下命令来安装项目中使用的 CocoaPods:

卸载重装

安装 CocoaPods 后,在 Xcode 中打开Extractor.xcworkspace 。

注意:您可能会注意到项目文件夹包含一个名为 Extractor.xcodeproj 的项目文件和一个名为Extractor.xcworkspace 工作区,这是您在 Xcode 中打开的文件。不要打开项目文件,因为它不是包含编译应用程序需要的额外 CocoaPods 项目。**

如果您不熟悉 CocoaPods,我们的CocoaPods 教程将帮助您入门。

该项目包含以下重要文件:

  1. ViewController.swift:这个项目中唯一的控制器。
  2. +UIImage.swiftUIImage修复图像方向的扩展。

设置 Firebase 帐户

要设置 Firebase 帐户,请按照此Firebase 入门教程中的帐户设置部分进行操作。

一般的联想是你:

  1. 创建一个帐户。
  2. 创建一个项目。
  3. 将iOS应用程序添加到项目中。
  4. GoogleService-Info.plist 拖到您的项目中。
  5. 在 AppDelegate 中初始化 Firebase。

这是一个简单的过程,如果您遇到任何障碍,可以提供帮助。

注意:您需要设置并为最终项目和最终项目创建自己的GoogleService-Info.plist

构建并运行应用程序,你会看到它看起来像这样:

入门应用

除了您还允许通过应用程序的外部操作的共享硬件编码,它没有任何其他的硬件编码。使您将使用 ML Kit 工具箱,例如图标生。

检测基本文本

为您的第一次文本检测做好准备!您可以先向用户演示如何使用该应用程序。

一个很好的图像是在应用程序第一次启动时扫描示例默认在资产使用文件夹中的捆绑图像。一个名为示例映射文本的图像,这是当前UIImageView视图中显示的作为。您将它图像。

但是,您需要一个文本检测器来检测图像中的第一个文本。

创建文本检测器

创建一个ScaledElementProcessor.swift文件并添加以下代码:

导入Firebase

类ScaledElementProcessor {

}

伟大的!你们都完成了!只是一个文本。在类中创建一个检测器属性:

Vision =  Vision .vision()
 var textRecognizer: VisionTextRecognizer!
  
在里面(){
  textRecognizer = vision.onDeviceTextRecognizer()
}

textRecognizer是您可以检测使用图像中文本的主要对象。您将使用它来识别当前的图像中包含的文本UIImageView。在类中添加以下检测方法:

func进程(在中:UIImageView,
  指向image:@escaping ( _text : String ) -> Void ) {
   // 1守卫让image = 
  imageView.image else { return }
   // 2
  让 visionImage =  VisionImage (image: image)
   // 3
  textRecognizer.process(visionImage) { 结果,错误
    // 4
    警卫
      错误==无,
      让结果=结果,
       !结果.text.isEmpty
      别说{
        打回来(””)
        返回
    }
    // 5 
    (结果。文本)
  }
}

花点时间识别码:

  1. 在这里,您检查是否imageView实际包含图像。没有,只是返回。但是,如果理想的情况下,您将抛出一个优雅的失败。
  2. ML Kit 使用一种特殊的VisionImage类型。它很有用,因为它可以包含 ML Kit 处理图像的特定元数据,例如的方向。
  3. textRecognizer有一个接受 的process方法,VisionImage它以传递给闭包参数的形式返回一个文本结果目录。
  4. 结果可能是nil,这种情况下,需要您为返回一个空字符串。
  5. 最后引发以识别的文本。

使用文本检测器

打开ViewController.swift,在类主体顶部的 outlets 之后,添加一个实例ScaledElementProcessor属性:

让处理器=  ScaledElementProcessor ( )

然后,在底部添加以下代码以在UITextViewviewDidLoad()中显示检测到的:**

processor.process(in: imageView) { 文本输入
  self .scannedText =文本
}

这个小块调用process(in:),主要imageView用来识别文本分配给scannedText中的属性。

运行应用程序,您应该会在图像部分查看以下文本:

您的
扫描
文本
将要
出现
这里

您可能需要滚动文本视图以显示最后几行。

请注意已扫描的“S”和“C”是如何大字体的。有时,对于特定的,可能会出现错误的大小。这就是文本显示中的原因UITextView,因此用户可以手动编辑以修复检测错误。

从图像中检测到文本

了解类

注意:您需要将代码复制到本节中的部分中,将其添加到应用程序的 解释。

视觉文本

你是否注意到但在许多参数textRecognizer.process(in:)中发现ScaledElementProcessor了一个对象result而不是普通的旧旧信息?这是一个实例,一个包含有用信息的类,例如已经返回的文本。文本。每个人都已经识别出文本元素的每一帧不是很酷吗? VisionText**

ML树的提供的部分类似于树的结构。您需要到工具包中找到所需的结构和结果。您的框架对您的对树没有以下多大意义,不要太担心。正在发生的事情。

但是,如果您有兴趣了解有关树数据结构的更多信息,您可以随时查看Swift 树数据结构的本教程。

视觉文本块

在处理已识别的文本树时VisionText——这是一个可以从一个多个文本树块(如中的分支)的对象(称为树**)。VisionTextBlock一个对象,如下所示:**

结果块中的块。块{

}

视觉文本元素

一个VisionTextBlock只是文本包含的对象(如文本行上的叶子),每一个都由一个实例行VisionTextElement表示。这个单个的对象玩弄您可以查看已识别的层次结构。

循环遍历每个对象如下所示:

结果块中的块。块{
  对于 block.lines 中的行 {
    对于 line.elements 中的元素 {

    }
  }
}

但是,每个对象都包含不同等级的框架。

您将把元素作为一个字词。使用此图片时,您将在每个屏幕上显示您的图片。

frame使用这个框架,可以在上面的文字周围画一个文字CGRect

显示文本框

检测帧

要在下面的图片内容中创建一个元素图。您需要CAShapeLayer使用frame文本图添加到顶部:打开 Swift并添加到顶部:**struct

结构元素{
  让框架:CGRect
  让 shapeLayer: CALayer
}

struct是一种方便。它使您CAShapeLayer可以轻松地从框架和现在来进行自己的操作,需要一个辅助方法来CAShapeLayer创建一个框架中的元素。

将以下代码添加到收货ScaledElementProcessor

func  createShapeLayer ( frame : CGRect ) -> CAShapeLayer {
   // 1
  让 bpath = UIBezierPath ( rect: frame) 
  让 shapeLayer =  CAShapeLayer ()
  shapeLayer.path = bpath.cgPath
   // 2 
  shapeLayer.strokeColor =特征.lineColor
  shapeLayer.fillColor =特性.fillColor
  shapeLayer.lineWidth =特征.lineWidth
  返回形状层
}

// MARK: - 私有的
  
// 3
私有枚举特征
  静态让 lineWidth: CGFloat  =  3.0
  静态让 lineColor =  UIColor .yellow.cgColor
  静态让 fillColor =  UIColor .clear.cgColor
}

以下是代码的作用:

  1. 一个构造CAShapeLayer没有接受一个的初始化程序CGRect。因此,您使用一个UIBezierPath图层CGRect设置pathUIBezierPath
  2. 颜色和颜色的视觉属性是通过Constants枚举设置的。
  3. 枚举一致的颜色和范围。

现在,替换process(in:callback:)为以下内容:

// 1
函数进程(
  在 imageView: UIImageView ,
  恶:@escaping ( _text : String , _scaledElements : [ ScaledElement ]) -> Void
  ) {守卫让image = 
  imageView.image else { return }
  让 visionImage =  VisionImage (image: image)
    
  textRecognizer.process(visionImage) { 结果,错误
    警卫
      错误==无,
      让结果=结果,
       !结果.text.isEmpty
      别说{
        打回来(””,[])
        返回
    }
  
    // 2 
    var scaledElements:[ ScaledElement ] = []
     // 3
    对于结果块中的块。块{
      对于 block.lines 中的行 {
        对于 line.elements 中的元素 {
          // 4
          让 shapeLayer =  self .createShapeLayer(frame: element.frame)
          让 scaledElement = 
            ScaledElement (frame: element.frame, shapeLayer: shapeLayer)

          // 5
          scaledElements.append(scaledElement)
        }
      }
    }
      
    恶(result.text,scaledElements)
  }
}

下面是变化:

  1. ScaledElement除了已经识别出的文本之外, 现在还表现出不同的 实例。
  2. scaledElements制作框架和图层的集合。
  3. 正如上面所概述的,代码使用for循环来获取每个元素的框架。
  4. 最里面的for循环从元素的框架创建形状层,然后用于构造一个新ScaledElement实例。
  5. 将新创建的实例添加到scaledElements

绘画

上面的代码是把你的铅笔放在一起了。现在,是画图的时候了!打开ViewController 。并在中viewDidLoad(),将调用替换为下面的process(in:)内容:

processor.process(in: imageView) { 文本,元素
  self .scannedText =文本
  elements.forEach() { 特征在
    自己.frameSublayer.addSublayer(feature.shapeLayer)
  }
}

ViewController有一个frameSublayer附加的属性imageView。在这里,您可以将每个元素的形状图层会添加到子,iOS 自动在图层上添加图形。

制作并运行。看看你的艺术作品!

未缩放到图像的帧

哦。那是什么?看起来你是毕加索而不是莫莫。这里可能发生了?好吧,现在是在谈论话题了。

理解图像缩放

scanned-text.png(宽x高);默认为654×999(宽x高);但它UIImageView具有“Aspect Fit”的“模式”,可将视图中的图像缩放为375×369。ML Kit接收内容的实际大小并根据返回元素帧。然后在缩放后的尺寸上绘制实际尺寸的帧,这会产生困惑的结果。

比较实际尺寸与尺寸

在上图中,请您注意缩放和实际尺寸之间的差异。可以将框架与实际尺寸相匹配。为了看到正确的尺寸,需要计算图像与位置的尺寸。

相当简单(👀......相当):

  1. 计算视图和图像的参考。
  2. 通过确定比较比例。
  3. 通过将它们乘以比例来计算高度、宽度和原点 x 和 y。
  4. 使用这些数据点创建一个新的 CGRect。

如果这令人困惑,那没有关系!看到你就明白了。

计算比例

打开ScaledElementProcessor.swift并添加以下方法:

// 1
函数 createScaledFrame(
  特征框架:CGRect, 
  imageSize:CGSize,viewFrame:CGRect)
  -> CGRect {
  让 viewSize = viewFrame.size
    
  // 2
  让参考视图= viewSize.width / viewSize.height
  让 resolutionImage = imageSize.width / imageSize.height
    
  3
  轴比例图:CGFloat
  如果图> //如果图图{
    比例= viewSize.height / imageSize.height
  } 无论如何{
    比例= viewSize.width / imageSize.width
  }
    
  // 4
  让 featureWidthScaled = featureFrame.size.width * scale
  让 featureHeightScaled = featureFrame.size.height * scale
    
  // 5
  让 imageWidthScaled = imageSize.width *比例
  让 imageHeightScaled = imageSize.height *比例
  让 imagePointXScaled = (viewSize.width - imageWidthScaled) /  2
  让 imagePointYScaled = (viewSize.height - imageHeightScaled) /  2
    
  // 6
  让 featurePointXScaled = imagePointXScaled + featureFrame.origin.x * scale
  让 featurePointYScaled = imagePointYScaled + featureFrame.origin.y * scale
    
  // 7
  返回CGRect (x: featurePointXScaled,
                y: featurePointYScaled,
                宽度:featureWidthScaled,
                高度:featureHeightScaled)
  }

这是代码中发生的事情:

  1. 该方法将CGRect图像的原始大小、显示的图像大小和UIImageView
  2. 图像和视图的分别通过将它们的高度和组成相来计算。
  3. 另一边则是这样。
  4. 这个方法计算宽度和高度。框架的宽度和高度乘以比例来计算宽度和高度。
  5. 框架的原点也必须缩放;否则,尺寸正确,它也会在错误的位置转移中心。
  6. 新原点的计算方法以 x 和 y 比例加到未缩放的原点乘车比例。
  7. 返回一个缩放CGRect的,配置有计算的原点和大小。

你有一个规模CGRect可以从doodled到doodle。是的,那是你自己。在你的下一个拼字游戏中查找并我。

转到ScaledElementProcessor.swift并修改最process(in:callback:)里面的循环for以使用以下代码:

对于 line.elements 中的元素 {
  让 frame =  self .createScaledFrame(
    featureFrame: element.frame,
    图像大小:图像大小,
    viewFrame: imageView.frame)
  
  让 shapeLayer =  self .createShapeLayer(frame: frame)
  让 scaledElement =  ScaledElement (frame: frame, shapeLayer: shapeLayer)
  scaledElements.append(scaledElement)
}

新添加的行创建一个缩放的框架,使用它来创建正确位置的图层。

作品并运行在正确的位置。你应该是画框的。

缩放到图像的帧

照片就够了;是时候从一些地方默认了!

用相机码头

该项目已经在 ViewController.swift的底部扩展中设置了相机和选择器代码。如果您现在尝试使用它,您会注意到没有一个帧匹配。那是因为它暂停使用预加载图像中的旧帧您需要在拍摄或选择照片时删除这些并绘制新的。

将以下方法添加到ViewController

函数 removeFrames() {
  守卫让 sublayers = frameSublayer.sublayers else { return }
  对于子层中的子层{
    sublayer.removeFromSuperlayer()
  }
}

这个方法使用你的for循环层从帧子层中删除所有子。这为提供了下一张照片的干净画布。

要合并检测代码,以下新方法添加到ViewController

// 1
自定义函数drawFeatures(
  在 imageView: UIImageView ,
  完成:(()->输入)?=无
  ) {
  // 2
  删除帧()
  processor.process(in: imageView) { 文本,元素
    elements.forEach() { 中的元素
      自我.frameSublayer.addSublayer(element.shapeLayer)
    }
    self .scannedText =文本
    // 3
    完成?()
  }
}

下面是变化:

  1. 这样做的方法UIImageView和理由,以便您知道何时完成。
  2. 在处理新图像之前会自动删除帧。
  3. 一切完成后引发成就。

现在,用下面的内容替换对processor.process(in:callback:)在的调用中viewDidLoad()

drawFeatures(在:imageView)

向下滚动到类扩展并找到imagePickerController(_:didFinishPickingMediaWithInfo:);随后添加到if块的类型,imageView.image = pickedImage

drawFeatures(在:imageView)

当您拍摄或选择新照片时,此代码可确保旧框架被移除并替换为新照片中的框架。

如果您使用的是真实的设备(如果您使用的是真实的设备),请尝试打印一些照片的东西。您会看到一些照片的东西:

乱码文本检测

这里发生的原因?

您将是区域内的介绍方向图像,因为以上是区域内的问题。

处理图像方向

应用程序被关注时为关注。当设备收藏此书签时,因此现在更容易关注用户。

这张照片要求用户看您拍摄的旋转照片。将UICameraPicker人像在幕后旋转90UIImageViewUIImage

旋转后的照片

会导致一些令人困惑的结果。您可以在VisionMetadata中方向的方向指定照片。

这就是 ML 查看照片的方式,因此涂装不正确。

一直以来,您将照片固定不变,该项目需要始终命名为 +UIImage.swift的扩展名。此扩展添加一种照片,UIImage可将任何照片的方向为上方位置。的方向正确,一切顺利进行!

打开ViewController.swift并在imagePickerController(_:didFinishPickingMediaWithInfo:)中替换imageView.image = pickedImage为以下内容:

// 1
让fixedImage = pickImage.fixOrientation()
 // 2 
imageView.image =固定图像

下面是变化:

  1. 新选择的图像,pickedImage被旋转回向上位置。
  2. 然后,将旋转后的图像分配给imageView

制作并运行。再拍那张照片的位置。你应该在的看到所有正确的内容。

工作机器学习套件框架

分享文字

这最后不需要你采取任何行动。这些不是最好的吗?该应用程序与UIActivityViewController。看shareDidTouch()

@IBAction  func  shareDidTouch(_ 发件人:UIBarButtonItem){
  让 vc =  UIActivityViewController (
    活动项目:[textView.text,imageView.image !],
    应用活动:[])

  现在(vc,动画:真,完成:无)
}

这是一个简单的两步。然后创建一个UIActivityViewController包含文本和图像的文件。然后present()让用户做剩下的事情。

结论

在本教程中,您学习了:

  • 通过制造文本检测照片应用程序来了解 ML Kit 的基础知识。
  • ML Kit 文本识别API、图像比例和方向。

而您在这些没有 ML 的所有情况下都完成了工作。:]

要了解有关 Firebase 和 ML Kit 的更多信息,请查看官方文档

点击下载项目资料与查看原文

这里也推荐一些面试相关的内容!