AVFoundation开发秘籍笔记-07高级捕捉功能之机器码识别(条码扫描)

一、概述

机器码识别,也就是条码扫描。AVFoundation定义了多种欧冠条码符号进行实时识别的方法,前置或后置摄像头都可以。

真个流程同人脸识别大体相似,区别就是输入元数据格式不同,另外就是对于元数据的处理和视图处理不同。

只要掌握流程,再去做更多的定制就容易很多,首先要明白基本原理以及视频捕捉基本原理

二、创建项目

1、创建并配置会话

  • 1、创建会话

self.captureSession = [[AVCaptureSession alloc] init];
self.captureSession.sessionPreset = AVCaptureSessionPreset640x480;
//设置捕捉会话预设类型,可以使用任意类型
//苹果建议使用最低合理解决方案以提高性能
  • 2、设置会话输入
// 创建会话输入

AVCaptureDevice *videoDevice =
    [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

AVCaptureDeviceInput *videoInput =
    [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:error];
if (videoInput) {
    if ([self.captureSession canAddInput:videoInput]) {
        [self.captureSession addInput:videoInput];
        self.activeVideoInput = videoInput;
    } 

    if (self.activeCamera.autoFocusRangeRestrictionSupported) { // 判断是否支持自动对焦
            if ([self.activeCamera lockForConfiguration:error]) {
                self.activeCamera.autoFocusRangeRestriction = AVCaptureAutoFocusRangeRestrictionNear;
                //捕捉设备的自动对焦通常在任何距离都可以进行扫描
                //不过大部分条码距离都不愿,所以可以缩小扫描区域来提升识别成功率
                
                [self.activeCamera unlockForConfiguration];
            }
        }
} 


  • 3、设置会话输出
self.metadataOutput = [[AVCaptureMetadataOutput alloc] init];
if ([self.captureSession canAddOutput:self.metadataOutput]) {
    [self.captureSession addOutput:self.metadataOutput];
    
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    [self.metadataOutput setMetadataObjectsDelegate:self queue:mainQueue];
    
    NSArray *types = @[AVMetadataObjectTypeQRCode,
                       AVMetadataObjectTypeAztecCode,];
    ;
    self.metadataOutput.metadataObjectTypes = types;
    //设置元数据类型,这里这是感兴趣对象是QR码和Aztec码
}
  • 4、实现代理AVCaptureMetadataOutputObjectsDelegate回调,当检测到条码数据时回调。
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
       fromConnection:(AVCaptureConnection *)connection {

   //处理元数据
    [self didDetectCodes: metadataObjects];
}

2、处理元数据,并设置条码框

  • 1、如果只是为了条码的值
- (void)didDetectCodes:(NSArray *)codes {
    for (AVMetadataMachineReadableCodeObject *code in array) {
            NSString *stringValue = code.stringValue;
            //这个就是条形码的值
            //不过一般一次只有一个值,或者直接取第一个元素即为条码值
    }
    
    //或者直接取第一个值
    AVMetadataMachineReadableCodeObject * metadataObject = [codes objectAtIndex : 0 ];  
    NSString *codeString = metadataObject.stringValue;
}
  • 2、标记出二维码所在位置

使用元数据的bounds属性确定其位置,并将之标记出来。

- (void)didDetectCodes:(NSArray *)codes {

    NSArray *array = [self transformedCodesFromCodes:codes];

    NSMutableArray *lostCodes = [self.codeLayers.allKeys mutableCopy];
    
    for (AVMetadataMachineReadableCodeObject *code in array) {
        NSString *stringValue = code.stringValue;
        //这个就是条形码的值
        
        if (stringValue) {
            [lostCodes removeObject:stringValue];
        } else {
            continue;//重新下一个循环
        }
        
        NSArray *layers = self.codeLayers[stringValue];
        if (!layers) {
            layers = @[[self makeBoundsLayer],[self makeCornersLayer]];
            self.codeLayers[stringValue] = layers;
            [self.previewLayer addSublayer:layers[0]];
            [self.previewLayer addSublayer:layers[1]];
        }
        
        CAShapeLayer *boundsLayer = layers[0];
        boundsLayer.path = [self bezierPathForBounds:code.bounds].CGPath;
        //得到一个CGPathRef赋给图层的path属性
        
        CAShapeLayer *cornersLayer = layers[1];
        cornersLayer.path = [self bezierPathForCorners:code.corners].CGPath;
        //对于cornersLayer,基于元数据对象创建一个CGPath
        
        NSLog(@"String :%@",stringValue);
    }
    
    for (NSString *stringValue in lostCodes) {
        for (CALayer *layer in self.codeLayers[stringValue]) {
            [layer removeFromSuperlayer];
        }
        [self.codeLayers removeObjectForKey:stringValue];
    }

}

- (NSArray *)transformedCodesFromCodes:(NSArray *)codes {
    NSMutableArray *transformCodes = [NSMutableArray array];
    
    for (AVMetadataObject *code  in codes) {
        AVMetadataObject *transformCode = [self.previewLayer transformedMetadataObjectForMetadataObject:code];
        //将设备坐标元数据对象转换为视图坐标空间对象
        
        [transformCodes addObject:transformCode];
    }

    return transformCodes;
}

- (UIBezierPath *)bezierPathForBounds:(CGRect)bounds {
    // 图层边界,创建一个和对象的bounds关联的UIBezierPath
    return [UIBezierPath bezierPathWithRect:bounds];
}

- (CAShapeLayer *)makeBoundsLayer {
    //CAShapeLayer 是具体化的CALayer子类,用于绘制Bezier路径
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.strokeColor = [UIColor colorWithRed:0.96f green:0.75f blue:0.06f alpha:1.0f].CGColor;
    shapeLayer.fillColor = nil;
    shapeLayer.lineWidth = 4.0f;
    
    return shapeLayer;
}

- (CAShapeLayer *)makeCornersLayer {
    
    CAShapeLayer *cornersLayer = [CAShapeLayer layer];
    cornersLayer.lineWidth = 2.0f;
    cornersLayer.strokeColor = [UIColor colorWithRed:0.172 green:0.671 blue:0.428 alpha:1.0].CGColor;
    cornersLayer.fillColor = [UIColor colorWithRed:0.190 green:0.753 blue:0.489 alpha:0.5].CGColor;
    


    return cornersLayer;;
}

- (UIBezierPath *)bezierPathForCorners:(NSArray *)corners {
    
    
    UIBezierPath *path = [UIBezierPath bezierPath];
    for (int i = 0; i < corners.count; i ++) {
        CGPoint point = [self pointForCorner:corners[i]];
        //遍历每个条目,为每个条目创建一个CGPoint
        if (i == 0) {
            [path moveToPoint:point];
        } else {
            [path addLineToPoint:point];
        }
        
        
    }
    [path closePath];
    return path;
    

    return nil;
}

- (CGPoint)pointForCorner:(NSDictionary *)corner {
    //包含角点对象的字典具有x值的条目和y值的条目,可以手动取出这些值手动创建CGPoint
    //Quartz框架提供方法实现此功能CGPointMakeWithDictionaryRepresentation
    //兑换如字典和指针即可
    CGPoint point;
    CGPointMakeWithDictionaryRepresentation((CFDictionaryRef)corner, &point);
    return point;

}

你可能感兴趣的:(AVFoundation开发秘籍笔记-07高级捕捉功能之机器码识别(条码扫描))