基于MacOSX平台下的二维码扫码功能

基于MacOSX平台下的二维码扫码功能

    最近项目接受了一个需求,要在macOS的系统上实现一个二维码扫码的功能.
    通过多方面的学习,最终实现了要求,具体的实现步骤将会在下面展示。

核心功能模块

    实现途径上,最开始我是打算采用iOS自带有的原生Api实现的,也是一般iOS通用的二维码识别方式,代码如下:
// 条码类型 AVMetadataObjectTypeQRCode
//设置扫码支持的编码格式(如下设置条形码和二维码兼容) 
_output.metadataObjectTypes=@[AVMetadataObjectTypeQRCode,
AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
    但是非常遗憾的是,此api只支持iOS7.0+,对于OS系统是不支持的,那么就不能采用这个方法来识别二维码了。
    如果使用ZXing的话,存在相当多的问题,需要修改的内容也是非常多的。
    楼主能力与时间有限,所以没有走通,如果有好的方法大家可以互相交流下。
    而这里主要使用的解析模块是系统自带的一个模块CIDetector.
    利用AVCaptureSession截取某一时刻的图像,然后转换成CIImage,传输给CIDetector解析。

1、初始化session

    与iOS的扫码功能一样,需要创建一个AVCaptureSession的上下文处理器。  
    设备和输入设备都是别无二致的,但是采集信息的时候不能采用AVCaptureMetadataOutput(iOS独有的)。    
    这里采用的是AVCaptureStillImageOutput,用来获取某一帧的图像。具体代码如下:
/**
 设置上下文处理器和输入输出
 */
- (void)setupCamera
{
    _session = [[AVCaptureSession alloc] init];
    [_session setSessionPreset:AVCaptureSessionPresetHigh];



    NSError *error = nil;

    _device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    AVCaptureDeviceInput *myDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:_device error:&error];
    [self.session addInput:myDeviceInput];



    _output = [[AVCaptureStillImageOutput alloc] init];
    NSDictionary *myOutputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey,nil];
    [_output setOutputSettings:myOutputSettings];

    [self.session addOutput:_output];

}

2、设置加载图层

    这块的部分和iOS的功能和作用一样,无需多言。
/**
 设置视频加载图层

 @param layer 图层
 @param bounds 范围
 */
- (void)setAVCaptureVideoPreviewLayerWithSupLayer:(CALayer *)layer
                                       withBounds:(NSRect)bounds
{
    if (preview!=nil)
    {
        [preview removeFromSuperlayer];
    }

    preview = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
    [preview setVideoGravity:AVLayerVideoGravityResizeAspectFill];


    [preview setBounds:bounds];

    [layer addSublayer:preview];
}

3、调用方法识别图片

    因为不是使用的AVCaptureMetadataOutput,所以获取图像的方法,可以是用一个计时器定时每秒去获取到当前的图像信息。   
    用一个计时器调用这个方法即可。
/**
 识别图片中的二维码

 @param fib 完成识别后返回的block
 */
- (void)identifyImageQR:(FinishIdentifyBlock)fib
{
    AVCaptureConnection *myVideoConnection =[self connectionWithMediaType:AVMediaTypeVideo fromConnections:[[self output] connections]];


    //撷取影像(包含拍照音效)
    [_output captureStillImageAsynchronouslyFromConnection:myVideoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {

        //完成撷取时的处理程序(Block)
        if (imageDataSampleBuffer) {
            NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];

            //取得的静态影像
            NSImage *myImage = [[NSImage alloc] initWithData:imageData];


            NSString * qrString_ = [self stringFromCiImage:[self getCIImage:myImage]];



            if (![qrString_ isEqualToString:@""]) {


                fib(qrString_,nil);



            }
            else
            {
                fib(nil,@"");

            }

        }
    }];
}
/**
 根据媒体类型,总共得到的数据,筛选出自己需要的数据

 @param mediaType 媒体类型
 @param connections 数组组
 @return 需要的数据
 */
- (AVCaptureConnection *)connectionWithMediaType:(NSString *)mediaType fromConnections:(NSArray *)connections
{
    for ( AVCaptureConnection *connection in connections ) {
        for ( AVCaptureInputPort *port in [connection inputPorts] ) {
            if ( [[port mediaType] isEqual:mediaType] ) {
                return connection;
            }
        }
    }
    return nil;
}

5、NSImage转换成CIImage,并进行解析。

    截取到的图片获取到的是NSImage(类似于UIImage),这个图像是不能用于二维码识别的。  
    利用getCIImage方法可以将输入的NSImage转换成CIImage,然后进一步交给后续的方法解析。
/**
 NIImage转换成CIIMage图像

 @param myImage 输入的NSimage图线
 @return 返回CIImage图像
 */
- (CIImage *)getCIImage:(NSImage *)myImage
{

    NSData  * tiffData = [myImage TIFFRepresentation];

    NSBitmapImageRep * bitmap;

    bitmap = [NSBitmapImageRep imageRepWithData:tiffData];



    // create CIImage from bitmap


    CIImage * ciImage = [[CIImage alloc] initWithBitmapImageRep:bitmap];



    // create affine transform to flip CIImage


    NSAffineTransform *affineTransform = [NSAffineTransform transform];

    [affineTransform translateXBy:0 yBy:128];

    [affineTransform scaleXBy:1 yBy:-1];



    // create CIFilter with embedded affine transform


    CIFilter *transform = [CIFilter filterWithName:@"CIAffineTransform"];

    [transform setValue:ciImage forKey:@"inputImage"];

    [transform setValue:affineTransform forKey:@"inputTransform"];



    // get the new CIImage, flipped and ready to serve


    CIImage * result = [transform valueForKey:@"outputImage"];



    // draw to view


    [result drawAtPoint: NSMakePoint ( 0,0 )

               fromRect: NSMakeRect  ( 0,0,128,128 )

              operation: NSCompositingOperationSourceOver

               fraction: 1.0];

    // cleanup

    return ciImage;
}


/**
 根据CIImage图像来进行二维码解析工作

 @param ciimage 输入的CIImage图像
 @return 返回解析的字符串
 */
- (NSString *)stringFromCiImage:(CIImage *)ciimage {

    NSString *content = @"" ;

    if (!ciimage) {

        return content;

    }
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode

                                              context:[CIContext contextWithOptions:nil]

                                              options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];

    NSArray *features = [detector featuresInImage:ciimage];

    if (features.count) {

        for (CIFeature *feature in features) {

            if ([feature isKindOfClass:[CIQRCodeFeature class]]) {

                content = ((CIQRCodeFeature *)feature).messageString;

                break;

            }

        }

    } else {

        NSLog(@"未正常解析二维码图片, 请确保iphone5/5c以上的设备");

    }
    return content;
}

扫码功能单例化头实现

    以上的方法全是自定的对象方法,可以用于写成一个单例类进行使用,头文件内容如下:
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
typedef void(^FinishIdentifyBlock)(NSString * qrString,NSString * error);
@interface MacQRScanSDK : NSObject

@property (strong,nonatomic)AVCaptureDevice * device;
@property (strong,nonatomic)AVCaptureDeviceInput * input;

@property (strong,nonatomic)AVCaptureStillImageOutput * output;

@property (strong,nonatomic)AVCaptureSession * session;
@property (strong,nonatomic)AVCaptureVideoPreviewLayer * preview;


/**
 创建MacQRScanSDK的单利对象

 @return 返回MacQRScanSDK对象
 */
+ (MacQRScanSDK *)sharedManager;



/**
 设置上下文处理器和输入输出
 */
- (void)setupCamera;


/**
 设置视频加载的图层

 @param layer 图层
 */


/**
 设置视频加载图层

 @param layer 图层
 @param bounds 范围
 */
- (void)setAVCaptureVideoPreviewLayerWithSupLayer:(CALayer*)layer
                                       withBounds:(NSRect)bounds;


/**
 开始扫描
 */
- (void)beginScan;


/**
 结束扫描
 */
- (void)stopScan;


/**
 识别图片中的二维码

 @param fib 完成识别后返回的block
 */
- (void)identifyImageQR:(FinishIdentifyBlock)fib;
@end

在ViewController中的调用

在viewDidLoad中实现初始化,并且设置对应的代码
- (void)viewDidLoad {
    [super viewDidLoad];
    //初始化单例类
    mac_qr_scan_ = [MacQRScanSDK sharedManager];
    //初始化相机
    [mac_qr_scan_ setupCamera];
    //设置AVCaptureVideoPreviewLayer
    [mac_qr_scan_ setAVCaptureVideoPreviewLayerWithSupLayer:self.centView_.layer withBounds:NSMakeRect(0, 0, window_width*2 , window_height*2 )];
}
    上面的setAVCaptureVideoPreviewLayerWithSupLayer,在Mac软件中会因为拖拽界面导致window重新设置。
    那么这个layer也是需要实时获取到window的宽和高来修改范围。具体的方法可以参考使用windowDidResize。
    windowDidResize会在屏幕改变的时候调用,是系统的一个通知,具体的使用方法可以自己查看一下api或者谷歌百度。
- (void)windowDidResize:(NSNotification *)aNotification
{
    window_width=self.window.frame.size.width;
    window_height=self.window.frame.size.height;
}
    因为使用AVCaptureStillImageOutput,它的作用是可以截取某一时刻摄像头捕捉的某一帧的图像。
    那么对于二维码来说,我们可以采用一个计时器,然后定时去调用下面的方法,获取图像并解析。
[mac_qr_scan_ identifyImageQR:^(NSString *qrString, NSString *error) {
    if (error==nil) {
        // 没有error,可以对qr进行操作

    }
    else
    {
        //有error,说明有问题,需要追踪
    }
}];
   通过这些方法,基本可以实现摄像头采集二维码并解析的功能。
   对于二维码采集,前置摄像头识别率情况堪忧,尽量保证在光线充足,且不要反光的情况下扫描,不然容易失败。
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值