8

I've been implementing a test of the new Vision framework which Apple introduced in WWDC2017. I am specifically looking at the barcode detection - I've been able to get after scanning the image from Camera/Gallery that it's a barcode image or not. However, I can't see what the actual barcode value or the payload data when looking at the barcodeDescriptor. There appears to be nothing exposed on the https://developer.apple.com/documentation/coreimage/cibarcodedescriptor page to identify any of the properties.

I am getting these errors:

  • Cannot connect to remote service: Error Domain=NSCocoaErrorDomain Code=4097 "connection to service named
    com.apple.BarcodeSupport.BarcodeNotificationService"
  • libMobileGestalt MobileGestalt.c:555: no access to InverseDeviceID (see problem/11744455>)
  • connection to service named com.apple.BarcodeSupport.BarcodeNotificationService Error
    Domain=NSCocoaErrorDomain Code=4097

Is there any way to access the barcode value from the VNBarcodeObservation? Any help would be greatly appreciated. Thank you! Here is the code I am using:

@IBAction func chooseImage(_ sender: Any) {
        imagePicker.allowsEditing = true
        imagePicker.sourceType = .photoLibrary

        present(imagePicker, animated: true, completion: nil)
    }

    @IBAction func takePicture(_ sender: Any) {
        if(UIImagePickerController .isSourceTypeAvailable(UIImagePickerControllerSourceType.camera)){
            imagePicker.sourceType = UIImagePickerControllerSourceType.camera
            self .present(imagePicker, animated: true, completion: nil)
        }
        else{
            let alert = UIAlertController(title: "Warning", message: "Camera not available", preferredStyle: UIAlertControllerStyle.alert)
            alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.default, handler: nil))
            self.present(alert, animated: true, completion: nil)
        }
    }

    //PickerView Delegate Methods

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

        imagePicker .dismiss(animated: true, completion: nil)
        classificationLabel.text = "Analyzing Image…"

        guard let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage
            else { fatalError("no image from image picker") }
        guard let ciImage = CIImage(image: pickedImage)
            else { fatalError("can't create CIImage from UIImage") }

        imageView.image = pickedImage
        inputImage = ciImage

        // Run the rectangle detector, which upon completion runs the ML classifier.
        let handler = VNImageRequestHandler(ciImage: ciImage, options: [.properties : ""])
        DispatchQueue.global(qos: .userInteractive).async {
            do {
                try handler.perform([self.barcodeRequest])
            } catch {
                print(error)
            }
        }
    }

    func imagePickerControllerDidCancel(_ picker: UIImagePickerController){
        picker .dismiss(animated: true, completion: nil)
        print("picker cancel.")
    }

    lazy var barcodeRequest: VNDetectBarcodesRequest = {
        return VNDetectBarcodesRequest(completionHandler: self.handleBarcodes)
    }()

    func handleBarcodes(request: VNRequest, error: Error?) {
        guard let observations = request.results as? [VNBarcodeObservation]
            else { fatalError("unexpected result type from VNBarcodeRequest") }
        guard observations.first != nil else {
            DispatchQueue.main.async {
                self.classificationLabel.text = "No Barcode detected."
            }
            return
        }

        // Loop through the found results
        for result in request.results! {

            // Cast the result to a barcode-observation
            if let barcode = result as? VNBarcodeObservation {

                // Print barcode-values
                print("Symbology: \(barcode.symbology.rawValue)")

                if let desc = barcode.barcodeDescriptor as? CIQRCodeDescriptor {
                    let content = String(data: desc.errorCorrectedPayload, encoding: .utf8)

                    // FIXME: This currently returns nil. I did not find any docs on how to encode the data properly so far.
                    print("Payload: \(String(describing: content))\n")
                    print("Error-Correction-Level: \(desc.errorCorrectedPayload)\n")
                    print("Symbol-Version: \(desc.symbolVersion)\n")
                }
            }
        }
    }
nathan
  • 9,329
  • 4
  • 37
  • 51
Hitesh Arora
  • 81
  • 1
  • 4
  • Check out [Session #510 - Advances in Core Image: Filters, Metal, Vision, and More - WWDC 2017](https://developer.apple.com/videos/play/wwdc2017/510/) at 35min they start talking about CIBarcodeDescriptor and errorCorrectedPayload. Sadly I wasn't able to read the message in the payload either. – nathan Jun 22 '17 at 16:48
  • Yeah, I am able to fix these errors by adding "OS_ACTIVITY_MODE" as disable in "Edit Scheme"-> Run-> Arguments, but I am not able to extract data from the payload. I am able to scan the barcode and then make an image out of it what they showed in the video but I am still stuck to extract information string from the data type. – Hitesh Arora Jun 22 '17 at 19:33
  • I have used your code as a basis for my own tests and I have had *some* luck (iOS 11 beta 3), but the results were puzzling. The `errorCorrectedPayload` contains data that was *sometimes* readable. https://stackoverflow.com/questions/45037418/zlib-stream-in-ios-11-vision-framework-barcodes-sometimes-decompress-sometimes – oelna Jul 11 '17 at 14:31
  • Update from Apple's support team: `Engineering has determined that this issue behaves as intended based on the following information: You’ll need to write your own parser.` – nathan Jul 27 '17 at 20:59

3 Answers3

9

Apparently, in the iOS 11 beta 5 Apple introduced new payloadStringValue property of VNBarcodeObservation. Now you can read info from QR-code with no problems

if let payload = barcodeObservation.payloadStringValue {
    print("payload is \(payload)")
}
swasta
  • 846
  • 8
  • 25
2

If Apple is not going to provide a library for this, something like the following will work:

extension CIQRCodeDescriptor {
    var bytes: Data? {
        return errorCorrectedPayload.withUnsafeBytes { (pointer: UnsafePointer<UInt8>) in
            var cursor = pointer

            let representation = (cursor.pointee >> 4) & 0x0f
            guard representation == 4 /* byte encoding */ else { return nil }

            var count = (cursor.pointee << 4) & 0xf0
            cursor = cursor.successor()
            count |= (cursor.pointee >> 4) & 0x0f

            var out = Data(count: Int(count))
            guard count > 0 else { return out }

            var prev = (cursor.pointee << 4) & 0xf0
            for i in 2...errorCorrectedPayload.count {
                if (i - 2) == count { break }

                let cursor = pointer.advanced(by: Int(i))
                let byte = cursor.pointee
                let current = prev | ((byte >> 4) & 0x0f)
                out[i - 2] = current
                prev = (cursor.pointee << 4) & 0xf0
            }
            return out
        }
    }
}

And then

String(data: descriptor.bytes!, encoding: .utf8 /* or whatever */)
Nick Kallen
  • 97
  • 1
  • 4
  • Could you explain/link the guarding of the `representation` variable? I'm attempting to parse the `CIQRCodeDescriptor` and my the value of `representation` (that is, 2) is causing my `descriptor.bytes` to be nil. – raoul Aug 01 '17 at 03:19
  • 1
    Your representation is 'alphanumeric' so you'll need a different algorithm. http://www.thonky.com/qr-code-tutorial/alphanumeric-mode-encoding – Nick Kallen Aug 01 '17 at 05:04
0

If you want to get the raw Data from the VNBarcodeObservation directly without it having to conform to some string encoding you can strip of the first 2 and 1/2 bytes like this, and get actual data without the QR code header.

            guard let barcode = barcodeObservation.barcodeDescriptor as? CIQRCodeDescriptor else { return }
            let errorCorrectedPayload = barcode.errorCorrectedPayload
            let payloadData = Data(bytes: zip(errorCorrectedPayload.advanced(by: 2),
                                              errorCorrectedPayload.advanced(by: 3)).map { (byte1, byte2) in
                return byte1 << 4 | byte2 >> 4
            })
junkgui
  • 179
  • 2
  • 7