#WWDC19•

Text Recognition in Vision FrameworkFrank Doepke, Vision Team
Cédric Bray, Vision Team© 2019 Apple Inc. All rights reserved. Redistribution or public display not permitted without written permission from Apple.

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNDetectTextRectanglesRequest { (request, error) in
guard let observations = request.results as? [VNTextObservation] else { return }}request.reportCharacterBoxes = true
try? requestHandler.perform([request])

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNDetectTextRectanglesRequest { (request, error) in
guard let observations = request.results as? [VNTextObservation] else { return }}request.reportCharacterBoxes = true
try? requestHandler.perform([request])

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNDetectTextRectanglesRequest { (request, error) in
guard let observations = request.results as? [VNTextObservation] else { return }for currentObservation in observations{// iterate over the character boxes in the observation
for currentCharacterBox in currentObservation.characterBoxes!
{

}

}

}

request.reportCharacterBoxes = true
try? requestHandler.perform([request])

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNDetectTextRectanglesRequest { (request, error) in
guard let observations = request.results as? [VNTextObservation] else { return }for currentObservation in observations{// iterate over the character boxes in the observation
for currentCharacterBox in currentObservation.characterBoxes!
{// TODO: Train CoreML model to do character recognition}}}

request.reportCharacterBoxes = true
try? requestHandler.perform([request])

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNDetectTextRectanglesRequest { (request, error) in
guard let observations = request.results as? [VNTextObservation] else { return }for currentObservation in observations{// iterate over the character boxes in the observation
for currentCharacterBox in currentObservation.characterBoxes!
{// TODO: Train CoreML model to do character recognition// TODO: run model on character box}}}

request.reportCharacterBoxes = true
try? requestHandler.perform([request])

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNDetectTextRectanglesRequest { (request, error) in
guard let observations = request.results as? [VNTextObservation] else { return }for currentObservation in observations{// iterate over the character boxes in the observation
for currentCharacterBox in currentObservation.characterBoxes!
{// TODO: Train CoreML model to do character recognition// TODO: run model on character box// TODO: threshold against possible garbage results}}}

request.reportCharacterBoxes = true
try? requestHandler.perform([request])

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNDetectTextRectanglesRequest { (request, error) in
guard let observations = request.results as? [VNTextObservation] else { return }for currentObservation in observations{// iterate over the character boxes in the observation
for currentCharacterBox in currentObservation.characterBoxes!
{// TODO: Train CoreML model to do character recognition// TODO: run model on character box// TODO: threshold against possible garbage results}// TODO: concatenate characters into string}}

request.reportCharacterBoxes = true
try? requestHandler.perform([request])

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNDetectTextRectanglesRequest { (request, error) in
guard let observations = request.results as? [VNTextObservation] else { return }}for currentObservation in observations{// iterate over the character boxes in the observation
for currentCharacterBox in currentObservation.characterBoxes!
{// TODO: Train CoreML model to do character recognition// TODO: run model on character box// TODO: threshold against possible garbage results}// TODO: concatenate characters into string// TODO: Fix recognized words based on dictionary//
and other probability heuristics for character pairs}

request.reportCharacterBoxes = true
try? requestHandler.perform([request])

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }for currentObservation in observations{
let topCandidate = currentObservation.topCandidates(1)if let recognizedText = topCandidate.first {print(recognizedText.string)}}}
request.recognitionLevel = .accurate

try? requestHandler.perform([request])

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }for currentObservation in observations{
let topCandidate = currentObservation.topCandidates(1)if let recognizedText = topCandidate.first {print(recognizedText.string)}}}
request.recognitionLevel = .accurate

try? requestHandler.perform([request])

let requestHandler = VNImageRequestHandler(url: imageURL, options: [:])
let request = VNRecognizeTextRequest { (request, error) in
guard let observations = request.results as? [VNRecognizedTextObservation] else { return }for currentObservation in observations{
let topCandidate = currentObservation.topCandidates(1)if let recognizedText = topCandidate.first {print(recognizedText.string)}}}
request.recognitionLevel = .accurate

try? requestHandler.perform([request])

Our Journey Today•

How Text Recognition works•

Example applications•

Best practices

Our Journey Today•

How Text Recognition works•

Example applications•

Best practices

Our Journey Today•

How Text Recognition works•

Example applications•

Best practices

Our Journey Today•

How Text Recognition works•

Example applications•

Best practices

•

What It Is and How It WorksTwo Paths to Choose From

Two Paths to Choose From

Fast

AccurateTwo Paths to Choose From

Fast
Character Detection

AccurateTwo Paths to Choose From

Fast
Character Detection

Character Recognition

Reduce stress 1eve1s (by 68 percent!)

AccurateTwo Paths to Choose From

Fast

Accurate

Character Detection

NN based Text Detection

Character Recognition

Reduce stress 1eve1s (by 68 percent!)

Two Paths to Choose From

Fast

Accurate

Character Detection

NN based Text Detection

Character Recognition

NN based Text Recognition

Reduce stress 1eve1s (by 68 percent!)

Reduce stress levels (by 68 percent!)

Two Paths to Choose From

Fast

Accurate

Character Detection

NN based Text Detection

Character Recognition

NN based Text Recognition

Reduce stress 1eve1s (by 68 percent!)

Reduce stress levels (by 68 percent!)

Language Processing NLPTwo Paths to Choose From

Fast

Accurate

Character Detection

NN based Text Detection

Character Recognition

NN based Text Recognition

Reduce stress 1eve1s (by 68 percent!)

Reduce stress levels (by 68 percent!)

Language Processing NLPResults

Two Paths to Choose FromAll on Device
Fast

Accurate

Character Detection

NN based Text Detection

Character Recognition

NN based Text Recognition

Reduce stress 1eve1s (by 68 percent!)

Reduce stress levels (by 68 percent!)

Language Processing NLPResults

Fast Versus AccurateFast Versus AccurateFast Versus AccurateFast Versus AccurateFast Versus Accurate
0.25 sec

2.0 secFast Versus Accurate

Fast

Accurate

Fast Versus Accurate

Processing time

Fast

Accurate

Optimized for real-time

Asynchronous processing

Fast Versus Accurate

Fast

Accurate

Processing time

Optimized for real-time

Asynchronous processing

Memory footprint

Smallest

LargerFast Versus Accurate

Fast

Accurate

Processing time

Optimized for real-time

Asynchronous processing

Memory footprint

Smallest

LargerSupport for rotated text

LimitedBroadFast Versus Accurate

Fast

Accurate

Processing time

Optimized for real-time

Asynchronous processing

Memory footprint

Smallest

LargerSupport for rotated text

LimitedBroad

Support for variety of fonts

LimitedDiverse font styles

Fast Versus Accurate

Fast

Accurate

Processing time

Optimized for real-time

Asynchronous processing

Memory footprint

Smallest

LargerSupport for rotated text

LimitedBroad

Support for variety of fonts

LimitedDiverse font styles

Accuracy for natural language

Good

Best

Use Cases Drive How to Configure the Request

Use Cases Drive How to Configure the Request

What is my input?
What are my processing constraints?
What am I going to do with the results?

Use Cases Drive How to Configure the Request

Camera capture

Use Cases Drive How to Configure the Request

Camera capture• Live capture at high frame rate — go fastUse Cases Drive How to Configure the Request

Camera capture• Live capture at high frame rate — go fast
• Opportunistic capture — go accurate

Use Cases Drive How to Configure the Request

Camera capture• Live capture at high frame rate — go fast
• Opportunistic capture — go accurate• Text size drives the resolution

Use Cases Drive How to Configure the Request

Post processing

Use Cases Drive How to Configure the Request

Post processing• Favor accuracy over speedReading Codes Versus Reading Natural Language

Language processingReading Codes Versus Reading Natural Language

Language processing• Corrects typical recognition errors

Reading Codes Versus Reading Natural Language

Language processing• Corrects typical recognition errors• It can get in the way for codes

Reading Codes Versus Reading Natural Language

Language processing• Corrects typical recognition errors• It can get in the way for codes• Increases the processing time and memory budget•

Performing Text Recognition

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])// Create request
let myTextRecognitionRequest = VNRecognizeTextRequest()

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])// Create request
let myTextRecognitionRequest = VNRecognizeTextRequest()// Specify a completion handler
myTextRecognitionRequest.completionHandler = myCompletionHandler

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])// Create request
let myTextRecognitionRequest = VNRecognizeTextRequest()// Specify a completion handler
myTextRecognitionRequest.completionHandler = myCompletionHandler// Select the recognition level
myTextRecognitionRequest.recognitionLevel = VNRequestTextRecognitionLevel.fast

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])// Create request
let myTextRecognitionRequest = VNRecognizeTextRequest()// Specify a completion handler
myTextRecognitionRequest.completionHandler = myCompletionHandler// Select the recognition level
myTextRecognitionRequest.recognitionLevel = VNRequestTextRecognitionLevel.accurate

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])// Create request
let myTextRecognitionRequest = VNRecognizeTextRequest()// Specify a completion handler
myTextRecognitionRequest.completionHandler = myCompletionHandler// Select the recognition level
myTextRecognitionRequest.recognitionLevel = VNRequestTextRecognitionLevel.accurate

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])// Create request
let myTextRecognitionRequest = VNRecognizeTextRequest()// Specify a completion handler
myTextRecognitionRequest.completionHandler = myCompletionHandler// Select the recognition level
myTextRecognitionRequest.recognitionLevel = VNRequestTextRecognitionLevel.accurate// Set the revision
myTextRecognitionRequest.revision = VNRecognizeTextRequestRevision1

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])// Create request
let myTextRecognitionRequest = VNRecognizeTextRequest()// Specify a completion handler
myTextRecognitionRequest.completionHandler = myCompletionHandler// Select the recognition level
myTextRecognitionRequest.recognitionLevel = VNRequestTextRecognitionLevel.accurate// Set the revision
myTextRecognitionRequest.revision = VNRecognizeTextRequestRevision1// Control language correction
myTextRecognitionRequest.usesLanguageCorrection = false

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])// Create request
let myTextRecognitionRequest = VNRecognizeTextRequest()// Specify a completion handler
myTextRecognitionRequest.completionHandler = myCompletionHandler// Select the recognition level
myTextRecognitionRequest.recognitionLevel = VNRequestTextRecognitionLevel.accurate// Set the revision
myTextRecognitionRequest.revision = VNRecognizeTextRequestRevision1// Control language correction
myTextRecognitionRequest.usesLanguageCorrection = true

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])// Create request
let myTextRecognitionRequest = VNRecognizeTextRequest()// Specify a completion handler
myTextRecognitionRequest.completionHandler = myCompletionHandler// Select the recognition level
myTextRecognitionRequest.recognitionLevel = VNRequestTextRecognitionLevel.accurate// Set the revision
myTextRecognitionRequest.revision = VNRecognizeTextRequestRevision1// Control language correction
myTextRecognitionRequest.usesLanguageCorrection = true

// Create request handler
let myRequestHandler = VNImageRequestHandler(url: fileURL, options: [:])// Create request
let myTextRecognitionRequest = VNRecognizeTextRequest()// Specify a completion handler
myTextRecognitionRequest.completionHandler = myCompletionHandler// Select the recognition level
myTextRecognitionRequest.recognitionLevel = VNRequestTextRecognitionLevel.accurate// Set the revision
myTextRecognitionRequest.revision = VNRecognizeTextRequestRevision1// Control language correction
myTextRecognitionRequest.usesLanguageCorrection = true// Send the request to the request handler
try myRequestHandler.perform([myTextRecognitionRequest])

// The results are in the request upon return
guard let results = request.results as? [VNRecognizedTextObservation] else {return}// Iterate over the line resultsfor visionResult in results {let maximumCandidates = 1
guard let candidate = visionResult.topCandidates(maximumCandidates).first else {
continue}
print(candidate.string)// Get the bounding box
let boundingBox = visionResult.boundingBox// Get the bounding box for a specific word
if let range = candidate.string.range(of: "stress"),
let boxObservation = try? candidate.boundingBox(for: range) {// Draw the box in the viewlet foundTextBox = boxObservation.boundingBox}}

// The results are in the request upon return
guard let results = request.results as? [VNRecognizedTextObservation] else {return}// Iterate over the line resultsfor visionResult in results {let maximumCandidates = 1
guard let candidate = visionResult.topCandidates(maximumCandidates).first else {
continue}
print(candidate.string)// Get the bounding box
let boundingBox = visionResult.boundingBox// Get the bounding box for a specific word
if let range = candidate.string.range(of: "stress"),
let boxObservation = try? candidate.boundingBox(for: range) {// Draw the box in the viewlet foundTextBox = boxObservation.boundingBox}}

// The results are in the request upon return
guard let results = request.results as? [VNRecognizedTextObservation] else {return}// Iterate over the line resultsfor visionResult in results {let maximumCandidates = 1
guard let candidate = visionResult.topCandidates(maximumCandidates).first else {
continue}
print(candidate.string)// Get the bounding box
let boundingBox = visionResult.boundingBox// Get the bounding box for a specific word
if let range = candidate.string.range(of: "stress"),
let boxObservation = try? candidate.boundingBox(for: range) {// Draw the box in the viewlet foundTextBox = boxObservation.boundingBox}}

// The results are in the request upon return
guard let results = request.results as? [VNRecognizedTextObservation] else {return}// Iterate over the line resultsfor visionResult in results {let maximumCandidates = 1
guard let candidate = visionResult.topCandidates(maximumCandidates).first else {
continue}
print(candidate.string)// Get the bounding box
let boundingBox = visionResult.boundingBox// Get the bounding box for a specific word
if let range = candidate.string.range(of: "stress"),
let boxObservation = try? candidate.boundingBox(for: range) {// Draw the box in the viewlet foundTextBox = boxObservation.boundingBox}}

// The results are in the request upon return
guard let results = request.results as? [VNRecognizedTextObservation] else {return}// Iterate over the line resultsfor visionResult in results {let maximumCandidates = 1
guard let candidate = visionResult.topCandidates(maximumCandidates).first else {
continue}
print(candidate.string)// Get the bounding box
let boundingBox = visionResult.boundingBox// Get the bounding box for a specific word
if let range = candidate.string.range(of: "stress"),
let boxObservation = try? candidate.boundingBox(for: range) {// Draw the box in the viewlet foundTextBox = boxObservation.boundingBox}}

// The results are in the request upon return
guard let results = request.results as? [VNRecognizedTextObservation] else {return}// Iterate over the line resultsfor visionResult in results {let maximumCandidates = 1
guard let candidate = visionResult.topCandidates(maximumCandidates).first else {
continue}
print(candidate.string)// Get the bounding box
let boundingBox = visionResult.boundingBox// Get the bounding box for a specific word
if let range = candidate.string.range(of: "stress"),
let boxObservation = try? candidate.boundingBox(for: range) {// Draw the box in the viewlet foundTextBox = boxObservation.boundingBox}}

•Real-Time Text Recognition

When to Use .fast

Use caseWhen to Use .fast

Use case• Read codes/serial numbers just like a barcode reader

When to Use .fast

Use case• Read codes/serial numbers just like a barcode reader• Constrained camera usage

When to Use .fast

Use case• Read codes/serial numbers just like a barcode reader• Constrained camera usage• Interactivity is key

When to Use .fast

Use case• Read codes/serial numbers just like a barcode reader• Constrained camera usage• Interactivity is key
request = VNRecognizeTextRequest(completionHandler: recognizeTextHandler)
request.recognitionLevel = .fast

•

Demo
Phone Number Reader

Demo Recap

Fast path to maintain frame rate
Guide the user on how to use the camera
Use ROI to target the recognition area
Disable language correction
Use our domain knowledge of phone numbers
Build evidence over time to reduce the noise•Document Camera•

The perfect companion

Notes Document Camera Available for Everyone

Notes Document Camera Available for Everyone

Used in Notes, Mail, Files, and Messages

Notes Document Camera Available for Everyone

Used in Notes, Mail, Files, and Messages
Perfect for reading documents

Notes Document Camera Available for Everyone

Used in Notes, Mail, Files, and Messages
Perfect for reading documents
Provides cleaned up “scans“ of your document• Perspective corrected• Evenly lit

import Vision
import VisionKit
let documentCameraViewController = VNDocumentCameraViewController()documentCameraViewController.delegate = self
present(documentCameraViewController, animated: true)…
extension ViewController: VNDocumentCameraViewControllerDelegate {public func documentCameraViewController(_ controller: VNDocumentCameraViewController,
didFinishWith scan: VNDocumentCameraScan) {}}textRecognitionWorkQueue.async {for pageIndex in 0 ..< scan.pageCount {let image = scan.imageOfPage(at: pageIndex)
if let cgImage = image.cgImage {let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
do {try requestHandler.perform(self.requests)} catch {print(error)}}}import Vision
import VisionKit
let documentCameraViewController = VNDocumentCameraViewController()documentCameraViewController.delegate = self
present(documentCameraViewController, animated: true)…
extension ViewController: VNDocumentCameraViewControllerDelegate {public func documentCameraViewController(_ controller: VNDocumentCameraViewController,
didFinishWith scan: VNDocumentCameraScan) {}}textRecognitionWorkQueue.async {for pageIndex in 0 ..< scan.pageCount {let image = scan.imageOfPage(at: pageIndex)
if let cgImage = image.cgImage {let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
do {try requestHandler.perform(self.requests)} catch {print(error)}}}import Vision
import VisionKit
let documentCameraViewController = VNDocumentCameraViewController()documentCameraViewController.delegate = self
present(documentCameraViewController, animated: true)…
extension ViewController: VNDocumentCameraViewControllerDelegate {public func documentCameraViewController(_ controller: VNDocumentCameraViewController,
didFinishWith scan: VNDocumentCameraScan) {}}textRecognitionWorkQueue.async {for pageIndex in 0 ..< scan.pageCount {let image = scan.imageOfPage(at: pageIndex)
if let cgImage = image.cgImage {let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
do {try requestHandler.perform(self.requests)} catch {print(error)}}}import Vision
import VisionKit
let documentCameraViewController = VNDocumentCameraViewController()documentCameraViewController.delegate = self
present(documentCameraViewController, animated: true)…
extension ViewController: VNDocumentCameraViewControllerDelegate {public func documentCameraViewController(_ controller: VNDocumentCameraViewController,
didFinishWith scan: VNDocumentCameraScan) {}}textRecognitionWorkQueue.async {for pageIndex in 0 ..< scan.pageCount {let image = scan.imageOfPage(at: pageIndex)
if let cgImage = image.cgImage {let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
do {try requestHandler.perform(self.requests)} catch {print(error)}}}import Vision
import VisionKit
let documentCameraViewController = VNDocumentCameraViewController()documentCameraViewController.delegate = self
present(documentCameraViewController, animated: true)…
extension ViewController: VNDocumentCameraViewControllerDelegate {public func documentCameraViewController(_ controller: VNDocumentCameraViewController,
didFinishWith scan: VNDocumentCameraScan) {}}textRecognitionWorkQueue.async {for pageIndex in 0 ..< scan.pageCount {let image = scan.imageOfPage(at: pageIndex)
if let cgImage = image.cgImage {let requestHandler = VNImageRequestHandler(cgImage: cgImage, options: [:])
do {try requestHandler.perform(self.requests)} catch {print(error)
}
}
}

•

Best Practices For Text Recognition

Cédric Bray, Vision Team•

Language knowledge•

Performance•

Processing results

Leverage Language Knowledge

Language-based correction• Specify the language• Improve transcription with on-device language models

Leverage Language Knowledge

Custom lexicon• Use custom vocabulary for domain-specific text
• Increase recognition accuracy of confusable or illegible text

// Check supported languages for current revision
let languages = VNRecognizeTextRequest.supportedRecognitionLanguages(
for: VNRequestTextRecognitionLevel.accurate,
revision: VNRecognizeTextRequestRevision1)

// Check supported languages for current revision
let languages = VNRecognizeTextRequest.supportedRecognitionLanguages(
for: VNRequestTextRecognitionLevel.accurate,
revision: VNRecognizeTextRequestRevision1)// Enable language-based correction
myTextRecognitionRequest.usesLanguageCorrection = true

// Check supported languages for current revision
let languages = VNRecognizeTextRequest.supportedRecognitionLanguages(
for: VNRequestTextRecognitionLevel.accurate,
revision: VNRecognizeTextRequestRevision1)// Enable language-based correction
myTextRecognitionRequest.usesLanguageCorrection = true// Specify custom lexicon words
myTextRecognitionRequest.customWords = [ "1337", "h4x0r", "sp34k"]

Tune for Better Performance

Minimum text height• Use when it is acceptable to ignore text below a certain size
• Improves execution time and memory usage by downscaling the image
myTextRecognitionRequest.minimumTextHeight = 0.5 // Text bigger than 50% of the image heightThe Case of Background Tasks

CPU versus GPU/Neural Engine• Configure for CPU only
• Leaves faster resources for higher-priority tasks
myTextRecognitionRequest.usesCPUOnly = true // Run on CPU only

Manage Progress

Progress updates
myTextRecognitionRequest.progressHandler = myProgressHandler

Cancellation
myTextRecognitionRequest.cancel()

•

Demo

•

My First Image Reader

Demo Recap

Pick recognition level that fits desired user interaction
Configure language support for your targeted use case
Leverage progress updates

•Processing Results

Expect Ambiguity in the Input

The case of house numbers
• lol
• lOl
• 101

Dive into the Candidate List

VNRecognizedTextObservation• Process transcription candidates

let maxCount = 3
let candidates = currentObservation.topCandidates(maxCandidateCount: maxCount)

Use Geometry to Map Results

Compare spatial information• Position and scale• Rotation

Use Geometry to Map Results

Compare spatial information• Position and scale• Rotation

Use Parsers to Label Results

Data Detectors• NSDataDetector for types of interest• Addresses, URLs, dates, and phone numbers

Use Parsers to Label Results

Data Detectors• NSDataDetector for types of interest• Addresses, URLs, dates, and phone numbers

Address
Phone number
EmailUse Parsers to Label Results

Domain-specific filters• Your own lexicon• Regular expressions

•

A Business Companion App

Business Companion App

Business Companion App

Document Category
PickerBusiness Companion App

Document Category
PickerDocument Camera

Business Companion App

Document Category
PickerDocument Camera

Text Recognition

Business Companion App

Document Category
PickerDocument Camera

Text Recognition

Results Analysis

Receipt

Business Card

Other

Business Companion App

Document Category
PickerDocument Camera

Text Recognition

Results Analysis

Visualization

Receipt

Table View

Business Card

Contact Card

Other

Text View

•

Demo

•

Business Companion

Demo Recap

Use geometric information to map results
Use Data Detectors, existing language API, or regular expressions
Use your domain knowledge

More Information
developer.apple.com/wwdc19/234

Machine Learning Lab

Friday, 2:00