Tom Shen
Tom Shen

Reputation: 2028

How to read QR code from static image

I know that you can use AVFoundation to scan a QR code using the device's camera. Now here comes the problem, how can I do this from an static UIImage object?

Upvotes: 26

Views: 27499

Answers (7)

Giang
Giang

Reputation: 2739

Objective-C

- (NSString *)readQR {
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{
        CIDetectorAccuracy:CIDetectorAccuracyHigh
    }];
    /// in here you can replace `self` to your image value
    CIImage *ciImage = [[CIImage alloc]initWithImage:self];
    NSArray *features = [detector featuresInImage:ciImage];
    if([features count] == 0) {
        return nil;
    }
    __block NSString *qrString = @"";
    [features enumerateObjectsUsingBlock:^(CIQRCodeFeature  * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        qrString = obj.messageString;
    }];
    return qrString;
}

Upvotes: 0

Aleksey Shevchenko
Aleksey Shevchenko

Reputation: 1251

If you need just a string you can use such code:

class QRToString {

    func string(from image: UIImage) -> String {

        var qrAsString = ""
        guard let detector = CIDetector(ofType: CIDetectorTypeQRCode,
                                        context: nil,
                                        options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]),
            let ciImage = CIImage(image: image),
            let features = detector.features(in: ciImage) as? [CIQRCodeFeature] else {
                return qrAsString
        }

        for feature in features {
            guard let indeedMessageString = feature.messageString else {
                continue
            }
            qrAsString += indeedMessageString
        }

        return qrAsString
    }
}

Upvotes: 5

Sahil Manchanda
Sahil Manchanda

Reputation: 10547

Swift 4 version of @Neimsz's answer

func detectQRCode(_ image: UIImage?) -> [CIFeature]? {
    if let image = image, let ciImage = CIImage.init(image: image){
        var options: [String: Any]
        let context = CIContext()
        options = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
        let qrDetector = CIDetector(ofType: CIDetectorTypeQRCode, context: context, options: options)
        if ciImage.properties.keys.contains((kCGImagePropertyOrientation as String)){
            options = [CIDetectorImageOrientation: ciImage.properties[(kCGImagePropertyOrientation as String)] ?? 1]
        } else {
            options = [CIDetectorImageOrientation: 1]
        }
        let features = qrDetector?.features(in: ciImage, options: options)
        return features

    }
    return nil
}

How to use

if let features = detectQRCode(#imageLiteral(resourceName: "qrcode")), !features.isEmpty{
    for case let row as CIQRCodeFeature in features{
        print(row.messageString ?? "nope")
    }
}

And during the execution this doesn't produce the Finalizing CVPixelBuffer 0x170133e20 while lock count is 1

I used the following QRCode Image (QRCode = https://jingged.com)

(Tested on iPhone 6 simulator with iOS version 11.2)

enter image description here

Output:

2018-03-14 15:31:13.159400+0530 TestProject[25889:233062] [MC] Lazy loading NSBundle MobileCoreServices.framework
2018-03-14 15:31:13.160302+0530 TestProject[25889:233062] [MC] Loaded MobileCoreServices.framework
https://jingged.com

Upvotes: 27

Neimsz
Neimsz

Reputation: 1554

The iOS API provides the CIDetector class from CoreImage framework. CIDetector let you find specific patterns in images, like faces, smiles, eyes, or in our case : QRCodes.

Here is the code to detect a QRCode from an UIImage in Objective-C:

-(NSArray *)detectQRCode:(UIImage *) image
{
    @autoreleasepool {
        NSLog(@"%@ :: %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
        NSCAssert(image != nil, @"**Assertion Error** detectQRCode : image is nil");

        CIImage* ciImage = image.CIImage; // assuming underlying data is a CIImage
        //CIImage* ciImage = [[CIImage alloc] initWithCGImage: image.CGImage];
        // to use if the underlying data is a CGImage

        NSDictionary* options;
        CIContext* context = [CIContext context];
        options = @{ CIDetectorAccuracy : CIDetectorAccuracyHigh }; // Slow but thorough
        //options = @{ CIDetectorAccuracy : CIDetectorAccuracyLow}; // Fast but superficial

        CIDetector* qrDetector = [CIDetector detectorOfType:CIDetectorTypeQRCode
                                                    context:context
                                                    options:options];
        if ([[ciImage properties] valueForKey:(NSString*) kCGImagePropertyOrientation] == nil) {
            options = @{ CIDetectorImageOrientation : @1};
        } else {
            options = @{ CIDetectorImageOrientation : [[ciImage properties] valueForKey:(NSString*) kCGImagePropertyOrientation]};
        }

        NSArray * features = [qrDetector featuresInImage:ciImage
                                                 options:options];

        return features;

    }
}

The returned NSArray* will contain CIFeature* if a QRCode is present and detected. If there was no QRCode, the NSArray* will be nil. If the QRCode decoding fails, the NSArray* will have no element.

To obtain the encoded string :

if (features != nil && features.count > 0) {
    for (CIQRCodeFeature* qrFeature in features) {
        NSLog(@"QRFeature.messageString : %@ ", qrFeature.messageString);
    }
}

As in the answer of @Duncan-C, you can then extract QRCode corners and draw an enclosing bounding box of the QRCode on the image.

Note : Under iOS10.0 beta 6, the call to [qrDetector featuresInImage:ciImage options:options] when using images coming from the cameraSampleBuffer logs this internal warning (it runs smoothly but spam the console with this message, and I could not find a way to get rid of it for now):

Finalizing CVPixelBuffer 0x170133e20 while lock count is 1.

Source :

Apple Dev API Reference - CIDetector

Apple Dev API Programming guide - Face detection

Upvotes: 19

Shai Mishali
Shai Mishali

Reputation: 9402

None of the answers here were extremely straightforward in regards to returning test messages. Made a tiny extension that works well for me:

https://gist.github.com/freak4pc/3f7ae2801dd8b7a068daa957463ac645

extension UIImage {
    func parseQR() -> [String] {
        guard let image = CIImage(image: self) else {
            return []
        }

        let detector = CIDetector(ofType: CIDetectorTypeQRCode,
                                  context: nil,
                                  options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])

        let features = detector?.features(in: image) ?? []

        return features.compactMap { feature in
            return (feature as? CIQRCodeFeature)?.messageString
        }
    }
}

Upvotes: 15

Duncan C
Duncan C

Reputation: 131501

Core Image has the CIDetector class, with the CIDetectorTypeQRCode for detecting QR codes. You can feed a Core Image filter either a still image or a video.

That should meet your needs. See the Xcode docs for more info.

The Github repo iOS8-day-by-day from ShinobiControls includes a project LiveDetection that shows how to use the CIDetectorTypeQRCode both from a video feed and from a still image. It looks like it hasn't been updated for Swift 2.0, and I wasn't able to get it to compile under Xcode 7.2.1, but the function performQRCodeDetection in the project DOES compile. (The compile problems are with code that handles all the horrible type-casting you have to deal with to handle CVPixelBuffers in Swift, which doesn't matter if all you want to do is find QRCodes in static images.)

EDIT:

Here is the key method from that site (in Swift)

func performQRCodeDetection(image: CIImage) -> (outImage: CIImage?, decode: String) {
  var resultImage: CIImage?
  var decode = ""
  if let detector = detector {
    let features = detector.featuresInImage(image)
    for feature in features as! [CIQRCodeFeature] {
      resultImage = drawHighlightOverlayForPoints(image, 
        topLeft: feature.topLeft, 
        topRight: feature.topRight,
        bottomLeft: feature.bottomLeft, 
        bottomRight: feature.bottomRight)
      decode = feature.messageString
    }
  }
  return (resultImage, decode)
}

Upvotes: 8

Badal Shah
Badal Shah

Reputation: 7612

Use Zbar SDK to read QRcode from Static Image.

ZBar-SDK-iOS

Please check this tutorial regarding intigration of Zbar SDK.

ZBar SDK Integration Tutorial

And then try to Scan static image.

Use Zbar scanner class to scan your image.

Here is documentation.

ZBarImageScanner.

Just for Example , How to use Zbar scanner class,

ZBarImageScanner *scandoc = [[ZBarImageScanner alloc]init];
NSInteger resultsnumber = [scandoc scanImage:yourUIImage];
if(resultsnumber > 0){
    ZBarSymbolSet *results = scandoc.results;
    //Here you will get result. 
}

Below link will help you.

scaning-static-uiimage-using-ios-zbar-sdk

Upvotes: 2

Related Questions