סריקת ברקודים באמצעות ML Kit ב-iOS

אפשר להשתמש ב-ML Kit כדי לזהות ולפענח ברקודים.

אני רוצה לנסות

לפני שמתחילים

  1. יש לכלול את רצפי ה-ML Kit ב-Podfile:
    pod 'GoogleMLKit/BarcodeScanning', '3.2.0'
    
  2. אחרי שמתקינים או מעדכנים את ה-Pods של הפרויקט, צריך לפתוח את פרויקט ה-Xcode באמצעות .xcworkspace שלו. ערכת ML Kit נתמכת ב-Xcode מגרסה 12.4 ואילך.

הנחיות להזנת תמונה

  • כדי ש-ML Kit יוכל לקרוא ברקודים באופן מדויק, תמונות הקלט חייבות להכיל ברקודים שמיוצגים על ידי כמות מספיקה של נתוני פיקסלים.

    הדרישות הספציפיות לנתוני הפיקסלים תלויות גם בסוג הברקוד וגם בכמות הנתונים שמקודדים בו, מאחר וברקודים רבים תומכים במטען ייעודי (payload) בגודל משתנה. באופן כללי, היחידה המשמעותית הקטנה ביותר של הברקוד צריכה להיות ברוחב של 2 פיקסלים לפחות, ועבור קודים דו-ממדיים, היא צריכה להיות בגובה של 2 פיקסלים.

    לדוגמה, ברקודים מסוג EAN-13 מורכבים מעמודות ומרווחים ברוחב של 1, 2, 3 או 4 יחידות. לכן, בתמונת ברקוד מסוג EAN-13 מומלץ לכלול פסים ורווחים ברוחב של 2, 4, 6 ו-8 פיקסלים לפחות. ברקוד EAN-13 הוא באורך כולל של 95 יחידות, ולכן הוא צריך להיות ברוחב של 190 פיקסלים לפחות.

    כדי ש-ML Kit תוכל לקרוא בצורה אמינה, צריך מידות פיקסלים גדולות יותר לפורמטים של דחיסת נתונים, כמו PDF417. לדוגמה, קוד PDF417 יכול לכלול עד 34 "מילים" ברוחב 17 יחידות בשורה אחת, ורצוי שיהיו ברוחב של 1,156 פיקסלים לפחות.

  • מיקוד באיכות נמוכה של התמונה עלול להשפיע על דיוק הסריקה. אם האפליקציה לא מקבלת תוצאות מקובלות, צריך לבקש מהמשתמש לצלם את התמונה מחדש.

  • באפליקציות טיפוסיות מומלץ לספק תמונה ברזולוציה גבוהה יותר, כמו 1, 280x720 או 1920x1080, כדי שניתן יהיה לסרוק ברקודים ממרחק גדול יותר מהמצלמה.

    עם זאת, באפליקציות שבהן זמן האחזור הוא קריטי, אפשר לשפר את הביצועים על ידי צילום תמונות ברזולוציה נמוכה יותר, אבל כדי לדרוש שהברקוד יהיה חלק מתמונת הקלט. ראו גם טיפים לשיפור הביצועים בזמן אמת.

1. הגדרת סורק הברקוד

אם אתם יודעים אילו פורמטים של ברקוד אתם מצפים לקרוא, תוכלו להגדיר אותו כך שיסרוק רק את הפורמטים האלה, וכך להאיץ את המהירות שלו.

לדוגמה, כדי לסרוק רק קוד אצטקי וקודי QR, יוצרים אובייקט BarcodeScannerOptions כמו בדוגמה הבאה:

Swift

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

יש תמיכה בפורמטים הבאים:

  • code128
  • code39
  • code93
  • codaBar
  • dataMatrix
  • EAN13
  • EAN8
  • ITF
  • qrCode
  • קוד מוצר אוניברסלי (UPCA)
  • קוד מוצר אוניברסלי (UPCE)
  • PDF417
  • Aztec

Objective-C

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

יש תמיכה בפורמטים הבאים:

  • קוד-128 (MLKBarcodeFormatCode128)
  • קוד-39 (MLKBarcodeFormatCode39)
  • קוד-93 (MLKBarcodeFormatCode93)
  • Codabar (MLKBarcodeFormatCodaBar)
  • מטריצת נתונים (MLKBarcodeFormatDataMatrix)
  • EAN-13 (MLKBarcodeFormatEAN13)
  • EAN-8 (MLKBarcodeFormatEAN8)
  • ITF (MLKBarcodeFormatITF)
  • קוד QR (MLKBarcodeFormatQRCode)
  • UPC-A (MLKBarcodeFormatUPCA)
  • UPC-E (MLKBarcodeFormatUPCE)
  • PDF-417 (MLKBarcodeFormatPDF417)
  • קוד אצטקי (MLKBarcodeFormatAztec)

2. הכנת התמונה לקלט

כדי לסרוק ברקודים בתמונה, צריך להעביר את התמונה כ-UIImage או כ-CMSampleBufferRef ל-method process() או results(in:) של BarcodeScanner:

יוצרים אובייקט VisionImage באמצעות UIImage או CMSampleBuffer.

אם משתמשים ב-UIImage, צריך לבצע את השלבים הבאים:

  • יוצרים אובייקט VisionImage עם UIImage. חשוב לוודא שמציינים את .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;
      }
    }
          
  • יצירת אובייקט VisionImage באמצעות האובייקט CMSampleBuffer והכיוון שלו:

    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. עיבוד התמונה

לאחר מכן, מעבירים את התמונה ל-method 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;
   }
 }

טיפים לשיפור הביצועים בזמן אמת

כדי לסרוק ברקודים באפליקציה בזמן אמת, כדאי לפעול לפי ההנחיות הבאות כדי להגיע לקצבי הפריימים הטובים ביותר:

  • לא מצלמים את הקלט ברזולוציה המקורית של המצלמה. במכשירים מסוימים, הקלטת קלט ברזולוציה המקורית יוצרת תמונות גדולות במיוחד (יותר מ-10 מגה-פיקסל), וכתוצאה מכך זמן האחזור קצר מאוד ואין יתרון לדיוק. במקום זאת, צריך לבקש מהמצלמה רק את הגודל הנדרש לסריקת ברקוד, שבדרך כלל לא יותר מ-2 מגה-פיקסל.

    עם זאת, לא מומלץ להפעיל את ההגדרות הקבועות מראש של סשן הצילום — AVCaptureSessionPresetDefault, AVCaptureSessionPresetLow, AVCaptureSessionPresetMedium וכן הלאה) כי הן יכולות למפות לרזולוציות לא מתאימות במכשירים מסוימים. אפשר להשתמש בהגדרות ספציפיות שנקבעו מראש, כמו AVCaptureSessionPreset1280x720.

    אם מהירות הסריקה חשובה, אפשר להנמיך עוד יותר את רזולוציית צילום התמונה. עם זאת, חשוב לזכור את הדרישות המינימליות של גודל הברקוד שפירטנו למעלה.

    אם ברצונך לזהות ברקודים מרצף של פריימים של וידאו בסטרימינג, המזהה עשוי להפיק תוצאות שונות בין פריים לפריים. כדאי להמתין עד שתופיע סדרה ברצף של אותו ערך כדי להיות בטוחים שהתוצאה טובה.

    ספרת הביקורת אינה נתמכת ב-ITF וב-CODE-39.

  • לעיבוד פריימים של וידאו, יש להשתמש ב-API הסינכרוני results(in:) של המזהה. כדאי להפעיל את השיטה הזו מהפונקציה captureOutput(_, didOutput:from:) של AVCaptureVideoDataOutputSampleBufferDelegate כדי לקבל באופן סינכרוני תוצאות מהפריים הנתון של הסרטון. יש לשמור את alwaysDiscardsLateVideoFrames של AVCaptureVideoDataOutput בתור true כדי לווסת את הקריאות לחיישן. אם בזמן שהמזהה פועל פריים וידאו חדש, הוא יוסר.
  • אם משתמשים בפלט של המזהה כדי ליצור שכבת-על של גרפיקה בתמונת הקלט, קודם צריך לקבל את התוצאה מ-ML Kit ואז לעבד את התמונה ואת שכבת-העל בשלב אחד. באופן הזה, מתבצע רינדור על פני המסך פעם אחת בלבד לכל מסגרת קלט מעובדת. כדי לראות דוגמה, אפשר לעיין ב-updatePreviewOverlayViewWithLastFrame בדוגמה למתחילים של ערכת ML.