基于Firebase平臺開發(fā)(一) —— 基于ML Kit的iOS圖片中文字的識別(一)

版本記錄

版本號 時(shí)間
V1.0 2019.02.01 星期五

前言

Firebase是一家實(shí)時(shí)后端數(shù)據(jù)庫創(chuàng)業(yè)公司顾瞻,它能幫助開發(fā)者很快的寫出Web端和移動(dòng)端的應(yīng)用膊存。自2014年10月Google收購Firebase以來判哥,用戶可以在更方便地使用Firebase的同時(shí)遥倦,結(jié)合Google的云服務(wù)谤绳。Firebase能讓你的App從零到一。也就是說它可以幫助手機(jī)以及網(wǎng)頁應(yīng)用的開發(fā)者輕松構(gòu)建App袒哥。通過Firebase背后負(fù)載的框架就可以簡單地開發(fā)一個(gè)App缩筛,無需服務(wù)器以及基礎(chǔ)設(shè)施。接下來幾篇我們就一起看一下基于Firebase平臺的開發(fā)堡称。

Firebase提供的服務(wù)

首先我們看一下Firebase目前提供的服務(wù):

這里看一下ML Kit關(guān)于機(jī)器學(xué)習(xí)的部分還是BETA瞎抛。


開始

首先看下寫作環(huán)境

Swift 4.2, iOS 12, Xcode 10

在這個(gè)ML Kit教程中,您將學(xué)習(xí)如何利用GoogleML Kit來檢測和識別文本却紧。

幾年前桐臊,有兩種類型的機(jī)器學(xué)習(xí)(ML)開發(fā)人員:高級開發(fā)人員和其他人。底層的ML水平可能很難晓殊,這是很多數(shù)學(xué)断凶,它使用邏輯回歸(logistic regression),稀疏性(sparsity)和神經(jīng)網(wǎng)絡(luò)(neural nets)等詞語巫俺。但它并不一定要那么難认烁。

您也可以成為ML開發(fā)人員! ML的核心很簡單介汹。有了它砚著,您可以通過訓(xùn)練軟件模型來識別模式而不是硬編碼每種情況和您能想到各種case來解決問題。但是痴昧,開始這可能是令人生畏的稽穆,這是您可以依賴現(xiàn)有工具的地方。

1. Machine Learning and Tooling

就像iOS開發(fā)一樣赶撰,ML就是工具舌镶。你不會(huì)建立自己的UITableView柱彻,或者至少你不應(yīng)該,你會(huì)使用一個(gè)框架餐胀,比如UIKit哟楷。

它與ML的方式相同。 ML擁有蓬勃發(fā)展的工具生態(tài)系統(tǒng)否灾。例如卖擅,Tensorflow簡化了訓(xùn)練和運(yùn)行模型。 TensorFlow LiteiOSAndroid設(shè)備提供模型支持墨技。

這些工具中的每一個(gè)都需要一些ML的經(jīng)驗(yàn)惩阶。如果您不是ML專家但想要解決特定問題怎么辦?對于這些情況扣汪,有ML Kit断楷。


ML Kit

ML Kit是一款移動(dòng)SDK,可將GoogleML專業(yè)知識帶入您的應(yīng)用崭别。 ML Kit的API有兩個(gè)主要部分冬筒,用于常見用例和自定義模型(common use cases and custom models),無論經(jīng)驗(yàn)如何都易于使用茅主。

當(dāng)前的API支持:

這些用例中的每一個(gè)都帶有一個(gè)預(yù)先訓(xùn)練的模型舞痰,該模型包含在一個(gè)易于使用的API中。 是時(shí)候開始建設(shè)了诀姚!

在本教程中响牛,您將構(gòu)建一個(gè)名為Extractor的應(yīng)用程序。 你有沒有拍下一張標(biāo)志或海報(bào)的圖片來寫下文字內(nèi)容学搜? 如果一個(gè)應(yīng)用程序可以將文本從標(biāo)志上剝離并保存給您娃善,隨時(shí)可以使用,那就太棒了瑞佩。 例如聚磺,您可以拍攝尋址信封的照片并保存地址。 這正是你要對這個(gè)項(xiàng)目做的炬丸! 做好準(zhǔn)備瘫寝!

首先,打開本教程的項(xiàng)目材料稠炬,該項(xiàng)目使用CocoaPods來管理依賴項(xiàng)焕阿。


Setting Up ML Kit

每個(gè)ML Kit API都有一組不同的CocoaPods依賴項(xiàng)。 這很有用首启,因?yàn)槟恍枰墤?yīng)用程序所需的依賴項(xiàng)暮屡。 例如,如果您沒有識別地標(biāo)毅桃,則在您的應(yīng)用中不需要該模型褒纲。 在Extractor中准夷,您將使用Text Recognition API

如果您要將Text Recognition API添加到您的應(yīng)用程序莺掠,那么您需要將以下行添加到您的Podfile中衫嵌,但您不必為啟動(dòng)項(xiàng)目執(zhí)行此操作,因?yàn)?code>Podfile中有已經(jīng)寫好 - 您可以檢查彻秆。

pod 'Firebase/Core' => '5.5.0'
pod 'Firebase/MLVision' => '5.5.0'
pod 'Firebase/MLVisionTextModel' => '5.5.0'

您必須打開終端應(yīng)用程序楔绞,切換到項(xiàng)目文件夾并運(yùn)行以下命令來安裝項(xiàng)目中使用的CocoaPods

pod install

安裝CocoaPods后,在Xcode中打開Extractor.xcworkspace唇兑。

注意:您可能會(huì)注意到項(xiàng)目文件夾包含名為Extractor.xcodeproj的項(xiàng)目文件和名為Extractor.xcworkspace的工作區(qū)文件酒朵,該文件是您在Xcode中打開的文件。 不要打開項(xiàng)目文件幔亥,因?yàn)樗话幾g應(yīng)用程序所需的其他CocoaPods項(xiàng)目耻讽。

該項(xiàng)目包含以下重要文件:

  • ViewController.swift:此項(xiàng)目中唯一的控制器察纯。
  • + UIImage.swift:用于修復(fù)圖像方向的UIImage extension帕棉。

Setting Up a Firebase Account

首先要?jiǎng)?chuàng)建一個(gè)賬戶,這個(gè)會(huì)在后面單獨(dú)分出來一篇進(jìn)行詳細(xì)介紹饼记。

一般的想法是:

  • 1) 創(chuàng)建一個(gè)帳戶香伴。
  • 2) 創(chuàng)建一個(gè)項(xiàng)目。
  • 3) 將iOS應(yīng)用添加到項(xiàng)目中具则。
  • 4) 將GoogleService-Info.plist拖到您的項(xiàng)目中即纲。
  • 5) 在AppDelegate中初始化Firebase

這是一個(gè)簡單的過程博肋,但如果您遇到任何障礙低斋,后面我會(huì)單獨(dú)進(jìn)行講解說明。

注意:您需要設(shè)置Firebase并為最終和初始項(xiàng)目創(chuàng)建自己的GoogleService-Info.plist匪凡。

構(gòu)建并運(yùn)行應(yīng)用程序膊畴,您將看到它看起來像這樣:

除了允許您通過右上角的操作按鈕共享硬編碼文本之外,它不會(huì)執(zhí)行任何操作病游。 您將使用ML Kit將此應(yīng)用程序變?yōu)楝F(xiàn)實(shí)唇跨。


Detecting Basic Text

準(zhǔn)備好第一次文本檢測! 您可以首先向用戶演示如何使用該應(yīng)用程序衬衬。

一個(gè)很好的演示是在應(yīng)用程序首次啟動(dòng)時(shí)掃描示例圖像买猖。 在資源文件夾中包含了一個(gè)名為scanning-text的圖像,該圖像當(dāng)前是視圖控制器的UIImageView中顯示的默認(rèn)圖像滋尉。 您將使用它作為示例圖像玉控。

但首先,您需要一個(gè)文本檢測器來檢測圖像中的文本狮惜。

1. Creating a Text Detector

創(chuàng)建名為ScaledElementProcessor.swift的文件并添加以下代碼:

import Firebase

class ScaledElementProcessor {

}

很好高诺! 你們都完成了既棺! 開個(gè)玩笑。 在類中創(chuàng)建text-detector屬性:

let vision = Vision.vision()
var textRecognizer: VisionTextRecognizer!
  
init() {
  textRecognizer = vision.onDeviceTextRecognizer()
}

textRecognizer是可用于檢測圖像中文本的主要對象懒叛。 您將使用它來識別UIImageView當(dāng)前顯示的圖像中包含的文本丸冕。 將以下檢測方法添加到類中:

func process(in imageView: UIImageView, 
  callback: @escaping (_ text: String) -> Void) {
  // 1
  guard let image = imageView.image else { return }
  // 2
  let visionImage = VisionImage(image: image)
  // 3
  textRecognizer.process(visionImage) { result, error in
    // 4
    guard 
      error == nil, 
      let result = result, 
      !result.text.isEmpty 
      else {
        callback("")
        return
    }
    // 5
    callback(result.text)
  }
}

花一點(diǎn)時(shí)間來理解這段代碼:

  • 1) 在這里,您檢查imageView是否實(shí)際包含圖像薛窥。 如果沒有胖烛,只需返回。 但是诅迷,理想情況下佩番,您可以拋出或提供優(yōu)雅的失敗提示。
  • 2) ML Kit使用特殊的VisionImage類型罢杉。 它很有用趟畏,因?yàn)樗梢园?code>ML Kit處理圖像的特定元數(shù)據(jù),例如圖像的方向滩租。
  • 3) textRecognizer有一個(gè)接收VisionImageprocess方法赋秀,它以傳遞給閉包的參數(shù)的形式返回一個(gè)文本結(jié)果數(shù)組。
  • 4) 結(jié)果可能是nil律想,在這種情況下猎莲,您將要為回調(diào)返回一個(gè)空字符串。
  • 5) 最后技即,觸發(fā)回調(diào)以中繼識別的文本著洼。

2. Using the Text Detector

打開ViewController.swift,在類主體頂部的outlets之后而叼,添加一個(gè)ScaledElementProcessor實(shí)例作為屬性:

let processor = ScaledElementProcessor()

然后身笤,在viewDidLoad()的底部添加以下代碼,以在UITextView中顯示檢測到的文本:

processor.process(in: imageView) { text in
  self.scannedText = text
}

這個(gè)小block調(diào)用process(in:)葵陵,傳遞主imageView并將識別的文本分配給回調(diào)中的scansText屬性液荸。

運(yùn)行該應(yīng)用程序,您應(yīng)該在圖像正下方看到以下文本:

Your
SCanned
text
will
appear
here 

您可能需要滾動(dòng)文本視圖以顯示最后幾行埃难。

注意掃描的“S”“C”是大寫的莹弊。 有時(shí),使用特定字體時(shí)涡尘,可能會(huì)出現(xiàn)錯(cuò)誤的情況忍弛。 這就是為什么文本顯示在UITextView中的原因,因此用戶可以手動(dòng)編輯以修復(fù)檢測錯(cuò)誤考抄。

3. Understanding the Classes

注意:您不必復(fù)制本節(jié)中的代碼细疚,它只是有助于解釋概念。您將在下一部分中向應(yīng)用添加代碼川梅。

VisionText

您是否注意到ScaledElementProcessor中的textRecognizer.process(in :)的回調(diào)在result參數(shù)中返回了一個(gè)對象而不是普通的文本疯兼?這是VisionText的一個(gè)實(shí)例然遏,它包含許多有用的信息,例如識別的文本吧彪。但是你想做的不僅僅是獲取文本待侵。描繪出每個(gè)識別的文本元素的每個(gè)frame不是很酷嗎?

ML Kit以類似于樹的結(jié)構(gòu)提供結(jié)果姨裸。您需要遍歷葉元素以獲取包含已識別文本的frame的位置和大小秧倾。如果對樹結(jié)構(gòu)的引用讓您感覺到困難,請不要太擔(dān)心傀缩。以下部分應(yīng)闡明正在發(fā)生的事情那先。

VisionTextBlock

使用已識別的文本時(shí),您可以從VisionText對象開始 - 這是一個(gè)對象(稱為樹)赡艰,它可以包含多個(gè)文本塊(如樹中的分支)售淡。您遍歷每個(gè)分支,這是塊(blocks)數(shù)組中的VisionTextBlock對象慷垮,如下所示:

for block in result.blocks {

}

VisionTextElement

VisionTextBlock只是一個(gè)對象揖闸,包含一系列文本(如樹枝上的葉子),每個(gè)文本都由一個(gè)VisionTextElement實(shí)例表示换帜。 這個(gè)對象的嵌套允許您查看已識別文本的層次結(jié)構(gòu)楔壤。

循環(huán)遍歷每個(gè)對象如下所示:

for block in result.blocks {
  for line in block.lines {
    for element in line.elements {

    }
  }
}

此層次結(jié)構(gòu)中的所有對象都包含文本所在的frame鹤啡。 但是惯驼,每個(gè)對象包含不同級別的粒度(granularity)。 塊可以包含多個(gè)行递瑰,一行可以包含多個(gè)元素祟牲,并且元素可以包含多個(gè)符號。

在本教程中抖部,您將使用元素(elements)作為粒度級別。 元素通常對應(yīng)于單詞。 這將允許您繪制每個(gè)單詞并向用戶顯示每個(gè)單詞在圖像中的位置意狠。

最后一個(gè)循環(huán)遍歷文本塊的每一行中的元素嘶卧。 這些元素包含frame,一個(gè)簡單的CGRect俯萎。 使用此frame傲宜,您可以在圖像上的單詞周圍繪制邊框。


Highlighting the Text Frames

1. Detecting Frames

要在圖像上繪制夫啊,您需要使用文本元素的frame創(chuàng)建CAShapeLayer函卒。 打開ScaledElementProcessor.swift并將以下struct添加到文件的頂部:

struct ScaledElement {
  let frame: CGRect
  let shapeLayer: CALayer
}

這個(gè)struct是便利實(shí)現(xiàn)。 它可以更輕松地將frame和CAShapeLayer分組到控制器撇眯。 現(xiàn)在报嵌,您需要一個(gè)輔助方法來從元素的frame創(chuàng)建CAShapeLayer虱咧。

將以下代碼添加到ScaledElementProcessor的末尾:

private func createShapeLayer(frame: CGRect) -> CAShapeLayer {
  // 1
  let bpath = UIBezierPath(rect: frame)
  let shapeLayer = CAShapeLayer()
  shapeLayer.path = bpath.cgPath
  // 2
  shapeLayer.strokeColor = Constants.lineColor
  shapeLayer.fillColor = Constants.fillColor
  shapeLayer.lineWidth = Constants.lineWidth
  return shapeLayer
}

// MARK: - private
  
// 3
private enum Constants {
  static let lineWidth: CGFloat = 3.0
  static let lineColor = UIColor.yellow.cgColor
  static let fillColor = UIColor.clear.cgColor
}

這是代碼的作用:

  • 1) CAShapeLayer沒有接收CGRect的初始化程序。 因此锚国,您使用CGRect構(gòu)造UIBezierPath并將形狀圖層的path設(shè)置為UIBezierPath腕巡。
  • 2) 顏色和寬度的可視屬性通過常量枚舉設(shè)置。
  • 3) 這個(gè)枚舉有助于保持著色和寬度一致血筑。

現(xiàn)在逸雹,用以下代碼替換process(in:callback :)

// 1
func process(
  in imageView: UIImageView, 
  callback: @escaping (_ text: String, _ scaledElements: [ScaledElement]) -> Void
  ) {
  guard let image = imageView.image else { return }
  let visionImage = VisionImage(image: image)
    
  textRecognizer.process(visionImage) { result, error in
    guard 
      error == nil, 
      let result = result, 
      !result.text.isEmpty 
      else {
        callback("", [])
        return
    }
  
    // 2
    var scaledElements: [ScaledElement] = []
    // 3
    for block in result.blocks {
      for line in block.lines {
        for element in line.elements {
          // 4
          let shapeLayer = self.createShapeLayer(frame: element.frame)
          let scaledElement = 
            ScaledElement(frame: element.frame, shapeLayer: shapeLayer)

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

下面進(jìn)行詳細(xì)說明:

  • 1) 除了識別的文本之外,回調(diào)現(xiàn)在還會(huì)獲取一系列ScaledElement實(shí)例云挟。
  • 2) scaledElements用作frameshape layer的集合梆砸。
  • 3) 正如上面所概述的,代碼使用for循環(huán)來獲取每個(gè)元素的frame园欣。
  • 4) 最里面的for循環(huán)從元素的frame創(chuàng)建shape layer帖世,然后用于構(gòu)造新的ScaledElement實(shí)例。
  • 5) 將新創(chuàng)建的實(shí)例添加到scaledElements沸枯。

2. Drawing

上面的代碼是把你的鉛筆放在一起日矫。 現(xiàn)在,是時(shí)候畫了绑榴! 打開ViewController.swift哪轿,在viewDidLoad()中,用以下代碼替換對process(in :)的調(diào)用:

processor.process(in: imageView) { text, elements in
  self.scannedText = text
  elements.forEach() { feature in
    self.frameSublayer.addSublayer(feature.shapeLayer)
  }
}

ViewController有一個(gè)frameSublayer屬性翔怎,附加到imageView窃诉。 在這里,您將每個(gè)元素的shape layer添加到子圖層赤套,以便iOS將自動(dòng)在圖像上繪制shape飘痛。

構(gòu)建并運(yùn)行。 看看你的藝術(shù)作品容握!

哦宣脉。 那是什么? 看起來你更像畢加索而不是莫奈剔氏。 這里發(fā)生了什么塑猖? 好吧,現(xiàn)在可能是談?wù)?code>scale的時(shí)候了谈跛。


Understanding Image Scaling

默認(rèn)的scanning-text.png圖像為654×999 (width x height)羊苟;但是,UIImageView具有“Aspect Fit”“Content Mode”币旧,可以在視圖中將圖像縮放到375×369践险。 ML Kit接收圖像的實(shí)際大小,并根據(jù)該大小返回元素frame。 然后根據(jù)縮放的大小繪制來自實(shí)際大小的frame巍虫,這會(huì)產(chǎn)生令人困惑的結(jié)果彭则。

在上圖中,請注意縮放大小和實(shí)際大小之間的差異占遥。 您可以看到frame與實(shí)際大小匹配俯抖。 要獲取正確的frame,您需要計(jì)算圖像與視圖的比例瓦胎。

公式相當(dāng)簡單(??):

  • 1) 計(jì)算視圖和圖像的分辨率芬萍。
  • 2) 通過比較分辨率確定比例。
  • 3) 通過將它們乘以scale來計(jì)算高度搔啊,寬度和原點(diǎn)x和y柬祠。
  • 4) 使用這些數(shù)據(jù)點(diǎn)創(chuàng)建新的CGRect

如果這聽起來令人困惑负芋,那就沒關(guān)系漫蛔! 當(dāng)你看到代碼時(shí),你會(huì)明白的旧蛾。


Calculating the Scale

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

// 1
private func createScaledFrame(
  featureFrame: CGRect, 
  imageSize: CGSize, viewFrame: CGRect) 
  -> CGRect {
  let viewSize = viewFrame.size
    
  // 2
  let resolutionView = viewSize.width / viewSize.height
  let resolutionImage = imageSize.width / imageSize.height
    
  // 3
  var scale: CGFloat
  if resolutionView > resolutionImage {
    scale = viewSize.height / imageSize.height
  } else {
    scale = viewSize.width / imageSize.width
  }
    
  // 4
  let featureWidthScaled = featureFrame.size.width * scale
  let featureHeightScaled = featureFrame.size.height * scale
    
  // 5
  let imageWidthScaled = imageSize.width * scale
  let imageHeightScaled = imageSize.height * scale
  let imagePointXScaled = (viewSize.width - imageWidthScaled) / 2
  let imagePointYScaled = (viewSize.height - imageHeightScaled) / 2
    
  // 6
  let featurePointXScaled = imagePointXScaled + featureFrame.origin.x * scale
  let featurePointYScaled = imagePointYScaled + featureFrame.origin.y * scale
    
  // 7
  return CGRect(x: featurePointXScaled,
                y: featurePointYScaled,
                width: featureWidthScaled,
                height: featureHeightScaled)
  }

這是代碼中發(fā)生的事情:

  • 1) 此方法接受CGRects的原始圖像大小莽龟,顯示的圖像大小和UIImageView的frame。
  • 2) 圖像和視圖的分辨率分別通過它們的高度和寬度之比來計(jì)算锨天。
  • 3) 比例由哪個(gè)分辨率更大來確定毯盈。如果視圖較大,則按高度縮放病袄;否則搂赋,你按寬度縮放。
  • 4) 此方法計(jì)算寬度和高度陪拘。frame的寬度和高度乘以比例以計(jì)算縮放的寬度和高度厂镇。
  • 5) frame的原點(diǎn)也必須縮放,否則左刽,即使尺寸正確,它也會(huì)偏離錯(cuò)誤的位置酌媒。
  • 6) 通過將x和y點(diǎn)scale添加到未縮放的原點(diǎn)乘以scale來計(jì)算新原點(diǎn)欠痴。
  • 7) 返回縮放的CGRect,使用計(jì)算的原點(diǎn)和大小進(jìn)行配置秒咨。

既然你有一個(gè)縮放的CGRect喇辽,你可以從涂鴉到sgraffito

轉(zhuǎn)到ScaledElementProcessor.swift中的process(in:callback :)并修改最里面的for循環(huán)以使用以下代碼:

for element in line.elements {
  let frame = self.createScaledFrame(
    featureFrame: element.frame,
    imageSize: image.size, 
    viewFrame: imageView.frame)
  
  let shapeLayer = self.createShapeLayer(frame: frame)
  let scaledElement = ScaledElement(frame: frame, shapeLayer: shapeLayer)
  scaledElements.append(scaledElement)
}

新添加的行創(chuàng)建一個(gè)縮放frame雨席,代碼用于創(chuàng)建正確的位置shape layer菩咨。

建立并運(yùn)行。 您應(yīng)該看到在正確的位置繪制的frame。 你是一位大師級畫家抽米!

足夠的默認(rèn)照片特占;是時(shí)候使用其他資源進(jìn)行測試了!


Taking Photos with the Camera

該項(xiàng)目已經(jīng)在ViewController.swift底部的擴(kuò)展中設(shè)置了相機(jī)和庫選取器代碼云茸。 如果您現(xiàn)在嘗試使用它是目,您會(huì)注意到?jīng)]有任何frame匹配。 那是因?yàn)樗栽谑褂妙A(yù)裝圖像中的舊frame标捺! 拍攝或選擇照片時(shí)懊纳,您需要?jiǎng)h除它們并繪制新的。

將以下方法添加到ViewController

private func removeFrames() {
  guard let sublayers = frameSublayer.sublayers else { return }
  for sublayer in sublayers {
    sublayer.removeFromSuperlayer()
  }
}

此方法使用for循環(huán)從frame sublayer中刪除所有子層亡容。 這為您提供了下一張照片的干凈畫布嗤疯。

要合并檢測代碼,請將以下新方法添加到ViewController

// 1
private func drawFeatures(
  in imageView: UIImageView, 
  completion: (() -> Void)? = nil
  ) {
  // 2
  removeFrames()
  processor.process(in: imageView) { text, elements in
    elements.forEach() { element in
      self.frameSublayer.addSublayer(element.shapeLayer)
    }
    self.scannedText = text
    // 3
    completion?()
  }
}

這是改變的地方:

  • 1) 此方法接受UIImageView和回調(diào)闺兢,以便您知道它何時(shí)完成身弊。
  • 2) 在處理新圖像之前會(huì)自動(dòng)刪除frame。
  • 3) 一切都完成后觸發(fā)完成回調(diào)列敲。

現(xiàn)在阱佛,用以下代碼替換viewDidLoad()中對processor.process(in:callback :)的調(diào)用:

drawFeatures(in: imageView)

向下滾動(dòng)到類擴(kuò)展并找到imagePickerController(_:didFinishPickingMediaWithInfo :);在imageView.image = pickedImage之后戴而,將這行代碼添加到if塊的末尾:

drawFeatures(in: imageView)

拍攝或選擇新照片時(shí)凑术,此代碼可確保刪除舊frame并替換為新照片中的frame。

構(gòu)建并運(yùn)行所意。 如果您使用的是真實(shí)設(shè)備(不是模擬器)淮逊,請拍下印刷文字。 你可能會(huì)看到奇怪的東西:

這里發(fā)生了什么扶踊?

您將在一秒鐘內(nèi)解決圖像方向泄鹏,因?yàn)樯厦媸欠较騿栴}。


Dealing With Image Orientations

此應(yīng)用程序以縱向方向鎖定秧耗。 在設(shè)備旋轉(zhuǎn)時(shí)重繪frame是很棘手的备籽,因此現(xiàn)在更容易限制用戶。

此限制要求用戶拍攝豎屏照片分井。 UICameraPicker將豎屏照片在幕后旋轉(zhuǎn)90度车猬。 您沒有看到旋轉(zhuǎn),因?yàn)?code>UIImageView會(huì)為您旋轉(zhuǎn)它尺锚。 但是珠闰,detector得到的是旋轉(zhuǎn)的UIImage

這導(dǎo)致一些令人困惑的結(jié)果瘫辩。 ML Kit允許您在VisionMetadata對象中指定照片的方向伏嗜。 設(shè)置正確的方向?qū)⒎祷卣_的文本坛悉,但將為旋轉(zhuǎn)的照片繪制frame。

因此承绸,您需要將照片方向固定為始終處于“向上”位置裸影。 該項(xiàng)目包含一個(gè)名為+ UIImage.swift的擴(kuò)展。 此擴(kuò)展為UIImage添加了一種方法八酒,可將任何照片的方向更改為向上位置空民。 一旦照片處于正確的方向,一切都將順利進(jìn)行羞迷!

打開ViewController.swift界轩,在imagePickerController(_:didFinishPickingMediaWithInfo :)中,用以下內(nèi)容替換imageView.image = pickedImage

// 1
let fixedImage = pickedImage.fixOrientation()
// 2
imageView.image = fixedImage

下面詳細(xì)說明:

  • 1) 新選擇的圖像pickedImage將旋轉(zhuǎn)回向上位置衔瓮。
  • 2) 然后浊猾,將旋轉(zhuǎn)的圖像分配給imageView

建立并運(yùn)行热鞍。 再拍那張照片葫慎。 你應(yīng)該在正確的地方看到一切。


Sharing the Text

最后一步不需要您采取任何措施薇宠,該應(yīng)用程序與UIActivityViewController集成偷办。 看看shareDidTouch()

@IBAction func shareDidTouch(_ sender: UIBarButtonItem) {
  let vc = UIActivityViewController(
    activityItems: [textView.text, imageView.image!], 
    applicationActivities: [])

  present(vc, animated: true, completion: nil)
}

這是一個(gè)簡單的兩步過程。 創(chuàng)建一個(gè)包含掃描文本和圖像的UIActivityViewController澄港。 然后調(diào)用present()并讓用戶完成其余的工作椒涯。

在本教程中,您學(xué)習(xí)了:

  • 通過構(gòu)建文本檢測照片應(yīng)用程序了解ML Kit的基礎(chǔ)知識回梧。
  • ML Kit文本識別API废岂,圖像比例和方向。

要了解有關(guān)FirebaseML Kit的更多信息狱意,請查看official documentation湖苞。

后記

本篇主要講述了基于ML Kit的iOS圖片中文字的識別,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末详囤,一起剝皮案震驚了整個(gè)濱河市财骨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纬纪,老刑警劉巖蚓再,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異包各,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)靶庙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門问畅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事护姆》耍” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵卵皂,是天一觀的道長秩铆。 經(jīng)常有香客問我,道長灯变,這世上最難降的妖魔是什么殴玛? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮添祸,結(jié)果婚禮上滚粟,老公的妹妹穿的比我還像新娘。我一直安慰自己刃泌,他們只是感情好凡壤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著耙替,像睡著了一般亚侠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上俗扇,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天硝烂,我揣著相機(jī)與錄音,去河邊找鬼狐援。 笑死钢坦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的啥酱。 我是一名探鬼主播爹凹,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼镶殷!你這毒婦竟也來了禾酱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤绘趋,失蹤者是張志新(化名)和其女友劉穎颤陶,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陷遮,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滓走,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了帽馋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片搅方。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡比吭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出姨涡,到底是詐尸還是另有隱情衩藤,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布涛漂,位于F島的核電站赏表,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏匈仗。R本人自食惡果不足惜瓢剿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望锚沸。 院中可真熱鬧跋选,春花似錦、人聲如沸哗蜈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽距潘。三九已至炼列,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間音比,已是汗流浹背俭尖。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洞翩,地道東北人稽犁。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像骚亿,于是被迫代替她去往敵國和親已亥。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容