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

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

  1. כוללים ב-Podfile את רצפי ה-ML Kit הבאים:
    pod 'GoogleMLKit/BarcodeScanning', '15.5.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, כמו בדוגמה הבאה:

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

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

  • code128
  • code39
  • code93
  • codaBar
  • dataMatrix
  • EAN13
  • EAN8
  • ITF
  • qrCode
  • UPCA
  • UPCE
  • PDF417
  • Aztec
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 נכון.
    let image = VisionImage(image: UIImage)
    visionImage
    .orientation = image.imageOrientation
    MLKVisionImage *visionImage = [[MLKVisionImage alloc] initWithImage:image];
    visionImage
    .orientation = image.imageOrientation;

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

  • לציין את הכיוון של נתוני התמונה שכלולים CMSampleBuffer

    כדי לקבל את הכיוון של התמונה:

    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
     
    }
    }
         
    - (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:
    let image = VisionImage(buffer: sampleBuffer)
    image
    .orientation = imageOrientation(
      deviceOrientation
    : UIDevice.current.orientation,
      cameraPosition
    : cameraPosition)
     MLKVisionImage *image = [[MLKVisionImage alloc] initWithBuffer:sampleBuffer];
     image
    .orientation =
       
    [self imageOrientationFromDeviceOrientation:UIDevice.currentDevice.orientation
                                    cameraPosition
    :cameraPosition];

3. קבלת מופע של BarcodeScanner

מקבלים מופע של BarcodeScanner:
let barcodeScanner = BarcodeScanner.barcodeScanner()
// Or, to change the default settings:
// let barcodeScanner = BarcodeScanner.barcodeScanner(options: barcodeOptions)
MLKBarcodeScanner *barcodeScanner = [MLKBarcodeScanner barcodeScanner];
// Or, to change the default settings:
// MLKBarcodeScanner *barcodeScanner =
//     [MLKBarcodeScanner barcodeScannerWithOptions:options];

4. עיבוד התמונה

לאחר מכן, מעבירים את התמונה ל-method process():
barcodeScanner.process(visionImage) { features, error in
  guard error
== nil, let features = features, !features.isEmpty else {
   
// Error handling
    return
 
}
 
// Recognized barcodes
}
[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 מייצג הברקוד שזוהה בתמונה. לכל ברקוד אפשר לראות וגם את הקואורדינטות התוחמות בתמונת הקלט, וגם את הנתונים הגולמיים המקודדים על ידי ברקוד. כמו כן, אם סורק הברקוד הצליח לזהות את סוג הנתונים שמקודדים לפי הברקוד, אפשר לקבל אובייקט שמכיל נתונים שנותחו.

לדוגמה:

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
  }
}
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.