本文主要讲解在实际开发中Xamarin.iOS扫描的原理和实现过程
现在的应用程序中对于二维码生成和扫描这个功能的应用非常普及,对于支付宝和微信这种的支付软件,二维码异常重要和关键,但对于二维码内部的生成原理和扫描过程究竟是怎么样实现的呢?这里我们今天就讲解下这些东西。
本文内容结构
1.二维码的形成原理
2.二维码扫描的原理
3.二维码扫描的具体代码实现
二维码的形成原理
首先我们可以看看下二维码的结构图分析
- 位置探测图形:用于标记二维码的矩形大小,定位作用。这三个定位图案有白边叫位置探测图形分隔符。之所以三个而不是四个意思就是三个就可以标识一个矩形了。
- 定位图形:也是用于定位的。原因是二维码有40种尺寸,尺寸过大了后需要有根标准线,不然扫描的时候可能会扫歪了。
- 格式信息:存在于所有的尺寸中,用于存放一些格式化数据的。
- 数据码和纠错码:除了上边的功能图形和版本格式信息外,图中其它所有的地方都是存放这数据码和纠错码。
二维码其实就是由很多0、1组成的数字矩阵,用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识别以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化等特点。二维条码/二维码能够在横向和纵向两个方位同时表达信息,因此能在很小的面积内表达大量的信息。
就拿我微信的这个二维码来举例:
简单的说,二维码就是把你想表达的信息翻译成黑白两种小方块,然后填到这个大方块中。有点类似我们中学的答题卡,就是把我们的语言翻译成机器可识别的语言,说白了就是把数字、字母、汉字等信息通过特定的编码翻译成二进制0和1,一个0就是一个白色小方块,一个1就是一个黑色小方块。我们通常需要生成的二维码的时候,我们将特定的字符串先转换成二进制数据流,然后进行处理后生成图中的二维码,有许多人会问那其中的微信图片怎么弄出来的呢?这个其实很简单,当我们先生成一张完整的二维码图片后,我们只需要对图片中进行绘制一张微信托图片即可。
二维码的扫描原理
首先我们看一段官方对手机扫描二维码的解释:
由于不同颜色的物体,其反射的可见光的波长不同,白色物体能反射各种波长的可见光,黑色物体则吸收各种波长的可见光.所以当摄像头扫描黑白相间的二维码上时,手机利用点运算的阈值理论将采集到的图象变为二值图像,即对图像进行二值化处理,得到二值化图像后,对其进行膨胀运算,对膨胀后的图象进行边缘检测得到条码区域的轮廓。
其实这一段话的解释就是:我们生成二维码时候我们将一段字符串变成二进制数据然后变成二维码,此时我们就需要将二维码转化为二进制数据再对这些数据进行纠错和译码,最后根据条码的逻辑编码规则把这些原始的数据转换成数据。而三个大黑方块保证我们在使用手机扫描的时候无论是什么方向,都能够正确识别二维码的内容。
接着我们看看在iOS中扫码的实现原理:
我们从上图可以看到扫码至少需要四个内容:设备(手机的摄像机),输入设备,输出设备,控制器(连接输入输出设备),我们在扫码的过程中,首先我们需要手机打开摄像头(获取相机的权限),接着我们需要新建一个输入设备的对象去处理输入的图像,然后我们需要一个输出设备去对我们接收到的图像处理后的结果进行输出,此时输入和输出设备间我们还需要一个控制器去建立链接,此时简单的扫码过程就完成了,但其实我们还需要一个试图区域(显示相机的范围)。
二维码扫描的具体代码实现
首先导入AVFoundation框架,新建控制器对象AVCaptureSession session
using AVFoundation;
using CoreFoundation;
using Foundation;
using System;
using UIKit;
using CoreGraphics;
namespace App2
{
public partial class Scan : UIViewController
{
static AVCaptureSession session;
static UILabel scanres;
static CoreAnimation.CALayer Layer;
ShelterView area;
CGRect clear_area;
UIView clear_area_view;
UILabel title;
UIView scan_line;
接着我们新建一个初始化输入和输出设备的方法,并根据传入参数不同,去调整扫描区域大小
public void StartScanWithSize(float size)
{
AVAuthorizationStatus status = AVCaptureDevice.GetAuthorizationStatus(AVMediaType.Video);
if(status == AVAuthorizationStatus.Denied|status == AVAuthorizationStatus.Restricted)
{
Console.WriteLine("应用相机权限受限,请到设置中启用");
}
//获取摄像设备
AVCaptureDevice device = AVCaptureDevice.DefaultDeviceWithMediaType(AVMediaType.Video);
//设置输入流
NSError error = null;
AVCaptureDeviceInput input = AVCaptureDeviceInput.FromDevice(device, out error);
if (error != null)
{
Console.WriteLine("相机有问题");
}
//判断输入流是否可用
if (input != null)
{
//创建输出流
AVCaptureMetadataOutput output = new AVCaptureMetadataOutput();
var OutPutObj = new OutputObjDelegate();
//设置委托
output.SetDelegate(OutPutObj, DispatchQueue.MainQueue);
//扫码区域大小设置(比例,相对横屏左上角)
nfloat x = clear_area.Y / View.Frame.Height;
nfloat y = clear_area.X / View.Frame.Width;
nfloat width = clear_area.Height / View.Bounds.Height;
nfloat height = clear_area.Width / View.Bounds.Width;
output.RectOfInterest = new CoreGraphics.CGRect(x, y, width, height);
//output.RectOfInterest = View.Bounds;
//初始化连接对象
session = new AVCaptureSession();
//设置高质量采集率0
session.SessionPreset = AVCaptureSession.PresetHigh;
session.AddInput(input);
session.AddOutput(output);
//设置扫码支持的编码格式
output.MetadataObjectTypes = AVMetadataObjectType.QRCode | AVMetadataObjectType.EAN13Code | AVMetadataObjectType.EAN8Code | AVMetadataObjectType.Code128Code;
AVCaptureVideoPreviewLayer layer = new AVCaptureVideoPreviewLayer(session);
layer.VideoGravity = AVLayerVideoGravity.ResizeAspectFill;
layer.Frame = View.Layer.Frame;
View.Layer.InsertSublayer(layer, 0);
//启动扫码
session.StartRunning();
}
}
实现委托类
public class OutputObjDelegate : AVCaptureMetadataOutputObjectsDelegate
{
public override void DidOutputMetadataObjects(AVCaptureMetadataOutput captureOutput, AVMetadataObject[] metadataObjects, AVCaptureConnection connection)
{
//base.DidOutputMetadataObjects(captureOutput, metadataObjects, connection);
if (metadataObjects.Length > 0)
{
//获取到信息后停止扫码
session.StopRunning();
AVMetadataMachineReadableCodeObject metaDataObj = metadataObjects[0] as AVMetadataMachineReadableCodeObject;
if (metadataObjects != null)
{
// scanres.Text = metaDataObj.StringValue;
//AVCaptureVideoPreviewLayer layer = (AVCaptureVideoPreviewLayer)Layer.Sublayers[0];
//layer.RemoveFromSuperLayer();
UIResponder responer = scanres.NextResponder;
do
{
if (responer is UIViewController)
{
var nav = responer as UIViewController;
ScanResult sc = new ScanResult();
sc.scan_res = metaDataObj.StringValue;
((UIViewController)responer).NavigationController.PushViewController( sc, true);
break;
}
responer = responer.NextResponder;
} while (responer != null);
}
}
}
}
当然这里我们的扫描区域的界面我们可以自定义去实现,这里我们就不对这个地方进行代码讲解,可以直接去Github上看Demo
当然如果我们生成的二维码出现不清晰或者非常模糊的情况,这里我给出大家一个对二维码清晰度的方法,通过下面这个方法应该就能解决清晰度问题了。
//图像清晰化处理
public UIImage DealFuzzyImage(CIImage img, float size)
{
CGRect ctr = img.Extent;
float scale = (float)Math.Min(size / ctr.Width, size / ctr.Height);
double s_width = ctr.Width * scale;
double s_height = ctr.Height * scale;
CGColorSpace color_space = CGColorSpace.CreateDeviceGray();
CGContext cg_context = new CGBitmapContext(null, (int)s_width, (int)s_height, 8, 0, color_space, CGImageAlphaInfo.None);
CIContext context = CIContext.FromOptions(null);
CGImage bitmap_imge = context.CreateCGImage(img, ctr);
cg_context.InterpolationQuality = CGInterpolationQuality.None;
cg_context.ScaleCTM(scale, scale);
cg_context.DrawImage(ctr, bitmap_imge);
CGImage scaleImg = ((CGBitmapContext)cg_context).ToImage();
return UIImage.FromImage(scaleImg);
}