CIDetectorTypeQRCode fails to scan transparent images

241 Views Asked by At

I am trying to scan QR images that user chooses from disk. I found a strange issue where all libraries I tried failed (CIDetector old port of ZXING or ZBAR). I know that there are ways to add white background (e.g. redraw image or using CIFilter) so the image will get scanned.

What is the correct way of scanning QR codes with transparent background (configure CIContext or CIDetector). (the image below fails to scan on iOS and macOS).

https://en.wikipedia.org/wiki/QR_code#/media/File:QR_code_for_mobile_English_Wikipedia.svg QR CODE PICTURE

- (void)scanImage:(CIImage *)image
{
    NSArray <CIFeature *>*features = [[self QRdetector] featuresInImage:image];
    NSLog(@"Number of features found: %lu", [features count]);
}

- (CIDetector *)QRdetector
{
    CIContext *context = [CIContext contextWithOptions:@{kCIContextWorkingColorSpace : [NSNull null]}]; //no difference using special options or nil as a context

    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy : CIDetectorAccuracyHigh, CIDetectorAspectRatio : @(1)}];
    return detector;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSURL *URL = [[NSBundle mainBundle] URLForResource:@"transparentqrcode" withExtension:@"png"];
    UIImage *image = [UIImage imageWithContentsOfFile:[URL path]];

    CIImage *ciImage = [CIImage imageWithContentsOfURL:URL];

    //CUSTOM CODE TO ADD WHITE BACKGROUND
    CIFilter *filter = [CIFilter filterWithName:@"CISourceAtopCompositing"];
    [filter setDefaults];
    CIColor *whiteColor = [[CIColor alloc] initWithColor:[UIColor whiteColor]];
    CIImage *colorImage = [CIImage imageWithColor:whiteColor];

    colorImage = [colorImage imageByCroppingToRect:ciImage.extent];
    [filter setValue:ciImage forKey:kCIInputImageKey];
    [filter setValue:colorImage forKey:kCIInputBackgroundImageKey];
    CIImage *newImage = [filter valueForKey:kCIOutputImageKey];


    [self scanImage:ciImage];
    return YES;
}
1

There are 1 best solutions below

1
DonMag On

As mentioned in the comments, it appears CIDetector treats the alpha channel as black. Replacing it with white works --- unless the QRCode itself is white with a transparent background.

I haven't done any profiling to see if this would be quicker, but it might be a better option.

- (IBAction)didTap:(id)sender {

    NSURL *URL = [[NSBundle mainBundle] URLForResource:@"transparentqrcode" withExtension:@"png"];

    CIImage *ciImage = [CIImage imageWithContentsOfURL:URL];

    NSArray <CIFeature *>*features = [self getImageFeatures:ciImage];

    // if CIDetector failed to find / process a QRCode in the image,
    // (such as when the image has a transparent background),
    // invert the colors and try again
    if (features.count == 0) {
        CIFilter *filter = [CIFilter filterWithName:@"CIColorInvert"];
        [filter setValue:ciImage forKey:kCIInputImageKey];
        CIImage *newImage = [filter valueForKey:kCIOutputImageKey];
        features = [self getImageFeatures:newImage];
    }

    if (features.count > 0) {
        for (CIQRCodeFeature* qrFeature in features) {
            NSLog(@"QRFeature.messageString : %@ ", qrFeature.messageString);
        }
    } else {
        NSLog(@"Unable to decode image!");
    }

}

- (NSArray <CIFeature *>*)getImageFeatures:(CIImage *)image
{
    NSArray <CIFeature *>*features = [[self QRdetector] featuresInImage:image];
    NSLog(@"Number of features found: %lu", [features count]);
    return features;
}

- (CIDetector *)QRdetector
{
    CIContext *context = [CIContext contextWithOptions:@{kCIContextWorkingColorSpace : [NSNull null]}]; //no difference using special options or nil as a context

    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy : CIDetectorAccuracyHigh, CIDetectorAspectRatio : @(1)}];
    return detector;
}