在 iOS 上使用机器学习套件扫描条形码

您可以使用机器学习套件识别和解码条形码。

  • 请试用示例应用,以查看此 API 的用法示例。

准备工作

  1. 在 Podfile 中添加以下机器学习套件 pod:
    pod 'GoogleMLKit/BarcodeScanning', '3.2.0'
    
  2. 安装或更新项目的 Pod 之后,使用 Xcode 项目的 .xcworkspace 打开该项目。Xcode 12.4 版或更高版本支持机器学习套件。

输入图片指南

  • 为了使机器学习套件准确读取条形码,输入图片必须包含由足够像素数据表示的条形码。

    由于许多条形码都支持可变尺寸的载荷,因此特定的像素数据要求取决于条形码的类型及其编码的数据量。一般来说,条形码的最小有效单元应至少为 2 像素宽;对于二维代码,则应至少为 2 像素高。

    例如,EAN-13 条形码由宽度为 1、2、3 或 4 个单元的柱形和空格组成,因此 EAN-13 条形码图片最好具有宽度至少为 2、4、6 和 8 像素的柱形和空格。由于 EAN-13 条形码的总宽度为 95 个单元,因此该条形码的宽度应至少为 190 像素。

    更密集的格式(例如 PDF417)需要更大的像素尺寸,这样机器学习套件才能可靠地读取。例如,PDF417 代码在一行中最多可包含 34 个 17 单元宽的“单词”,理想状态下宽度至少为 1156 像素。

  • 图片聚焦不良会影响扫描准确性。如果您的应用未获得可接受的结果,请让用户重新拍摄图片。

  • 对于典型应用,建议提供分辨率较高的图片,例如 1280x720 或 1920x1080,这样可在距离相机较远的位置扫描条形码。

    不过,在延迟时间至关重要的应用中,您可以通过以较低的分辨率捕获图片来提高性能,但要求条形码构成输入图片的大部分内容。另请参阅提高实时性能的相关提示

1. 配置条形码扫描器

如果您知道要读取哪些条形码格式,可以将条形码扫描程序配置为仅扫描这些格式,从而提高条形码扫描器的速度。

例如,如需仅扫描 Aztec 码和二维码,请按照以下示例构建 BarcodeScannerOptions 对象:

Swift

let format = .all
let barcodeOptions = BarcodeScannerOptions(formats: format)
  

支持以下格式:

  • 代码 128
  • 代码 39
  • 代码 93
  • codaBar
  • DataMatrix
  • EAN13
  • EAN8
  • 首屏
  • 二维码
  • UPCA
  • UPC
  • PDF417
  • Aztec

Objective-C

MLKBarcodeScannerOptions *options =
  [[MLKBarcodeScannerOptions alloc]
   initWithFormats: MLKBarcodeFormatQRCode | MLKBarcodeFormatAztec];

支持以下格式:

  • Code-128(MLKBarcodeFormatCode128
  • Code-39(MLKBarcodeFormatCode39
  • Code-93(MLKBarcodeFormatCode93
  • Codabar (MLKBarcodeFormatCodaBar)
  • 数据矩阵 (MLKBarcodeFormatDataMatrix)
  • EAN-13(MLKBarcodeFormatEAN13
  • EAN-8(MLKBarcodeFormatEAN8
  • ITF(MLKBarcodeFormatITF
  • 二维码 (MLKBarcodeFormatQRCode)
  • UPC-A(MLKBarcodeFormatUPCA
  • UPC-E(MLKBarcodeFormatUPCE
  • PDF-417(MLKBarcodeFormatPDF417
  • Aztec 代码 (MLKBarcodeFormatAztec)

2. 准备输入图片

如需扫描图片中的条形码,请将图片作为 UIImageCMSampleBufferRef 传递给 BarcodeScannerprocess()results(in:) 方法:

使用 UIImageCMSampleBuffer 创建 VisionImage 对象。

如果您使用的是 UIImage,请按以下步骤操作:

  • 使用 UIImage 创建一个 VisionImage 对象。请务必指定正确的 .orientation

    Swift

    let image = VisionImage(image: UIImage)
    visionImage.orientation = image.imageOrientation

    Objective-C

    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage.orientation = image.imageOrientation;

如果您使用的是 CMSampleBuffer,请按以下步骤操作:

  • 指定 CMSampleBuffer 中包含的图片数据的方向。

    要获取图片方向,请执行以下操作:

    Swift

    func imageOrientation(
      deviceOrientation: UIDeviceOrientation,
      cameraPosition: AVCaptureDevice.Position
    ) -> UIImage.Orientation {
      switch deviceOrientation {
      case .portrait:
        return cameraPosition == .front ? .leftMirrored : .right
      case .landscapeLeft:
        return cameraPosition == .front ? .downMirrored : .up
      case .portraitUpsideDown:
        return cameraPosition == .front ? .rightMirrored : .left
      case .landscapeRight:
        return cameraPosition == .front ? .upMirrored : .down
      case .faceDown, .faceUp, .unknown:
        return .up
      }
    }
          

    Objective-C

    - (UIImageOrientation)
      imageOrientationFromDeviceOrientation:(UIDeviceOrientation)deviceOrientation
                             cameraPosition:(AVCaptureDevicePosition)cameraPosition {
      switch (deviceOrientation) {
        case UIDeviceOrientationPortrait:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationLeftMirrored
                                                                : UIImageOrientationRight;
    
        case UIDeviceOrientationLandscapeLeft:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationDownMirrored
                                                                : UIImageOrientationUp;
        case UIDeviceOrientationPortraitUpsideDown:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationRightMirrored
                                                                : UIImageOrientationLeft;
        case UIDeviceOrientationLandscapeRight:
          return cameraPosition == AVCaptureDevicePositionFront ? UIImageOrientationUpMirrored
                                                                : UIImageOrientationDown;
        case UIDeviceOrientationUnknown:
        case UIDeviceOrientationFaceUp:
        case UIDeviceOrientationFaceDown:
          return UIImageOrientationUp;
      }
    }
          
  • 使用 CMSampleBuffer 对象和屏幕方向创建 VisionImage 对象:

    Swift

    let image = VisionImage(buffer: sampleBuffer)
    image.orientation = imageOrientation(
      deviceOrientation: UIDevice.current.orientation,
      cameraPosition: cameraPosition)

    Objective-C

     MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
     image.orientation =
       [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                    cameraPosition:cameraPosition];

3. 获取 BarcodeScanner 的实例

获取 BarcodeScanner 的一个实例:

Swift

let barcodeScanner = BarcodeScanner.barcodeScanner()
// Or, to change the default settings:
// let barcodeScanner = BarcodeScanner.barcodeScanner(options: barcodeOptions)

Objective-C

MLKBarcodeScanner *barcodeScanner = [MLKBarcodeScanner barcodeScanner];
// Or, to change the default settings:
// MLKBarcodeScanner *barcodeScanner =
//     [MLKBarcodeScanner barcodeScannerWithOptions:options];

4. 处理图片

然后,将图片传递给 process() 方法:

Swift

barcodeScanner.process(visionImage) { features, error in
  guard error == nil, let features = features, !features.isEmpty else {
    // Error handling
    return
  }
  // Recognized barcodes
}

Objective-C

[barcodeScanner processImage:image
                  completion:^(NSArray<MLKBarcode *> *_Nullable barcodes,
                               NSError *_Nullable error) {
  if (error != nil) {
    // Error handling
    return;
  }
  if (barcodes.count > 0) {
    // Recognized barcodes
  }
}];

5. 从条形码中获取信息

如果条形码扫描操作成功,扫描器会返回一个 Barcode 对象数组。每个 Barcode 对象代表一个在图片中检测到的条形码。对于每个条形码,您可以获取它在输入图片中的绑定坐标以及由条形码编码的原始数据。此外,如果条形码扫描器能够确定条形码编码的数据类型,您还可以获取包含已解析数据的对象。

例如:

Swift

for barcode in barcodes {
  let corners = barcode.cornerPoints

  let displayValue = barcode.displayValue
  let rawValue = barcode.rawValue

  let valueType = barcode.valueType
  switch valueType {
  case .wiFi:
    let ssid = barcode.wifi?.ssid
    let password = barcode.wifi?.password
    let encryptionType = barcode.wifi?.type
  case .URL:
    let title = barcode.url!.title
    let url = barcode.url!.url
  default:
    // See API reference for all supported value types
  }
}

Objective-C

for (MLKBarcode *barcode in barcodes) {
   NSArray *corners = barcode.cornerPoints;

   NSString *displayValue = barcode.displayValue;
   NSString *rawValue = barcode.rawValue;

   MLKBarcodeValueType valueType = barcode.valueType;
   switch (valueType) {
     case MLKBarcodeValueTypeWiFi:
       ssid = barcode.wifi.ssid;
       password = barcode.wifi.password;
       encryptionType = barcode.wifi.type;
       break;
     case MLKBarcodeValueTypeURL:
       url = barcode.URL.url;
       title = barcode.URL.title;
       break;
     // ...
     default:
       break;
   }
 }

提高实时性能的相关提示

如果要在实时应用中扫描条形码,请遵循以下准则以实现最佳帧速率:

  • 请勿以相机的原始分辨率捕获输入内容。在某些设备上,以原生分辨率捕获输入会生成非常大(超过 1000 万像素)的图片,导致延迟时间极短,对准确性没有任何影响。而是应仅从相机中请求条形码扫描所需的尺寸,通常不超过 200 万像素。

    不过,不推荐使用指定的捕获会话预设(AVCaptureSessionPresetDefaultAVCaptureSessionPresetLowAVCaptureSessionPresetMedium 等),因为它们可以映射到某些设备上不合适的分辨率。请改用特定预设,例如 AVCaptureSessionPreset1280x720

    如果扫描速度很重要,您可以进一步降低图片拍摄分辨率。不过,请牢记上面列出的最低条形码大小要求。

    如果您尝试识别一系列流式视频帧中的条形码,则识别器可能会在不同帧之间产生不同的结果。您应等到获得连续的相同值系列后再确认是否会返回良好的结果。

    ITF 和 CODE-39 不支持校验和数字。

  • 如需处理视频帧,请使用检测器的 results(in:) 同步 API。从 AVCaptureVideoDataOutputSampleBufferDelegatecaptureOutput(_, didOutput:from:) 函数中调用此方法,以同步获取给定视频帧的结果。将 AVCaptureVideoDataOutputalwaysDiscardsLateVideoFrames 保留为 true,以限制对检测器的调用。如果在检测器运行时有新的视频帧可用,将被丢弃。
  • 如果您使用检测器的输出在输入图片上叠加图形,请先从机器学习套件获取结果,然后在一个步骤中完成图片的渲染和叠加。采用这一方法,每个已处理的输入帧只需在显示表面呈现一次。如需查看示例,请参阅机器学习套件快速入门示例中的 updatePreviewOverlayViewWithLastFrame