1. 程式人生 > >iOS二維碼掃描/識別

iOS二維碼掃描/識別

一直以為二維碼功能比較簡單,  從來沒有放在心上過,  由於公司專案需要這個功能,  也算是第一次真正去做二維碼的東西.  從網上看別人的部落格很多都是寫的比較碎片, 沒有一個能做的比較完整的,  今天我的這個模組做完, 索性總結一下吧, 希望更多的人少走彎路

網上很多二維碼的識別的框架其中以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 <AVFoundation/AVFoundation.h>

#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 ()<AVCaptureMetadataOutputObjectsDelegate, UIImagePickerControllerDelegate, 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:@"http://"] || [qrCodeStr containsString:@"https://"]) {
        
        if ([qrCodeStr containsString:@"http://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