背景
前段时间对公司项目中的扫码组件做了一次优化,提高了弱光环境下低精度条形码的识别速度和正确率(具体提升幅度没有做量化分析)。基本思路是使用原生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