美文网首页
iOS中关于二维码的识别与生成

iOS中关于二维码的识别与生成

作者: 与佳期 | 来源:发表于2016-07-24 22:19 被阅读583次

原文 : 与佳期的个人博客(gonghonglou.com)

提起二维码 QR Code 想必大家都不再陌生,“扫一扫加好友”、“扫一扫付款”等已是我们日常生活中司空见惯的情形。

以这篇博客来讲述在iOS中关于二维码的识别与生成,会尽可能周全的将二维码识别与生成相关的操作讲述清楚。

关于二维码生成的原理,感兴趣的话各位可以移驾 二维码的生成细节和原理 以做参考。

注:文中识别与生成二维码的方法同样适用于条形码。

先上几张二维码压压惊......

我的二维码

识别二维码

摄像头扫描二维码

iOS7之后苹果推出系统原生API来支持通过扫描获取二维码的功能,较其它 ZBarZXing 等第三方库有明显的性能优势。

首先,你需要弄清楚要用到的以下对象分别起到什么作用:

1、 AVCaptureDevice
捕获数据的物理设备,如:摄像机、麦克风。 开关灯属性torchMode就是由它管理的。

2、 AVCaptureSession
会话,管理输入流、输出流之间的数据传递。

3、 AVCaptureDeviceInput
输入流,从物理设备获取数据。

4、 AVCaptureMetadataOutput
输出流,需要设置输出流代理及所在线程,由代理对象处理输出流数据。

需要说明的是:

  • 需要将输出流添加到会话后,才能指定元数据类型,否则会报错。
  • 将输出流设置在主线程中,其代理方法会执行一次。设置在其他线程的话,代理会执行多次且次数不可控制。
  • 可以通过设置AVFoundationrectOfInterest属性来设定扫描区域,该属性默认取值是CGRectMake(0, 0, 1, 1)即全屏扫描
    xywidthheight的取值范围都是0~1,且原点在屏幕右上角,所以和我们正常理解的CGRect相比xy对调,widthheight对调。
如图:

5、 AVCaptureVideoPreviewLayerCALayer
预览图层,显示相机拍摄到的画面。正因为它是CALayer的子类,为了将它添加到屏幕上,我们需要额外添加一个UIView,在这个UIViewlayer上添加AVCaptureVideoPreviewLayerCALayer,否则它会覆盖住所有的控件(除非你把所有的控件都在添加这个视图之后添加)。

AVCaptureSession 设置开始与结束扫描

// 开始会话
[self.captureSession startRunning];
// 停止会话
[self.captureSession stopRunning];
self.captureSession = nil;
  1. 获取AVCaptureDevice实例
  2. 初始化输入流
  3. 初始化输出流
    3.1.设置代理及所在线程
    3.2设置扫描区域
  4. 创建会话
    4.1添加输入流
    4.2添加输出流
    *3.3指定元数据类型
  5. 创建预览图层
// 1.获取AVCaptureDevice实例
self.captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

// 2.初始化输入流
NSError * error;
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:self.captureDevice error:&error];
if (!input) {
   NSLog(@"%@", [error localizedDescription]);
   return;
}

// 3.初始化输出流
AVCaptureMetadataOutput *captureMetadataOutput = [AVCaptureMetadataOutput new];
// 3.1设置代理及所在线程
[captureMetadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// 3.2设置扫描区域
[captureMetadataOutput setRectOfInterest:[self scanPlace]];

// 4.创建会话
self.captureSession = [AVCaptureSession new];
// 4.1添加输入流
[self.captureSession addInput:input];
if ([self.captureSession canAddInput:input]) {
   [self.captureSession addInput:input];
}
// 4.2添加输出流
if ([self.captureSession canAddOutput:captureMetadataOutput]) {
   [self.captureSession addOutput:captureMetadataOutput];
}

// 3.3指定元数据类型
captureMetadataOutput.metadataObjectTypes = @[AVMetadataObjectTypeUPCECode,
                                             AVMetadataObjectTypeCode39Code,
                                             AVMetadataObjectTypeCode39Mod43Code,
                                             AVMetadataObjectTypeEAN13Code,
                                             AVMetadataObjectTypeEAN8Code,
                                             AVMetadataObjectTypeCode93Code,
                                             AVMetadataObjectTypeCode128Code,
                                             AVMetadataObjectTypePDF417Code,
                                             AVMetadataObjectTypeQRCode, // 二维码
                                             AVMetadataObjectTypeAztecCode];

// 5创建预览图层
self.captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.captureSession];
[self.captureVideoPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
[self.captureVideoPreviewLayer setFrame:self.scanView.layer.bounds];
[self.scanView.layer addSublayer:self.captureVideoPreviewLayer];

AVCaptureMetadataOutput 的代理方法处理输出流,返回扫描结果

#pragma mark - 代理方法处理输出流
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    if (metadataObjects != nil && [metadataObjects count] > 0) {
        AVMetadataMachineReadableCodeObject *metadataObj = [metadataObjects objectAtIndex:0];
        NSString *result = metadataObj.stringValue;
        NSLog(@"result:%@", result);
        if ([[metadataObj type] isEqualToString:AVMetadataObjectTypeQRCode]) {
            NSLog(@"二维码");
        } // ...
        [self closeScanQRCode];
    }
}

开灯

给开关灯按钮调用以下 AVCaptureDevice 方法,轻松实现开灯效果

- (void)turnLight {
    // 判断允许设置
    if ([self.device hasTorch]) {
        [self.device lockForConfiguration:nil];
        if (self.device.torchMode == AVCaptureTorchModeOff) {
            [self.device setTorchMode:AVCaptureTorchModeOn]; // 开灯
        } else if (self.device.torchMode == AVCaptureTorchModeOn) {
            [self.device setTorchMode:AVCaptureTorchModeOff]; // 关灯
        }
        [self.device unlockForConfiguration];
    }
}

总结下来就是:
AVCaptureSession管理从物理设备AVCaptureDevice那里获取的输入流AVCaptureDeviceInput数据
通过输出流AVCaptureMetadataOutput显示到预览图层AVCaptureVideoPreviewLayerCALayer
并且由代理方法-(void)captureOutput:(AVCaptureOutput*)captureOutput didOutputMetadataObjects:(NSArray*)metadataObjects fromConnection:(AVCaptureConnection*)connection;处理捕获到的数据。

相册识别二维码

iOS8之后系统提供的识别二维码图片的方法相当简单

// 读取二维码
UIImage *sourceImage = ...;
CIContext *context = [CIContext contextWithOptions:nil];
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
CIImage *image = [CIImage imageWithCGImage:sourceImage.CGImage];
NSArray *array = [detector featuresInImage:image];
CIQRCodeFeature *feature = [array firstObject];
NSString *result = feature.messageString;
NSLog(@"result:%@", result);

UIImagePickerController 打开系统相册,选择图片识别:

- (void)openPhotoLibrary {
    UIImagePickerController *photoPicker = [UIImagePickerController new];
    photoPicker.delegate = self;
    // 打开的相册类型
    photoPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    [self presentViewController:photoPicker animated:YES completion:NULL];
}

UIImagePickerController 的代理方法识别相册中选中的二维码:

#pragma mark - UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    [self dismissViewControllerAnimated:YES completion:^{
        // 读取二维码
        UIImage *sourceImage = [info objectForKey:UIImagePickerControllerOriginalImage];
        CIContext *context = [CIContext contextWithOptions:nil];
        CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
        CIImage *image = [CIImage imageWithCGImage:sourceImage.CGImage];
        NSArray *array = [detector featuresInImage:image];
        CIQRCodeFeature *feature = [array firstObject];
        NSString *result = feature.messageString;
        NSLog(@"result:%@", result);
    }];
}

生成二维码

使用系统提供的CIFilter可以方便简单的生成二维码

生成二维码方法

- (UIImage *)generateQRCode:(NSString *)code width:(CGFloat)width height:(CGFloat)height {
    CIImage *qrcodeImage;
    NSData *data = [code dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:false];
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    
    [filter setValue:data forKey:@"inputMessage"];
    [filter setValue:@"H" forKey:@"inputCorrectionLevel"];
    qrcodeImage = [filter outputImage];
    
    // 消除模糊
    CGFloat scaleX = width / qrcodeImage.extent.size.width; // extent 返回图片的frame
    CGFloat scaleY = height / qrcodeImage.extent.size.height;
    CIImage *transformedImage = [qrcodeImage imageByApplyingTransform:CGAffineTransformScale(CGAffineTransformIdentity, scaleX, scaleY)];
    
    return [UIImage imageWithCIImage:transformedImage];
}

使用 CIFilter 生成带背景色的二维码

- (UIImage *)gaveColor:(NSString *)code width:(CGFloat)width height:(CGFloat)height {
    NSData *data = [code dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:false];
    CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    [filter setValue:data forKey:@"inputMessage"];
    [filter setValue:@"H" forKey:@"inputCorrectionLevel"];
    
    //上色
    CIFilter *colorFilter = [CIFilter filterWithName:@"CIFalseColor"
                                       keysAndValues:
                             @"inputImage",filter.outputImage,
                             @"inputColor0",[CIColor colorWithCGColor:[UIColor purpleColor].CGColor], // 前景色
                             @"inputColor1",[CIColor colorWithCGColor:[UIColor cyanColor].CGColor], // 背景色
                             nil];
    CIImage *qrcodeImage = colorFilter.outputImage;
    
    // 消除模糊
    CGFloat scaleX = width / qrcodeImage.extent.size.width; // extent 返回图片的frame
    CGFloat scaleY = height / qrcodeImage.extent.size.height;
    CIImage *transformedImage = [qrcodeImage imageByApplyingTransform:CGAffineTransformScale(CGAffineTransformIdentity, scaleX, scaleY)];
    
    return [UIImage imageWithCIImage:transformedImage];
}

通过遍历图片的像素给二维码个性化上色

这里需要指出的是,如果你在下边方法中传入的 image 是通过 CIFilter 方法直接生成的,那么该方法是没法工作的。同理,下一节中保存图片时仍然不能用 CIFilter 方法直接生成的 image,而采用 CGContextRef 获取图片。(参见下一节:保存二维码

// 颜色变化
void ProviderReleaseData (void *info, const void *data, size_t size) {
    free((void *)data);
}
- (UIImage *)imageBlackToTransparent:(UIImage *)image {
    // 分配内存
    const int imageWidth = image.size.width;
    const int imageHeight = image.size.height;
    size_t bytesPerRow = imageWidth * 4;
    uint32_t *rgbImageBuf = (uint32_t *)malloc(bytesPerRow * imageHeight);
    
    // 创建context
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace,kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
    CGContextDrawImage(context, CGRectMake(0, 0, imageWidth, imageHeight), image.CGImage);
    
    // 遍历像素
    int pixelNum = imageWidth * imageHeight;
    uint32_t *pCurPtr = rgbImageBuf;
    for (int i = 0; i < pixelNum; i++, pCurPtr++) {
        if ((*pCurPtr & 0xFFFFFF00) < 0x99999900) {
            // 改变下面的代码,将图片转成想要的颜色
            uint8_t *ptr = (uint8_t *)pCurPtr;
            if (i<pixelNum/2) {
                ptr[3] = 1; //0~255
                ptr[2] = 200;
                ptr[1] = 200;
            } else {
                ptr[3] = 200; //0~255
                ptr[2] = 1;
                ptr[1] = 200;
            }
        } else { // 白色 255,255,255
            uint8_t *ptr = (uint8_t *)pCurPtr;
            ptr[0] = 0;
        }
    }
    
    // 将内存转成image
    CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight,ProviderReleaseData);
    CGImageRef imageRef = CGImageCreate(imageWidth, imageHeight, 8, 32, bytesPerRow, colorSpace,kCGImageAlphaLast | kCGBitmapByteOrder32Little, dataProvider, NULL, true, kCGRenderingIntentDefault);
    CGDataProviderRelease(dataProvider);
    UIImage *resultUIImage = [UIImage imageWithCGImage:imageRef];
    
    // 释放
    CGImageRelease(imageRef);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);
    
    return resultUIImage;
}

生成条码图片

- (UIImage *)generateBarCode:(NSString *)code width:(CGFloat)width height:(CGFloat)height {
    CIImage *barcodeImage;
    NSData *data = [code dataUsingEncoding:NSISOLatin1StringEncoding allowLossyConversion:false];
    CIFilter *filter = [CIFilter filterWithName:@"CICode128BarcodeGenerator"];
    
    [filter setValue:data forKey:@"inputMessage"];
    barcodeImage = [filter outputImage];
    
    // 消除模糊
    CGFloat scaleX = width / barcodeImage.extent.size.width; // extent 返回图片的frame
    CGFloat scaleY = height / barcodeImage.extent.size.height;
    CIImage *transformedImage = [barcodeImage imageByApplyingTransform:CGAffineTransformScale(CGAffineTransformIdentity, scaleX, scaleY)];
    
    return [UIImage imageWithCIImage:transformedImage];
}

保存二维码

// 开启位图上下文
UIGraphicsBeginImageContextWithOptions(self.imageView.bounds.size, NO, 0);
// 获取绘图上下文
CGContextRef context = UIGraphicsGetCurrentContext();
// 将图片渲染的上下文中
[self.imageView.layer renderInContext:context];
// 获取上下文中的图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
// 关闭位图上下文
UIGraphicsEndImageContext();

// 保存图片
UIImageWriteToSavedPhotosAlbum(image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);

UIImageWriteToSavedPhotosAlbum 方法的指定回调,监测保存是否成功

#pragma mark -  指定回调方法
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
    NSString *string;
    if(!error){
        NSLog(@"save success");
    }else{
        NSLog(@"save failed");
    }  
}

看一哈效果图


二维码的识别与生成

ok,就这些吧。
Demo地址:https://github.com/gonghonglou/QRCodeDemo

写到这里估计涉及了二维码操作的大部分内容,期望对诸君有所帮助。

祝大家敲码愉快!

后记

相关文章

网友评论

      本文标题:iOS中关于二维码的识别与生成

      本文链接:https://www.haomeiwen.com/subject/nylgjttx.html