这篇文章是天天品尝iOS7甜点系列的一部分,你可以查看完整的系列目录:天天品尝iOS7甜点


Introduction – 介绍

在昨天,我们已经查看了CoreImage中包含的新的过滤器中的一些,并且发现在iOS7中,我们可以有能力自己生成一个二维码。所以,既然给出了如何生成二维码,就需要能够对这个二维码进行解码,当然不能让你失望了,我们在今天的文章中就来介绍如何使用AVFoundation框架中的一些新特性进行解码二维码。

本章的实例程序能够在github上面进行访问,访问地址:github.com/ShinobiControls/iOS7-day-by-day

AVFoundation pipeline – AVFoundation工作流

AVFoundation是一个大的框架,它可以促进创建,编辑,显示和捕获多媒体。这篇文章并不是主要介绍如何使用AVFoundation,而是我们要通过这个框架来提取手机屏幕上面的二维码,为了能够使用这个框架,我们首先需要导入这个框架:

1
@import AVFoundation;

当我们捕获媒体的时候,我使用AVCaptureSession类来充当我们工作流的核心。然后我们需要添加输入和输出来完成这次会话。我们将会在viewDidLoad方法中设置这些。首先,创建一个会话:

1
AVCaptureSession *session = [[AVCaptureSession alloc] init];

我们需要添加主要的摄像头作为会话的输入。输入就是一个AVCaptureDeviceInput对象,它是通过一个AVCaptureDevice对象创建的:

1
2
3
4
5
6
7
8
9
10
11
12
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;

AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];

if (input) {  // Add the input to the session
  [session addInput:input];
}else {  NSLog(@"error: %@", error);
  return;
}

这里我们获得了一个默认视频输入的设备引用,它将代表设备上面的后置摄像头。然后使用这个设备创建一个AVCaptureDeviceInput输入对象,然后把它添加到会话中。

为了获得获得视频中的内容,我们需要创建一个AVCaptureVideoPreviewLayer.它是一个CALayer的子类,当它添加到会话中的时候,它可以显示当前视图中的输出内容。考虑到这些,我们需要一个实例变量_previewLayer来作为AVCaptureVideoPreviewLayer的引用:

1
2
3
4
5
_previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:session];
_previewLayer.videoGravit = AVLayerVideoGravityResizeAspectFill;
_previewLayer.bounds = self.view.bounds;
_previewLayer.position = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds));
[self.view.layer addSublayer:_previewLayer];

videoGravity属性是用来指定视频是如何出现在层上面。由于视频的尺寸和屏幕不是相等的,我们可以把视频的边缘给砍掉,然后让它填充整个屏幕,所以使用AVLayerVideoGravityResizeAspectFill值。我们添加这个层作为视图层的子层。

现在才是真正开启会话的时候:

1
2
// Start the AVSession running
[session startRunning];

如果你运行应用程序,你将会摄像头的输出信息呈现在屏幕上面-神奇的感觉。

Capturing metadata – 捕获元数据

从iOS5开始就可以使用我们上述的内容,但是在这个章节,我们将会做很多事情,而这些东西只有在iOS7中才会存在的。

一个AVCaptureSession对象有附加的AVCaptureOutput对象。形成一个工作流的终点。在这里我们感兴趣的是AVCaptureOutput的子类AVCaptureMetadataOutpu.它可以查出视频中的任何元数据然后输出它。这个类型的输出并不会形成图像或者视频,而是从图像或者视频中提取的元数据本身。设置这些如下所示:

1
2
3
4
5
*out = [[AVCaptureMetadataOutput alloc] init];
// Have to add the output before setting metadata types
[session addOutput:output];
// What different things can we register to recognise?
NSLog(@"%@", [output availableMetadataObjectTypes]);

这里,我们创建一个元数据输出对象,并且把它作为一个输出添加到会话中。然后我们可以提供一个方法用来记录不同的元数据类型的列表:

1
2
3
4
5
6
7
8
9
10
11
12
2013-10-09 11:10:26.085 CodeScanner[6277:60b] (
    "org.gs1.UPC-E",
    "org.iso.Code39",
    "org.iso.Code39Mod43",
    "org.gs1.EAN-13",
    "org.gs1.EAN-8",
    "com.intermec.Code93",
    "org.iso.Code128",
    "org.iso.PDF417",
    "org.iso.QRCode",
    "org.iso.Aztec"
)

需要重要注意的就是我们在尝试这个的时候已经把我们的元数据输出设置到会话中。由于可用的类型依赖与输入设备。我们可以使用下面的代码注册查找的二维码类型:

1
2
// We're only interested in QR Codes
[output setMetadataObjectTypes:@[AVMetadataObjectTypeQRCode]];

这是一个数组类型,所以你可以只能多个你想要的元数据类型。

当元数据对象从视频流中找到一些东西,它就可以生成元数据,然后通知它的代理,所以,我们需要设置代理:

1
2
// This VC is the delegate, Please call us on the main queue
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];

由于AVFoundation被设计成可以允许线程访问,所以我们需要指定代理在那个线程中使用。

我们需要适配AVCaptureMetadataOutputObjectsDelegate协议:

1
2
3
4
5
@interface SCViewController () <AVCaptureMetadataOutputObjectsDelegate> {  AVCaptureVideoPreviewLayer *_previewLayer;
  UILabel *_decodeMessage;
}
@end

我们需要实现协议的方法是captureOutput:didOutputMetadataObjects:fromConnection::

1
2
3
4
5
6
7
8
9
10
#prgma mark - AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {  for (AVMetadataObject *metadata in metadataObjects) {      if ([metadata.type isEqualToString:AVMetadataObjectTypeQRCode]) {          AVMetadataMachineReadableCodeObject *transformed = (AVMetadataMachineReadableCodeObject *)metadata;
          // Update the view with the decoded text
          _decodedMessage.text = [transformed stringValue];
      }
  }
}

其中的metadataObjects数组包含了AVMetadataObject对象(就是我们设置检索的类型的数据).由于我们只注册查找二维码类型,我们我们得到的数组里面的内容的类型都是AVMetadataObjectTypeQRCode.AVMetadataMachineReadableCodeObject类型具有一个stringValue属性,它包含了所有元数据队形解码的值信息。在这里,我们把获得到的字符串信息显示到_decodedMessage标签上面,标签我们可以在viewDidLoad方法中进行设置:

1
2
3
4
5
6
7
// Add a label to display the resultant message
_decodedMessage = [[UILabel alloc] initWithFrame:CGRectMake(0, CGRectGetHeight(self.view.bounds) - 75, CGRectGetWidth(self.view.bounds), 75)];
_decodedMessage.numberOfLines = 0;
_decodedMessage.backgroundColor = [UIColor colorWithWhite:0.8 alpha:0.9];
_decodedMessage.textColor = [UIColor darkGrayColor];
_decodedMessage.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:_decodedMessage];

运行应用程序,然后用摄像头对准二维码,我们就可以在标签上面解码出二维码上面的内容,然后显示出来,具体的效果如下图所示:

Drawing the code outline – 给二维码添加上线框

除了提供解码元数据对象,它还包含了位置的边界,我们的应用程序将会直观的标识出具体的元数据对象的具体的位置。

为了达到这个目的,首先我们需要创建一个UIView的子类,它可以提供一系列的点,然后将会连接起来。这将会使我们明确的来构建它:

1
2
3
@interface SCShapeView : UIView
@property (strong, nonatomic) NSArray *corners;
@end

其中的corners属性数组包含了CGPoint对象,每一个都代表我们希望绘制图像路径的拐角处。

我们将会使用一个CAShapeLayer来进行绘制这些点,并且这是非常有效率的方法来绘制图形:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@interface SCShapeView() {  CAShapeLayer *_outline;
}
@end

@implementation SCShapeView

- (id)initWithFrame:(CGRect)frame {  self = [super initWithFrame:frame];
  if (self) {      // Initialization code
      _outline = [CAShapeLayer new];
      _outline.strokeColor = [[[UIColor blueColor] colorWithAlphaComponent:0.8] CGColor];
      _outline.lineWidth = 2.0;
      _outline.fillColor = [[UIColor clearColor] CGColor];
      [self.layer addSublayer:_outline];
  }
  return self;
}

@end

在这里我们创建一个图像层,设置一些外观的属性,然后把它添加到当前的层中。我们现在就需要设置图形的路径,也就是我们现在需要设置corners属性了:

1
2
3
4
5
6
- (void)setCorners:(NSArray *)corners {  if (corners != _corners) {      _corners = corners;
      _outline.path = [[self createPathFromPoints:corners] CGPath];
  }
}

上述含义是如果corners属性发生了变化,图形就将会使用新的位置进行绘制.我们使用一个工具方法来通过封装了CGPoint对象的数组对象来创建一个UIBezierPath

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- (UIBeazierPath *)cratePathFromPoints:(NSArray *)points {  UIBezierPath *path = [UIBezierPath new];
  // Start at the first corner
  [path moveToPoint:[[points firstObject] CGPointValue]];

  // Now draw lines around the corners
  for (NSUinter i = 1, i < points.count; i++) {      [path addLineToPoint:[points[i] CGPointValue]];
  }

  // And join it back to the first corner
  [path addLineToPoint:[[points firstObject] CGPointValue]];

  return path;
}

这实际上是创建了一个完成的图形,运用了UIBezierPath的API。

现在我们创建这个图形视图,我们需要在试图控制器中使用它,然后把它现在在我们查找的二维码上面。让我们创建一个实例变量,然后在viewDidLoad方法中进行初始化:

1
2
3
4
_boundingBox = [[SCShapeView alloc] initWithFrame:self.view.bounds];
_boundingBox.backgroundColor = [UIColor clearColor];
_boundingBox.hidden = YES;
[self.view addSubview:_boundingBox];

现在我们需要更新这个视图中元数据输出的代理方法:

1
2
3
4
5
6
7
8
9
10
11
// Transform the meta-data coordinates to screen coords
AVMetadataMachineReadableCodeObject *transformed = (AVMetadataMachineReadableCodeObject *)[_previewLayer transformedMetadataObjectForMetadataObject:metadata];
// Update the frame on the _boundingBox view, and show it
_boundingBox.frame = transformed.bounds;
_boundingBox.hidden = NO;
// Now convert the corners array into CGPoints in the coordinate system
// of the bounding box itself
NSArray *translatedCorners = [self translatePoints:transformed.corners fromView:self.view toView:_boundingBox];

// Set the corners array
_boundingBox.corners = translatedCorners;

AVFoundation在屏幕上面进行绘制的时候,使用一个不同于UIKit的坐标系,所以第一部分我们需要使用AVCaptureVideoPreviewLayer中的一个代码片段transformedMetadataObjectForMetadataObject:方法来把自身的坐标系进行转换。使它编程我们自己预览层的坐标系统.

然后我们设置我们图形层的frame,它是通过查找的二维码的bounds得到的,然后对图形层进行显示.

现在我们就需要设置corners属性了,让图形层能够正确的显示,但是在此之前,我们需要再次改变系统坐标系。我们可以运用下面的工具类来达到这个目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (NSArray *)translatePoints:(NSArray *) fromView:(UIView *)fromView toView:(UIView *)toView {  NSMutableArray *translatedPoints = [NSMutableArray new];

  // The points are provided in a dictionary with keys X and Y
  for (NSDictionary *point in points) {      // Let's turn them into CGPoints
      CGPoint pointValue = CGPointMake([point[@"X"] floatValue], [point[@"Y"] floatValue]);
      // Now translate from one view to the other
      CGPoint translatedPoint = [fromView convertPoint:pointValue toView:toView];
      // Box them up and add to the array
      [translatedPoint addObject:[NSValue valueWithCGPoint:translatedPoint]];
  }
  return [translatedPoints copy];
}

通过上面的方法我们转换成为正确的CGPoint数组设置到corners属性中。

如果你运行应用程序,你就会看到一个高亮的线框显示在我们的二维码上面:

 

最后一个小点是把绘制出来的线框过一段时间消失掉。这就阻止了当前没有发现二维码信息的时候,线框还存在的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)startOverlayHideTimer
{    // Cancel it if we're already running
    if(_boxHideTimer) {        [_boxHideTimer invalidate];
    }

    // Restart it to hide the overlay when it fires
    _boxHideTimer = [NSTimer scheduledTimerWithTimeInterval:0.2
                                                     target:self
                                                   selector:@selector(removeBoundingBox:)
                                                   userInfo:nil
                                                    repeats:NO];
}

- (void)removeBoundingBox:(id)sender
{    // Hide the box and remove the decoded text
    _boundingBox.hidden = YES;
    _decodedMessage.text = @"";
}

我们可以在代理方法方法中进行调用计时器:

1
2
// Sart the timer which will hiden the overlay
[self startOverlayHideTimer];

Conclusion – 总结

AVFoundation框架是强大且复杂的,但是在iOS7中,它变得更好了。以前在移动设备上面查找条形码是一个十分艰巨的任务,但是通过我们今天介绍的这些新的元数据输出类型,现在就变得十分的简单而高效。不管你是否需要使用到条形码,这都是一个你需要用到的简单方法。

本文翻译自:iOS7 Day-by-Day :: Day 16 :: Decoding QR Codes with AVFoundation

Posted by kingiol 2014-01-20 Mon  iOS7, iOS7 Day-by-Day, 翻译

iOS7—Day by day—Day16:Decoding QR Codes With AVFoundation相关推荐

  1. iOS7—Day by day

    iOS7是苹果操作系统从iPhone3G诞生依赖最大的一次了,不但是UI方面的重大更新改进,同时引进了很多令开发者欢呼雀跃的APIs,通过这些APIs可以搭建更加牛X的apps. 我决定帮助开发者在一 ...

  2. Create QR Code

    Create QR Code Generate QR Codes using Google's Chart API. Text to embed in QR Code Image Size :   1 ...

  3. qr码是二维码码_如何使用QR码进行有效的营销和推广

    qr码是二维码码 Efficient means doing things right. Effective is about doing the right things. 高效意味着做正确的事. ...

  4. 如何在Angular 10中生成QR码

    In this tutorial, we'll learn how to generate QR codes in Angular 10 by building a simple example ap ...

  5. 二维码:关于QR code的版权问题

    QR二维条码是日本的专利,那如果我们开发相应的编码和解码软件构成侵权,以及需要支付专利费吗? 专利问题是潜在的,QR目前并没有收取相关的费用,但是不代表以后不收取,DM就让NOKIA交了2亿美元. 国 ...

  6. 打印机qr代码_自动将QR代码添加到网页中,以便从打印副本中轻松链接引用

    打印机qr代码 In this author's humble opinion, most QR Codes are acne in print. However, there are a few p ...

  7. angular 代码生成器_使用Angular 10构建QR代码生成器

    angular 代码生成器 In this tutorial, we'll learn how to build a QR Codes generator application using the ...

  8. 如何在iPhone上使用Chrome扫描QR码

    QR (Quick Response) codes are found in many places, such as advertisements, billboards, business win ...

  9. qr码是二维码码_如何使用QR码安装Android应用和共享联系人

    qr码是二维码码 Have you ever seen those odd looking square barcodes on a website and wondered what the hec ...

  10. 如何使用iPhone的相机应用程序扫描QR码

    Before iOS 11, iPhone users had to download a third-party app in order to scan a QR code. However, t ...

最新文章

  1. lighttpd+PHP安装
  2. MYSQL批量插入数据库实现语句性能分析
  3. Linux禁止普通用户su至root
  4. python单元测试之unittest框架使用
  5. html.renderaction 控制器,Html.RenderAction简单用法
  6. WCF入门的了解准备工作
  7. (zz)编译Ubuntu Linux内核
  8. python123第七章_Python入门第7/10页
  9. xtragrid 某个值 查找_Java 经典算法:二分法查找(循环和递归两种方式实现)
  10. SQL Server 2005:面向信息管理的全新平台
  11. Redis介绍及常用命令【转载】
  12. centos7 修改时区
  13. 关于CodeBlocks下载后无法编译运行的问题
  14. 测试学习——全链路压测
  15. 机房冷风吹-linux基础环境搭建(基础篇)
  16. ORA-00932: 数据类型不一致: 应为 CHAR, 但却获得 NUMBER
  17. java错误找不到符号怎么办_java错误找不到符号
  18. 批量执行ABAQUS的inp文件——整理
  19. 51单片机驱动无源蜂鸣器
  20. myEclipse10安装以及破解

热门文章

  1. 深度linux12.12安装,深度Linux 12.12 Alpha发布
  2. php实时股票,PHP实现股票趋势图和柱形图
  3. 当web应用包含了websocket长连接,如何在web应用前加一层nginx转发
  4. Zynga的数据分析
  5. 人民网:《百度公布博客服务商前十强 MSN博客超过本土博客》
  6. 注册了DELPHI盒子
  7. win7 把html作为桌面,微软开始部署桌面HTML5版必应Bing
  8. 如何修改电驴服务器地址,emule设置连接服务器地址
  9. hive生产实践问题(一)在使用Hive Client跑job时,一直提示job被kill,
  10. Java黑皮书编程练习题6.08(摄氏度和华氏度之间的转换)