iOS扫码优化-原生解析+ZXing辅助解析

1,047 阅读3分钟

背景

前段时间对公司项目中的扫码组件做了一次优化,提高了弱光环境下低精度条形码的识别速度和正确率(具体提升幅度没有做量化分析)。基本思路是使用原生AVFoundation框架解析可读码,同时输出视频流并截取图像进行处理,最后后使用ZXing解码库对处理后的图像进行解析。本文记录了主要实现思路和关键代码,文末附上demo地址。

采集图像

使用AVFoundation框架解析可读码,只需要创建AVCaptureMetadataOutput输出流,而使用ZXing辅助解析需要增加多一个AVCaptureVideoDataOutput来获取视频图像数据

- (void)initAVCapture {
    ...
    //创建元数据输出流,用于原生解码框架
    _metaOutput = [[AVCaptureMetadataOutput alloc]init];
    [_metaOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];

    //视频图像输出流,用于ZXing软解析
    _videoOutput = [[AVCaptureVideoDataOutput alloc] init];
    _videoOutput.alwaysDiscardsLateVideoFrames = YES;
    [_videoOutput setSampleBufferDelegate:self queue:dispatch_queue_create("VideoDataOutputQueue", DISPATCH_QUEUE_SERIAL)];
    
    //指定视频流输出格式
    NSString *key = (NSString *)kCVPixelBufferPixelFormatTypeKey;
    NSNumber *value = [NSNumber numberWithUnsignedInt:kCVPixelFormatType_32BGRA];
    NSDictionary *videoSettings = [NSDictionary dictionaryWithObject:value forKey:key];
    [_videoOutput setVideoSettings:videoSettings];
    ...
}

获取AVCaptureMetadata解析结果

通过AVCaptureMetadataOutput的输出,可以在代理方法中获取到AVFoundation对可读码的解析结果:

-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    NSString *resultString ;
    if (metadataObjects.count > 0) {
        //这里直接取第一个
        AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0];
        resultString = metadataObject.stringValue; 
        ...
    }
}

初始化ZXing解码器

  • 图像流数据的处理及ZXing解析过程封装在RSScanImageDecoder类中
  • 初始化RSScanImageDecoder类,注意需要设置解码器解析率与相机采集分辨率一致
    ...
    // 设置相机采集分辨率
    [_session setSessionPreset:AVCaptureSessionPreset1920x1080];
    // 初始化解码器
    self.imageDecoder = [[RSScanImageDecoder alloc] init];
    // 设置解码器解析率
    ImageResolution resolution = {1920,1080};
    self.imageDecoder.imageResolution = resolution;
    // 设置ZXing图像识别范围,解析工具类内会做坐标转换处理
    self.imageDecoder.cropRect = CGRectMake(_scanRect.frame.origin.x, _scanRect.frame.origin.y, _scanRect.frame.size.width, _scanRect.frame.size.height);

获取AVCaptureVideoDataOutput图像流数据

在代理方法中获取AVCaptureVideoDataOutput的输出,传输给RSScanImageDecoder实例进行处理解析

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    ...
    [self.imageDecoder decodeSampleBuffer:sampleBuffer processing:_filterPreview success:^(NSString *str) {
        NSLog(@"ZXing解析结果:%@",str);
    }];
    ...
}

使用RSScanImageDecoder解析图像流过程

- (void)decodeSampleBuffer:(CMSampleBufferRef)sampleBuffer processing:(UIImageView *)previewImageView success:(void (^)(NSString *))success {
    // 裁剪CMSampleBufferRef并转换成UIImage
    UIImage *image = [self getImageFromSampleBuffer:sampleBuffer];
    
    // 相机采集数据的原点在左上角,需要做坐标转换修正输出图片的方向
    image = [self fixImageOrientation:image];

    // 增强图片对比度
    image = [self enhanceImage:image];

    // 二值化处理
    image = [self convertToGrayScaleWithImage:image];
    
    //传入ZXing识别图像
    [self recognizeImage:image success:success];
}
  • 裁剪CMSampleBufferRef并转换成UIImage
- (UIImage *)getImageFromSampleBuffer:(CMSampleBufferRef)sampleBuffer {
    CMSampleBufferRef cropSampleBuffer;
    
    //buffer裁剪
    cropSampleBuffer = [self cropSampleBufferByCPU:sampleBuffer];
    
    //buffer转换成UIImage
    UIImage *image = [self imageWithOutputSampleBuffer:cropSampleBuffer];
    CFRelease(cropSampleBuffer);
    
    return image;
}
  • 图像方向修正 CMSampleBufferRef直接转换出来的图片是顺时针旋转90度的,需要做方向修正

  • 增强图片对比度 通过CoreImage的亮度和曝光度滤镜,增强画面的对比度,使二值化处理后输出的图像效果更好

  • 二值化处理 转换成黑白二值化图像,提高ZXing识别准确度

  • 使用ZXingWrapper进行解析

[ZXingWrapper recognizeImage:image block:^(ZXBarcodeFormat barcodeFormat, NSString *str) {
    if (str != nil && str.length > 0) {
        //recognize successfully
        dispatch_async(dispatch_get_main_queue(), ^{
            if (success) success(str);
        });
    }
}];

受限于笔者的技术及认知水平,文中可能有所纰漏,还望读者交流指正!

具体代码细节可查看Demo:RSScan