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

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

רוצה לנסות?

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

  1. כוללים ב-Podfile את רצפי ה-ML Kit הבאים:
    pod 'GoogleMLKit/BarcodeScanning', '3.2.0'
    
  2. אחרי שמתקינים או מעדכנים את קבוצות ה-Pod של הפרויקט, פותחים את פרויקט 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 יחידות פיקסלים לרוחב.

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

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

  • באפליקציות טיפוסיות מומלץ לספק ערך גבוה יותר היא תמונה ברזולוציה של 1280x720 או 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 ל-process() או ל-results(in:) של BarcodeScanner method:

יצירת אובייקט 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.

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

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

    אין תמיכה בספרת ביקורת (checksum) ב-ITF וב-CODE-39.

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