סריקת ברקודים באמצעות למידת מכונה ב-ML

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

רוצה לנסות?

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

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

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

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

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

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

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

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

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

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

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

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

לדוגמה, כדי לסרוק רק קודי Aztec וקודי QR, צריך לבנות אובייקט BarcodeScannerOptions כמו בדוגמה הבאה:

Swift

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

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

  • קוד128
  • קוד39
  • קוד99
  • CodaBar
  • dataMatrix
  • EAN13
  • EAN8
  • ITF
  • קוד qrCode
  • קוד מוצר אוניברסלי (UPCA)
  • קוד מוצר אוניברסלי (UPC)
  • 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:

יוצרים אובייקט 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. עיבוד התמונה

לאחר מכן, צריך להעביר את התמונה לשיטה 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.

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