欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

iOS二维码扫描/识别

程序员文章站 2022-05-01 17:52:48
一直以为二维码功能比较简单, 从来没有放在心上过, 由于公司项目需要这个功能, 也算是第一次真正去做二维码的东西. 从网上看别人的博客很多都是写的比较碎片, 没有一个能做的比较完整...

一直以为二维码功能比较简单, 从来没有放在心上过, 由于公司项目需要这个功能, 也算是第一次真正去做二维码的东西. 从网上看别人的博客很多都是写的比较碎片, 没有一个能做的比较完整的, 今天我的这个模块做完, 索性总结一下吧, 希望更多的人少走弯路

网上很多二维码的识别的框架其中以ZXing和ZBar最为著名, 不过苹果官方的API也开始支持了二维码扫描和识别! 下面说说我走的弯路.

首先是使用苹果官方的API做了相机扫描功能, 经过多次测试, 识别精准度特别高, 而且识别速度相当快,

然后就想着用官方API做相册照片二维码识别, 于是兴高采烈的就去写, 当写完之后做测试的时候发现这个玩意儿竟然不支持iOS7.0, 可是我们的项目要支持到7.0, 于是乎没办法只能全部删了, 然后通过各种搜索, 了解了zxing和zbar, 看一篇博客说zxing的识别效率高, 于是就各种翻文档,查资料, 最后终于搞定, 然后测试, 一切OK, 本以为就这么完了, 结果却不是想象的那么简单, 当识别手动拍摄时的二维码时, 识别率低到无法忍受, 没办法只能换了zbar, 经过各种测试, zbar的识别精准度和识别率都非常高!

总结就是, 二维码扫描用系统API, 相册二维码识别用ZBar. 以下是代码部分, 代码中三种识别二维码的方法都有些, 感兴趣的可以自己测测, 顺便说一下, 系统的API相册二维码识别率也不高. 以下就是我的二维码扫描的这套代码

 

//
//  KHBQRCodeViewController.m
//  KHBQRCode
//
//  Created by 王俊岭 on 16/7/19.
//  Copyright © 2016年 王俊岭. All rights reserved.
//

#import "KHBQRCodeViewController.h"
#import 

#import "ZXingObjC.h"
#import "ZBarSDK.h"

#import "KHBButton.h"

#import "KHBWebViewController.h"
#import "KHBQRCodeDetailViewController.h"

//扫描框宽度占比
#define khb_SCANNERWIDTH_RATE 520/750
//扫描框中心Y的偏移量
#define khb_SCANNERCENTER_OFFSET screenHeight*100/667

@interface KHBQRCodeViewController ()vcapturemetadataoutputobjectsdelegate, uinavigationcontrollerdelegate="">

@property (nonatomic,strong) AVCaptureMetadataOutput *output;
@property (nonatomic,strong) AVCaptureSession *session;
@property (nonatomic,strong) AVCaptureVideoPreviewLayer *prelayer;

@property (nonatomic, weak) UIView *scannerLine;
@property (nonatomic, weak) UIImageView *scannerView;

@end

@implementation KHBQRCodeViewController

/**
 *  viewDidLoad
 */
- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"扫一扫";
    [self instanceDevice];
}
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.session startRunning];
}

/**配置相机属性*/
- (void)instanceDevice{
    //获取摄像设备
    NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput *input = [[AVCaptureDeviceInput alloc] initWithDevice:[devices firstObject] error:nil];
    //创建输出流
    AVCaptureMetadataOutput * output = [[AVCaptureMetadataOutput alloc]init];
    //获取扫描范围并设置
    double scannerViewWidth = screenWidth * khb_SCANNERWIDTH_RATE;
    double scannerViewHeight = scannerViewWidth;
    double scannerViewX = (screenWidth - scannerViewWidth)/2;
    double scannerViewY = (screenHeight- 64 - scannerViewWidth)/2 - khb_SCANNERCENTER_OFFSET;
    output.rectOfInterest = CGRectMake(scannerViewY/screenHeight,
                                       scannerViewX/screenWidth,
                                       scannerViewHeight/screenHeight,
                                       scannerViewWidth/screenWidth);
    //设置代理 在主线程里刷新
    [output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
    
    //初始化链接对象
    self.session = [[AVCaptureSession alloc]init];
    //高质量采集率
    [self.session setSessionPreset:AVCaptureSessionPresetHigh];
    if ([self.session canAddInput:input]) {
       
        [self.session addInput:input];
    }
    if ([self.session canAddOutput:output]) {
        [self.session addOutput:output];
        //设置扫码支持的编码格式(如下设置条形码和二维码兼容)
        NSMutableArray *a = [[NSMutableArray alloc] init];
        if ([output.availableMetadataObjectTypes containsObject:AVMetadataObjectTypeQRCode]) {
            [a addObject:AVMetadataObjectTypeQRCode];
        }
        if ([output.availableMetadataObjectTypes containsObject:AVMetadataObjectTypeEAN13Code]) {
            [a addObject:AVMetadataObjectTypeEAN13Code];
        }
        if ([output.availableMetadataObjectTypes containsObject:AVMetadataObjectTypeEAN8Code]) {
            [a addObject:AVMetadataObjectTypeEAN8Code];
        }
        if ([output.availableMetadataObjectTypes containsObject:AVMetadataObjectTypeCode128Code]) {
            [a addObject:AVMetadataObjectTypeCode128Code];
        }
        output.metadataObjectTypes=a;
    }
    AVCaptureVideoPreviewLayer * layer = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
    self.prelayer = layer;
    layer.videoGravity=AVLayerVideoGravityResizeAspectFill;
    layer.frame=self.view.layer.bounds;
    [self.view.layer insertSublayer:layer atIndex:0];
    [self setOverlayPickerView];
    [self.session addObserver:self forKeyPath:@"running" options:NSKeyValueObservingOptionNew context:nil];
    //开始捕获
    [self.session startRunning];
}

/**
 *  监听扫码状态-修改扫描动画
 *
 *  @param keyPath
 *  @param object
 *  @param change
 *  @param context
 */
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context{
    if ([object isKindOfClass:[AVCaptureSession class]]) {
        BOOL isRunning = ((AVCaptureSession *)object).isRunning;
        if (isRunning) {
            [self addAnimation];
        }else{
            [self removeAnimation];
        }
    }
}

/**
 *
 *  获取扫码结果
 *
 *  @param captureOutput
 *  @param metadataObjects
 *  @param connection
 */
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    if (metadataObjects.count>0) {
        [self.session stopRunning];
        AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex :0];
        //输出扫描字符串
        NSString *qrCodeStr = metadataObject.stringValue;
        [self analyzeQRCodeStr:qrCodeStr];
       //        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:data preferredStyle:(UIAlertControllerStyleAlert)];
//        UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction *action) {
//            [self alertAction];
//        }];
//        [alertController addAction:okAction];
//        [self presentViewController:alertController animated:YES completion:nil];
    }
}
/**判断字符串类型做跳转*/
- (void)analyzeQRCodeStr:(NSString *)qrCodeStr {
    if ([qrCodeStr containsString:@"https://"] || [qrCodeStr containsString:@"https://"]) {
        
        if ([qrCodeStr containsString:@"https://itunes.apple.com"]) {
            [[UIApplication sharedApplication] openURL:[NSURL URLWithString:qrCodeStr]];
            [self.session startRunning];
        } else {
            KHBWebViewController *webVC = [KHBWebViewController new];
            webVC.url = qrCodeStr;
            [self.navigationController pushViewController:webVC animated:YES];
        }
        
    } else {
        KHBQRCodeDetailViewController *detailVC = [[KHBQRCodeDetailViewController alloc] init];
        detailVC.QRCodeDetail = qrCodeStr;
        [self.navigationController pushViewController:detailVC animated:YES];
    }

}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

/**
 *
 *  创建扫码页面
 */
- (void)setOverlayPickerView {
    //中间扫描框
    UIImageView *scannerView = [[UIImageView alloc] init];
    [self.view addSubview:scannerView];
    self.scannerView = scannerView;
    scannerView.image = [UIImage imageNamed:@"扫描框.png"];
    scannerView.contentMode = UIViewContentModeScaleAspectFit;
    scannerView.backgroundColor = [UIColor clearColor];
    [scannerView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(self.view.mas_centerX);
        make.centerY.equalTo(self.view.mas_centerY).offset(-khb_SCANNERCENTER_OFFSET);
        make.width.equalTo(@(screenWidth * khb_SCANNERWIDTH_RATE));
        make.height.equalTo(@(screenWidth * khb_SCANNERWIDTH_RATE));
    }];

    //左侧遮盖层
    UIImageView *leftView = [[UIImageView alloc] init];
    [self.view addSubview:leftView];
    leftView.alpha = 0.5;
    leftView.backgroundColor = [UIColor blackColor];
    [leftView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view.mas_left);
        make.right.equalTo(scannerView.mas_left);
        make.top.bottom.equalTo(self.view);
    }];
    
    //右侧遮盖层
    UIImageView *rightView = [[UIImageView alloc] init];
    [self.view addSubview:rightView];
    rightView.alpha = 0.5;
    rightView.backgroundColor = [UIColor blackColor];
    [rightView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(scannerView.mas_right);
        make.right.equalTo(self.view.mas_right);
        make.top.bottom.equalTo(self.view);
    }];
    //顶部遮盖层
    UIImageView* upView = [[UIImageView alloc] init];
    [self.view addSubview:upView];
    upView.alpha = 0.5;
    upView.backgroundColor = [UIColor blackColor];
    [upView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(leftView.mas_right);
        make.right.equalTo(rightView.mas_left);
        make.top.equalTo(self.view.mas_top);
        make.bottom.equalTo(scannerView.mas_top);
    }];
    
    //底部遮盖层
    UIImageView * downView = [[UIImageView alloc] init];
    [self.view addSubview:downView];
    downView.alpha = 0.5;
    downView.backgroundColor = [UIColor blackColor];
    [downView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(leftView.mas_right);
        make.right.equalTo(rightView.mas_left);
        make.top.equalTo(scannerView.mas_bottom);
        make.bottom.equalTo(self.view.mas_bottom);
    }];

    //扫描线
    UIImageView *scannerLine = [[UIImageView alloc] init];
    [self.view addSubview:scannerLine];
    self.scannerLine = scannerLine;
    scannerLine.image = [UIImage imageNamed:@"扫描线.png"];
//    scannerLine.contentMode = UIViewContentModeScaleAspectFill;
    scannerLine.backgroundColor = [UIColor clearColor];
    [scannerLine mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.right.equalTo(scannerView);
    }];
    
    //label
    UILabel *msgLabel = [[UILabel alloc] init];
    [self.view addSubview:msgLabel];
    msgLabel.backgroundColor = [UIColor clearColor];
    msgLabel.textColor = [UIColor whiteColor];
    msgLabel.textAlignment = NSTextAlignmentCenter;
    msgLabel.font = [UIFont systemFontOfSize:14];
    msgLabel.text = @"将取景框对准二维码,即可自动扫描";
    [msgLabel mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(scannerView.mas_bottom).offset(10);
        make.left.right.equalTo(self.view);
    }];
    
    //相册识别
    KHBButton *photoBtn = [[KHBButton alloc] init];
    [self.view addSubview:photoBtn];
//    photoBtn.titleLabel.text = @"相册";
    [photoBtn setTitle:@"相册" forState:UIControlStateNormal];
    [photoBtn setTitleColor:RGB(188, 188, 188) forState:UIControlStateNormal];
    [photoBtn setImage:[UIImage imageNamed:@"相册图标"] forState:UIControlStateNormal];
    [photoBtn setTitleColor:RGB_HEX(0xf66060) forState:UIControlStateHighlighted];
    [photoBtn setImage:[UIImage imageNamed:@"相册图标红"] forState:UIControlStateHighlighted];
    photoBtn.titleLabel.font = [UIFont systemFontOfSize:16];
    [photoBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [photoBtn addTarget:self action:@selector(photoBtnClicked) forControlEvents:UIControlEventTouchUpInside];
    [photoBtn mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(scannerView.mas_right).offset(-20);
        make.top.equalTo(msgLabel.mas_bottom).offset(30);
        
    }];
   
    //扫码按钮
    KHBButton *scanBtn = [[KHBButton alloc] init];
    [self.view addSubview:scanBtn];
    [scanBtn setTitle:@"扫码" forState:UIControlStateNormal];
    [scanBtn setTitleColor:RGB_HEX(0xbcbcbc) forState:UIControlStateNormal];
    [scanBtn setImage:[UIImage imageNamed:@"扫描图标"] forState:UIControlStateNormal];
    
    [scanBtn setTitleColor:RGB_HEX(0xf66060) forState:UIControlStateDisabled];
    [scanBtn setImage:[UIImage imageNamed:@"扫描图标红"] forState:UIControlStateDisabled];
    
//    scanBtn.backgroundColor = [UIColor clearColor];
    scanBtn.enabled = NO;
    scanBtn.titleLabel.font = [UIFont systemFontOfSize:16];
    [scanBtn mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(scannerView.mas_left).offset(20);
        make.top.equalTo(msgLabel.mas_bottom).offset(30);
        
    }];
    
    
}

- (void)photoBtnClicked {
    if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]){
        //1.初始化相册拾取器
        UIImagePickerController *controller = [[UIImagePickerController alloc] init];
        //2.设置代理
        controller.delegate = self;
        //3.设置资源:
        /**
            UIImagePickerControllerSourceTypePhotoLibrary,相册
            UIImagePickerControllerSourceTypeCamera,相机
            UIImagePickerControllerSourceTypeSavedPhotosAlbum,照片库
         */
        controller.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
        //4.随便给他一个转场动画
        controller.modalTransitionStyle=UIModalTransitionStyleCrossDissolve;
        [self presentViewController:controller animated:YES completion:NULL];
        
    }else{
        NSLog(@"设备不支持访问相册,请在设置->隐私->照片中进行设置!");
    }

}


#pragma mark- ImagePickerController delegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    //1.获取选择的图片
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    //2.初始化一个监测器
    
    [picker dismissViewControllerAnimated:YES completion:^{
        //监测到的结果数组
           }];
   // [self getURLWithImage:info[UIImagePickerControllerOriginalImage]];
    [self test:info[UIImagePickerControllerOriginalImage]];
}

/**原生*/
-(void)test:(UIImage *)img {
    CIDetector*detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh }];
    NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:img.CGImage]];
    if (features.count >=1) {
        /**结果对象 */
        CIQRCodeFeature *feature = [features objectAtIndex:0];
        NSString *scannedResult = feature.messageString;
        [self analyzeQRCodeStr:scannedResult];
    }
    else{
        UIAlertView *alter1 = [[UIAlertView alloc] initWithTitle:@"解析失败" message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
        [alter1 show];

    }

}


/**ZXing*/
-(void)test1:(UIImage *)img{
    
    UIImage *loadImage= img;
    CGImageRef imageToDecode = loadImage.CGImage;
    
    ZXLuminanceSource *source = [[ZXCGImageLuminanceSource alloc] initWithCGImage:imageToDecode];
    ZXBinaryBitmap *bitmap = [ZXBinaryBitmap binaryBitmapWithBinarizer:[ZXHybridBinarizer binarizerWithSource:source]];
    
    NSError *error = nil;
    ZXDecodeHints *hints = [ZXDecodeHints hints];
    ZXMultiFormatReader *reader = [ZXMultiFormatReader reader];
    ZXResult *result = [reader decode:bitmap
                                hints:hints
                                error:&error];
    if (result) {
        NSString *contents = result.text;
        [self analyzeQRCodeStr:contents];
    } else {
        UIAlertView *alter1 = [[UIAlertView alloc] initWithTitle:@"解析失败" message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
        [alter1 show];

    }
}
/**ZBar*/
- (void)test2: (UIImage*)img {
    UIImage * aImage = img;
    ZBarReaderController *read = [ZBarReaderController new];
    CGImageRef cgImageRef = aImage.CGImage;
    ZBarSymbol* symbol = nil;
    NSString *qrResult = nil;
    for(symbol in [read scanImage:cgImageRef]) {
        qrResult = symbol.data ;
        NSLog(@"qrResult = symbol.data %@",qrResult);
    }
    if (qrResult) {
        NSString *contents = qrResult;
        [self analyzeQRCodeStr:contents];
    } else {
        UIAlertView *alter1 = [[UIAlertView alloc] initWithTitle:@"解析失败" message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles: nil];
        [alter1 show];
        
    }
}
/**
 *  添加扫码动画
 */
- (void)addAnimation {
//    UIView *line = [self.view viewWithTag:self.line_tag];
//    line.hidden = NO;
    self.scannerLine.hidden = NO;
    CABasicAnimation *animation = [KHBQRCodeViewController moveYTime:2 fromY:[NSNumber numberWithFloat:0] toY:@(screenWidth * khb_SCANNERWIDTH_RATE - 5) rep:OPEN_MAX];
//    [line.layer addAnimation:animation forKey:@"LineAnimation"];
    [self.scannerLine.layer addAnimation:animation forKey:@"LineAnimation"];
}

+ (CABasicAnimation *)moveYTime:(float)time fromY:(NSNumber *)fromY toY:(NSNumber *)toY rep:(int)rep {
    CABasicAnimation *animationMove = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
    [animationMove setFromValue:fromY];
    [animationMove setToValue:toY];
    animationMove.duration = time;
    animationMove.delegate = self;
    animationMove.repeatCount  = rep;
    animationMove.fillMode = kCAFillModeForwards;
    animationMove.removedOnCompletion = NO;
    animationMove.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    return animationMove;
}
/**
 *  去除扫码动画
 */
- (void)removeAnimation {
//    UIView *line = [self.view viewWithTag:self.line_tag];
//    [line.layer removeAnimationForKey:@"LineAnimation"];
//    line.hidden = YES;
    [self.scannerLine.layer removeAnimationForKey:@"LineAnimation"];
    self.scannerLine.hidden = YES;
}
/**
 *  从父视图中移出
 */
- (void)selfRemoveFromSuperview {
    [self.session stopRunning];
    [self.prelayer removeFromSuperlayer];
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (UIStatusBarStyle)preferredStatusBarStyle {
    return UIStatusBarStyleLightContent;
}

- (void)dealloc {
    [self.session removeObserver:self forKeyPath:@"running"];
}

@end