美文网首页
二维码生成与检测

二维码生成与检测

作者: Yasic | 来源:发表于2018-03-19 17:53 被阅读35次

    生成二维码

    生成二维码主要用到 CIFilter,主要的函数如下

        CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
        [filter setDefaults];
        NSData *data = [@"微信 Yasic" dataUsingEncoding:NSUTF8StringEncoding];
        [filter setValue:data forKey:@"inputMessage"];
        [filter setValue:@"H" forKey:@"inputCorrectionLevel"];
        CIImage *outPutImage = [filter outputImage];
    

    但是要注意的是,接下来如果直接用 CIImage 来生成 UIImage,得到的效果可能如下图所示

    UIImage *result1 = [UIImage imageWithCIImage:outPutImage];
    
    UncorrectQRImage.png

    可以看到图片很模糊,此时网上大部分博客给出的解决方案如下

        /// 将 CGRect 取整到最近的完整点是非常重要的。小数值会让边框画在像素边界处。因为像素已经是最小单元(不能再细分),小数值会使绘制时取周围几个像素的平均值,这样看起来就模糊了。CGRectIntegral 将表示原点的值向下取整,表示大小的值向上取整,这样就保证了你的绘制代码平整地对齐到像素边界。作为一个经验性的原则,如果你在执行任何一个可能产生小数值的操作(例如除法,CGGetMid[X|Y],或是 CGRectDivide),在把一矩形作为视图的边框之前应该用CGRectIntegral正则化它。
        CGRect extent = CGRectIntegral(image.extent);
        CGFloat size = 300;
        CGFloat scale = MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent)); // 计算需要缩放的比例
        
        size_t width = CGRectGetWidth(extent) * scale; // 计算缩放后的尺寸
        size_t height = CGRectGetHeight(extent) * scale;
        CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();
        /// CIContext 属于Core Image框架(文档中提到主要的功能就是,用内置或自定义的过滤器处理图片和视频 以及在视频图片中检测面部和眼睛子类的特征和跟踪面部。和 Core Graphics 的主要区别 就是更注重于视频图片的加工处理)的,是一个 OC 对象
        CIContext *context = [CIContext contextWithOptions:nil];
        CGImageRef bitmapImage = [context createCGImage:image fromRect:extent]; // 转化为位图
        
        /// CGContextRef 属于Core Graphics 框架(使用 Quartz 进行 2D 渲染,处理基于路径的绘图、抗锯齿渲染、渐变、图像、颜色管理、pdf文档等 2D 绘图渲染功能)
        /// data 是一个指针,指向存储绘制的bitmap context的实际数据的地址,最少大小为bytesPerRow* height.可以传入null,让quartz自动分配计算
        /// width/height bitmap的宽度,高度,以像素为单位
        /// bitsPerComponent 一个component占据多少字节。对于32bit的RGBA空间,则是8(8*4=32)。
        /// bytesPerRow 每一行的byte数目。如果data传入null,这里传入0,则会自动计算
        /// space 颜色空间,一般就是DeviceRGB
        /// bitmapInfo,一个常量,指定了是否具有alpha通道,alpha通道的位置,像素点存储的数据类型是float还是Integer等信息
        CGContextRef bitmapContextRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);
        CGContextSetInterpolationQuality(bitmapContextRef, kCGInterpolationNone);//不允许上下文在各个保真度等级插入像素
        
        /// 该方法控制坐标系统水平方向上缩放 sx,垂直方向上缩放 sy。在缩放后的坐标系统上绘制图形时,所有点的 X 坐标都相当于乘以 sx 因子,所有点的 Y 坐标都相当于乘以 sy 因子
        CGContextScaleCTM(bitmapContextRef, scale, scale); //对位图上下文进行缩放
        
        CGContextDrawImage(bitmapContextRef, extent, bitmapImage);//绘制位图到位图上下文
        CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapContextRef);//生成缩放后的位图
        UIImage *outputImage = [UIImage imageWithCGImage:scaledImage];
        CGContextRelease(bitmapContextRef);
        CGImageRelease(bitmapImage);
    

    它的原理是,将 CIImage 绘制到一个灰度图上下文中(CGColorSpaceCreateDeviceGray),根据给定的 size,对画布和图像进行缩放。

    这样生成的图片如下所示

    CorrectQRImage.png

    可以看出二维码的确清晰了很多,但是没有博客具体说明了真正影响清晰度的原因,实际上影响清晰度的原因是这一句代码

    CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone);//允许上下文在各个保真度等级插入像素
    

    这里对图像插值质量做了设置,图像插值的概念如下

    插值是对原图像的像素重新分布,从而来改变像素数量的一种方法。在图像放大过程中,像素也相应地增加,增加的过程就是“插值”发生作用的过程,“插值”程序自动选择信息较好的像素作为增加、弥补空白像素的空间,而并非只使用临近的像素,所以在放大图像时,图像看上去会比较平滑、干净。不过需要说明的是插值并不能增加图像信息,尽管图像尺寸变大,但效果也相对要模糊些,过程可以理解为白酒掺水。

    而 CGContextSetInterpolationQuality 可能默认采用了高品质的插值函数,导致生成的图片由于进行了冗余的像素插值而变得模糊,因此将插值品质设置为 NONE,禁止进行插值操作,就可以保证像素按照原本的分布来缩放了。

    猜测三种插值品质枚举值对应的插值算法:

    • Low: Nearest neighbor interpolation

    • Medium: Linear or bilinear interpolation

    • High: Quadratic or bicubic interpolation

    • 给二维码加水印

    主要原理就是在生成的二维码图片上 drawImage 自己的水印图片

        UIGraphicsBeginImageContextWithOptions(outputImage.size, NO, [[UIScreen mainScreen] scale]);
        [outputImage drawInRect:CGRectMake(0,0 , size, size)];
        UIImage *waterimage = [UIImage imageNamed:@"DogLogo"];
        [waterimage drawInRect:CGRectMake((size - waterImagesize)/2.0, (size - waterImagesize)/2.0, waterImagesize, waterImagesize)];
        UIImage *newPic = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    
    WaterQRImage.png
    • 生成彩色二维码

    彩色二维码原理是遍历图片的每一个像素点,进行 rgb 值的改变。

    
    void ProviderReleaseData (void *info, const void *data, size_t size){
        free((void*)data);
    }
    
    {
        UIColor *targetColor = [UIColor colorWithRed:0.3 green:0.7 blue:0.7 alpha:1.0];
        const CGFloat *components = CGColorGetComponents(targetColor.CGColor);
        CGFloat red = components[0] * 255;
        CGFloat green = components[1] * 255;
        CGFloat blue = components[2] * 255;//分别获取 RGB 值
        
        int imageWidth = outputImage.size.width;
        int imageHeight = outputImage.size.height;
        
        size_t bytesPerRow = imageWidth * 4;
        uint32_t *rgbImageBuf = (uint32_t*)malloc(bytesPerRow * imageHeight);
        CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
        CGContextRef rgbContext = CGBitmapContextCreate(rgbImageBuf, imageWidth, imageHeight, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
        CGContextDrawImage(rgbContext, CGRectMake(0, 0, imageWidth, imageHeight), outputImage.CGImage); //绘制图像到rgb上下文中
        
        int pixelNum = imageWidth * imageHeight; //像素总数,每一个像素包含rgba四个通道
        uint32_t *pCurPtr = rgbImageBuf;
        for (int i = 0; i < pixelNum; i++, pCurPtr++){ //遍历像素
            if ((*pCurPtr & 0xFFFFFF00) < 0x99999900){ // 按rgb区分
                uint8_t *ptr = (uint8_t*)pCurPtr;
                ptr[3] = red;
                ptr[2] = green;
                ptr[1] = blue;
            }else{
                uint8_t* ptr = (uint8_t*)pCurPtr;
                ptr[0] = 255;
            }
        }
        
        // rgbImageBuf 里包含了 image 的像素信息
        CGDataProviderRef dataProvider = CGDataProviderCreateWithData(NULL, rgbImageBuf, bytesPerRow * imageHeight, ProviderReleaseData);
        CGImageRef imageRef = CGImageCreate(imageWidth, // 图片的宽度
                                            imageHeight, // 图片的高度
                                            8, // 一个component占据多少字节
                                            32, // 每一个像素占用的bits
                                            bytesPerRow, // 每一行占用多少bytes,宽度乘以 4
                                            colorSpace,
                                            kCGImageAlphaLast | kCGBitmapByteOrder32Little,
                                            dataProvider, // 数据源提供者
                                            NULL, // 一个解码数组
                                            true, // 抗锯齿参数
                                            kCGRenderingIntentDefault); //图片渲染相关参数
        outputImage = [UIImage imageWithCGImage:imageRef];
        CGDataProviderRelease(dataProvider);
    }
    
    ColorfulQRImage.png

    相机识别二维码

    这里不赘述关于 iOS 相机的配置过程,只关注与二维码相关的步骤。

    iOS 有识别二维码的 API,只需要将 AVCaptureMetadataOutput 配置到 session 的输出中,然后设置其 AVCaptureMetadataOutputObjectsDelegate 代理对象,系统检测到二维码后会调用相应的委托方法。

    • 配置 AVCaptureMetadataOutput
    - (AVCaptureMetadataOutput *)metaDataOutput
    {
        if (!_metaDataOutput) {
            _metaDataOutput = [[AVCaptureMetadataOutput alloc] init];
            [_metaDataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
            _metaDataOutput.rectOfInterest = CGRectMake(0, 0, 1, 1);
        }
        return _metaDataOutput;
    }
    
    {
        // 配置QROutput
        if ([self.captureSession canAddOutput:self.metaDataOutput]){
            // 顺序相反会崩溃,因为未配置到 session 时 availableMetadataObjectTypes 为空
            [self.captureSession addOutput:self.metaDataOutput];
            self.metaDataOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode];
        }
    }
    
    • 回调方法
    - (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray<AVMetadataMachineReadableCodeObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
        if (metadataObjects.count == 0) {
            return;
        }
        NSString *result = [metadataObjects.firstObject stringValue];
        NSLog(@"%@", result);
    }
    

    这里要注意一点,在设置 metaDatOutput 时设置了一个 rectOfInterest 属性,它能限制扫描二维码的区域,它定义了对相机采集的每一帧图像里代码真正感兴趣的检测区域,它有两个特点

    • rectOfInterest 结构体的四个值取值范围在 0~1,即指明对于每一帧,其关心的范围的相对比例
    • rectOfInterest 默认是横屏模式,其 x y 值与通常坐标系相反,也就是说它的真正含义是 CGRectMake(y坐标, x坐标, 高度, 宽度)

    当然默认情况下 rectOfInterest 的值是 CGRectMake(0, 0, 1, 1),即整个帧范围。

    识别内存中二维码图片

    对于网络或者相册选取后读到内存中的图片,以及项目中自带的图片,要检测二维码信息就比较简单,直接使用 CIDetector 就可以检测。

        CIContext *context = [CIContext contextWithOptions:nil];
        CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
        CIImage *targetImage = [CIImage imageWithCGImage:[UIImage imageNamed:@"CorrectQRImage.png"].CGImage];
        NSArray *features = [detector featuresInImage:targetImage];
        
        if (features.count == 0) {
            
        } else {
            CIQRCodeFeature *feature = features.firstObject;
            NSLog(@"%@", feature.messageString);
        }
    

    可以看到,CIDetector 会返回一个数组,其中包含多个 CIQRCodeFeature,对应此图片包含的多个二维码对象,CIQRCodeFeature 对象则包含了具体的编码信息。

    相关文章

      网友评论

          本文标题:二维码生成与检测

          本文链接:https://www.haomeiwen.com/subject/flipqftx.html