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

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

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

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

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

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

    הדרישות הספציפיות לגבי נתוני פיקסל תלויות בסוג הברקוד ובכמות הנתונים שמקודדים בו, כי ברקודים רבים תומכים במטען ייעודי של משתנים. באופן כללי, היחידה המשמעותית ביותר של הברקוד צריכה להיות ברוחב של 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. הגדרת סורק הברקוד

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

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

Swift

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

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

  • קוד128
  • קוד 39
  • קוד993
  • CodaBar
  • DataMatrix
  • EAN13
  • EAN8
  • ITF
  • קוד QR
  • קוד מוצר אוניברסלי (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 שיטה BarcodeScanner's process() או results(in:):

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

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

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

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

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