Vision框架详细解析(八) —— 基于Vision的QR扫描(一)

版本记录

版本号 时间
V1.0 2020.10.15 星期四

前言

iOS 11+macOS 10.13+ 新出了Vision框架,提供了人脸识别、物体检测、物体跟踪等技术,它是基于Core ML的。可以说是人工智能的一部分,接下来几篇我们就详细的解析一下Vision框架。感兴趣的看下面几篇文章。
1. Vision框架详细解析(一) —— 基本概览(一)
2. Vision框架详细解析(二) —— 基于Vision的人脸识别(一)
3. Vision框架详细解析(三) —— 基于Vision的人脸识别(二)
4. Vision框架详细解析(四) —— 在iOS中使用Vision和Metal进行照片堆叠(一)
5. Vision框架详细解析(五) —— 在iOS中使用Vision和Metal进行照片堆叠(二)
6. Vision框架详细解析(六) —— 基于Vision的显著性分析(一)
7. Vision框架详细解析(七) —— 基于Vision的显著性分析(二)

开始

首先看下主要内容:

在此Vision Framework教程中,您将学习如何使用iPhone的相机扫描QR码并自动在Safari中打开编码的URL。内容来自翻译

下面看下写作环境:

Swift 5, iOS 14, Xcode 12

下面就是正文啦。

条码无处不在:产品,广告,电影票上。 在本教程中,您将学习如何使用Vision Framework在iPhone上扫描条形码。 您将使用Vision Framework API,例如VNDetectBarcodesRequest,VNBarcodeObservationVNBarcodeSymbology,并学习如何使用AVCaptureSession进行实时图像采集。

那不是全部! 您还将熟悉:

  • 将相机用作输入设备。
  • 生成和评估图像置信度分数(confidence score)
  • SFSafariViewController中打开网页。

打开入门项目。看一下ViewController.swift。 您将在代码中找到一些辅助方法。

注意:要遵循本教程,您需要运行iOS 11或更高版本的实体iPhone 5s以上的手机。 要在物理设备上运行该应用程序,请确保在Xcode项目设置的Signing and Capabilities部分中设置您的团队。 如果需要设置设备和Xcode项目的帮助,请查看app store tutorial

在开始扫描条形码之前,最好先获得使用相机的许可。


Getting Permission to Use Camera

为了保护用户隐私,Apple要求开发人员在访问其相机之前必须征得用户的许可。 有两个步骤可准备您的应用以请求正确的许可:

  • 1) 通过在Info.plist中添加键和值来说明应用程序使用相机的原因和方式。
  • 2) 使用AVCaptureDevice.requestAccess(for:completionHandler :)提示用户输入您的解释并获取用户输入以获得权限。

入门项目在Info.plist中包含键值对。 您可以在Privacy – Camera Usage Description键下找到它。

要提示用户使用相机的权限,请打开ViewController.swift。 接下来,在checkPermissions()中找到// TODO: Checking permissions。 在方法内添加以下代码:

switch AVCaptureDevice.authorizationStatus(for: .video) {
// 1
case .notDetermined:
  AVCaptureDevice.requestAccess(for: .video) { [self] granted in
    if !granted {
      showPermissionsAlert()
    }
  }

// 2
case .denied, .restricted:
  showPermissionsAlert()

// 3
default: 
  return
}

在上面的代码中,您要求iOS提供您应用的当前相机授权状态。

  • 1) 如果状态尚未确定,则意味着用户尚未进行权限选择,您可以调用AVCaptureDevice.requestAccess(for:completionHandler :)。 它向用户显示一个对话框,要求允许使用相机。 如果用户拒绝您的请求,则您会在iPhone设置中再次显示一条弹出消息,询问权限。
  • 2) 如果用户先前提供了对摄像头的访问权限,或者拒绝了应用程序对摄像头的访问权限,则会显示一条alert,要求更新设置以允许访问。
  • 3) 否则,该用户已经为您的应用授予使用相机的权限,因此您无需执行任何操作。

构建并运行,您将看到以下内容:

拥有摄像头许可后,您可以继续开始采集会话(capturing session)


Starting an AVCaptureSession

现在,您有权访问设备上的相机。 但是,当您关闭手机上的alert时,什么也不会发生! 您现在可以按照以下步骤开始使用iPhone相机来解决此问题:

  • 1) 设置捕获会话(capturing session)的质量。
  • 2) 定义摄像机的输入。
  • 3) 为相机定一个输出。
  • 4) Run一下capturing session

1. Setting Capture Session Quality

Xcode中,导航到ViewController.swift。 找到setupCameraLiveView()并在// TODO: Setup captureSession之后添加以下代码:

captureSession.sessionPreset = .hd1280x720

captureSessionAVCaptureSession的一个实例。 借助AVCaptureSession,您可以管理采集活动并协调数据从输入设备流向采集输出的方式。

在上面的代码中,将采集会话质量设置为HD

接下来,您将定义要使用应用程序的众多iPhone相机中的哪一个,并将选择的内容传递给captureSession

2. Defining a Camera for Input

继续在setupCameraLiveView()中,在// TODO: Add input之后添加此代码:

// 1
let videoDevice = AVCaptureDevice
  .default(.builtInWideAngleCamera, for: .video, position: .back)

// 2
guard
  let device = videoDevice,
  let videoDeviceInput = try? AVCaptureDeviceInput(device: device),
  captureSession.canAddInput(videoDeviceInput) 
  else {
    // 3
    showAlert(
      withTitle: "Cannot Find Camera",
      message: "There seems to be a problem with the camera on your device.")
    return
  }

// 4
captureSession.addInput(videoDeviceInput)

在这里:

  • 1) 查找位于iPhone背面的默认广角相机。
  • 2) 确保您的应用程序可以将相机用作capture session的输入设备。
  • 3) 如果相机有问题,请向用户显示错误消息。
  • 4) 否则,将后置广角相机设置为capture session的输入设备。

准备好capture session后,您现在就可以设置相机输出了。

3. Making an Output

现在您已经从摄像机接收到视频了,您需要一个放置它的地方。 在// TODO: Add output之后,继续您上次中断的地方,添加:

let captureOutput = AVCaptureVideoDataOutput()
// TODO: Set video sample rate
captureSession.addOutput(captureOutput)

在这里,您将capture session的输出设置为AVCaptureVideoDataOutput的实例。 AVCaptureVideoDataOutput是一个capture输出,用于记录视频并提供对视频帧的访问以进行处理。 您稍后将对此添加更多。

最后,是时候运行capture session了。

4. Running the Capture Session

找到// TODO: Run session注释,并在其后直接添加以下代码:

captureSession.startRunning()

这将启动相机会话,并使您能够继续使用Vision框架。

但首先! 许多人忘记了要再次停止相机会话。

为此,请在viewWillDisappear(_ :)中找到// TODO:Stop Session注释,并在其后添加以下内容:

captureSession.stopRunning()

如果您的视图消失了,这将停止capture session,从而释放一些宝贵的内存。

在设备上构建并运行项目。 这样,后置摄像头就会显示在iPhone屏幕上! 如果您的手机正对着计算机,则外观类似于:

有了这些,现在是时候进入Vision框架了。


Vision Framework

苹果公司创建了Vision Framework,使开发人员可以应用计算机视觉算法对输入的图像和视频执行各种任务。 例如,您可以将Vision用于:

  • 人脸和地标检测
  • 文字检测
  • 图像配准
  • 通用特征跟踪

Vision还允许您将自定义Core ML模型用于图像分类或对象检测等任务。

1. Vision and the Camera

Vision Framework在静止图像上运行。 当然,当您在iPhone上使用相机时,图像会平滑移动,就像您期望从视频中看到的那样。 但是,视频是由一系列静止图像接连播放组成的,几乎就像一本翻页书一样flip book

当将相机与Vision Framework配合使用时,Vision会将运动视频分割成其组成图像,并以称为采样率(sample rate)的某个频率处理这些图像之一。

在本教程中,您将使用Vision Framework查找图像中的条形码。Vision框架可以读取17种不同的条形码格式,包括UPCQR码。

在接下来的部分中,您将指示您的应用查找QR码并阅读其内容。是时候开始了!


Using the Vision Framework

要在您的应用中实施Vision Framework,您需要执行以下三个基本步骤:

  • 1) Request - 请求:当您想使用该框架检测某些内容时,可以使用VNRequest的子类来定义请求。
  • 2) Handler - 处理:使用VNImageRequestHandler的子类处理该请求并为任何检测执行图像分析。
  • 3) Observation - 观察:您可以使用VNObservation的子类分析已处理请求的结果。

是时候创建您的第一个Vision请求了。

1. Creating a Vision Request

Vision提供了VNDetectBarcodesRequest来检测图像中的条形码。您现在就实现它。

ViewController.swift中,在文件顶部找到// TODO: Make VNDetectBarcodesRequest variable,并在其后添加以下代码:

lazy var detectBarcodeRequest = VNDetectBarcodesRequest { request, error in
  guard error == nil else {
    self.showAlert(
      withTitle: "Barcode error",
      message: error?.localizedDescription ?? "error")
    return
  }
  self.processClassification(request)
}

在此代码中,您设置了一个VNDetectBarcodesRequest,它将在调用时检测条形码。 当方法认为找到条形码时,会将条形码传递给processClassification(_ :)。 您稍后将定义processClassification(_ :)

但是首先,您需要重新查看视频和采样率。

2. Vision Handler

请记住,视频是图像的集合,并且Vision Framework会以某种频率处理这些图像之一。 要相应地设置视频,请找到setupCameraLiveView()并找到您之前离开的TODO:// TODO: Set video sample rate。 然后,在注释之后和调用addOutput(_ :)之前添加以下代码:

captureOutput.videoSettings = 
  [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32BGRA)]
captureOutput.setSampleBufferDelegate(
  self, 
  queue: DispatchQueue.global(qos: DispatchQoS.QoSClass.default))

在此代码中,您将视频流的像素格式设置为32-bit BGRA。 然后,将self设置为sample buffer的代理。 当缓冲区中有新图像可用时,Vision会从AVCaptureVideoDataOutputSampleBufferDelegate调用适当的代理方法。

由于您已通过self作为代理,因此必须使ViewController遵循AVCaptureVideoDataOutputSampleBufferDelegate代理。 您的类已经遵循了,并定义了一个回调方法:captureOutput(_:didOutput:from :)。 找到此方法并在// TODO: Live Vision之后插入以下内容:

// 1
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { 
  return 
}

// 2
let imageRequestHandler = VNImageRequestHandler(
  cvPixelBuffer: pixelBuffer,
  orientation: .right)

// 3
do {
  try imageRequestHandler.perform([detectBarcodeRequest])
} catch {
  print(error)
}

在这里:

  • 1) 从sample buffer中获取图像,例如从flip book中获取页面。
  • 2) 使用该图像创建一个新的VNImageRequestHandler
  • 3) 使用上面的handler执行detectBarcodeRequest

3. Vision Observation

回想一下Vision Request部分。 在那里,您构建了detectBarcodeRequest,如果认为发现条形码,则会调用processClassification(_ :)。 最后一步,您将填写processClassification(_ :)来分析已处理请求的结果。

processClassification(_ :)中,找到// TODO: Main logic,并在其下面添加以下代码:

// 1
guard let barcodes = request.results else { return }
DispatchQueue.main.async { [self] in
  if captureSession.isRunning {
    view.layer.sublayers?.removeSubrange(1...)

    // 2
    for barcode in barcodes {
      guard
        // TODO: Check for QR Code symbology and confidence score
        let potentialQRCode = barcode as? VNBarcodeObservation 
        else { return }

      // 3
      showAlert(
        withTitle: potentialQRCode.symbology.rawValue,
        // TODO: Check the confidence score
        message: potentialQRCode.payloadStringValue ?? "" )
    }
  }
}

在此代码中,您:

  • 1) 从请求中获取潜在条形码的列表。
  • 2) 遍历可能的条形码以单独分析每个条形码。
  • 3) 如果结果之一恰好是条形码,则显示带有条形码类型和条形码中编码的字符串的alert

构建并再次运行。 这次,将相机对准条形码。

Booooom,您扫描了!

到现在为止还挺好。 但是,如果您可以通过某种方式确切地知道所指向的对象实际上是条形码,该怎么办? 接下来的更多内容...


Adding a Confidence Score

到目前为止,您已经与AVCaptureSessionVision Framework进行了广泛的合作。 但是,您还可以采取更多措施来加强实现。 具体来说,您可以将Vision Observation限制为仅识别QR型条形码,并且可以确保视觉框架确定在图像中找到了QR码。

每当条形码观察者(barcode observer)分析已处理请求的结果时,它都会设置一个称为confidence的属性。 此属性告诉您结果的置信度,通常将其置为[0,1],其中1是最置信度。

processClassification(_ :)内部,找到// TODO: Check for QR Code symbology and confidence score并更换guard

guard 
  let potentialQRCode = barcode as? VNBarcodeObservation 
  else { return }

guard 
  // TODO: Check for QR Code symbology and confidence score
  let potentialQRCode = barcode as? VNBarcodeObservation,
  potentialQRCode.confidence > 0.9
  else { return }

在这里,您可以确保Vision至少有90%的置信度找到了条形码。

现在,使用相同的方法,找到// TODO: Check the confidence score。 消息密钥下面的值当前为potentialQRCode.payloadStringValue ?? ""。 更改为:

String(potentialQRCode.confidence)

现在,您将显示置信度分数,而不是在弹窗中显示条形码的有效载荷。 因为分数是数字,所以您将值合并为字符串,以便它可以显示在弹窗中。

构建并运行。 扫描示例QR码时,您会在弹出的弹窗中看到置信度得分。

做得很好! 如您所见,采样的QR码的置信度得分非常高,这意味着Vision可以肯定这实际上是QR码。

1. Using Barcode Symbology

当前,您的扫描会响应所有类型的条形码。 但是,如果您希望扫描仪仅响应QR码怎么办? 幸运的是,Vision为您提供了解决方案!

在上面的Vision Observation部分中,您使用VNBarcodeObservationconfidence来确定Vision是否能找到条形码。 同样,您可以使用VNBarcodeObservationsymbology属性来检查在Vision Request中找到的符号类型。

再次在processClassification(_ :)中找到// TODO: Check for QR Code symbology and confidence score。 然后在guard内部,在第一个let子句之后添加以下条件:

potentialQRCode.symbology == .QR,

您的guard现在应该如下所示:

guard
  let potentialQRCode = barcode as? VNBarcodeObservation,
  potentialQRCode.symbology == .QR,
  potentialQRCode.confidence > 0.9
  else { return }

有了这个新条件,您已经添加了一个检查以确保条形码的类型为.QR。 如果条形码不是QR码,则忽略该请求。

构建并运行。 扫描采样QR码,看看您的应用程序现在忽略了采样条形码。

最后,您将通过在Safari中打开一个编码的URL来完成这一漂亮的新功能。


Opening Encoded URLs in Safari

条形码扫描仪现已完成。 当然,简单地扫描和读取条形码只是成功的一半。

您的用户将希望对扫描结果进行处理。 毕竟,他们确实允许您使用相机!

QR码通常包含指向有趣网站的URL。 当用户扫描QR码时,他们想转到编码地址处的页面。

在以下各节中,您将致力于做到这一点。

1. Setting Up Safari

首先,您需要添加对在SFSafariViewController中打开链接的支持。

找到observationHandler(payload :),然后在// TODO: Open it in Safari之后添加以下内容:

// 1
guard 
  let payloadString = payload,
  let url = URL(string: payloadString),
  ["http", "https"].contains(url.scheme?.lowercased()) 
  else { return }

// 2
let config = SFSafariViewController.Configuration()
config.entersReaderIfAvailable = true

// 3
let safariVC = SFSafariViewController(url: url, configuration: config)
safariVC.delegate = self
present(safariVC, animated: true)

使用此代码,您:

  • 1) 确保QR码中的编码字符串是有效的URL。
  • 2) 设置SFSafariViewController的新实例。
  • 3) 将Safari打开到QR码中编码的URL

接下来,您将在扫描有效的QR码后着手触发此功能。

2. Opening Safari

要使用此功能打开URL,如果发现一个QR码,则必须告诉processClassification(_ :)使用observationHandler(payload :)

processClassification(_ :)中,使用

showAlert(
  withTitle: potentialQRCode.symbology.rawValue,
  // TODO: Check the confidence score
  message: String(potentialQRCode.confidence)

使用

observationHandler(payload: potentialQRCode.payloadStringValue)

在这里,您无需打开条形码阅读器遇到QR码时的弹窗,而只需打开Safari即可找到QR码中编码的URL。

构建并运行设备,然后将其指向示例QR码。

恭喜你! 您开发了一个功能齐全的应用程序,该应用程序使用iPhone的相机扫描和读取QR码,并在Safari中打开编码的URL!

在本教程中,您学习了如何:

  • 使用Vision Framework
  • 制作并执行VNRequest
  • 如何使用SFSafariViewController
  • 如何限制要扫描的条形码的种类。

该项目只是您使用Vision Framework所做的一切的开始。 如果您想了解更多信息,请查看 Face Detection Tutorial Using the Vision Framework for iOS。 对于更高级的挑战,请查看AR Face Tracking Tutorial for iOS: Getting Started

您也可以访问Vision FrameworkAVCaptureSessionVNDetectBarcodesRequestVNBarcodeObservationVNBarcodeSymbology 的开发人员文档 developer documentation

后记

本篇主要讲述了基于Vision的QR扫描,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352