iOS边缘检测(Document Scanner)

简介: iOS边缘检测(Document Scanner)
  • 1.1  CoreImage的四种识别功能
  • 1.2 边缘检测思路
  • 1.3 用高精度边缘识别器识别特征
  • 1.4 绘制边缘检测图层
  • 1.5 Swift 版本
  • 2.1 生成二维码
  • 2.2  生成条码
  • 2.3 读取二维码(二维码识别)
  • 2.4  第三方框架

引言

为了提升用户体验,在OCR识别场景都将利用到边缘检测

image.png

涉及的权限

NSCameraUsageDescription

从CSDN下载Demo源码:https://download.csdn.net/download/u011018979/19260280

1、应用场景:为了提升用户体验,在OCR识别场景都将利用到边缘检测 2、原理:采用原生CoreImage框架下CIDetector可进行边缘检测,识别到边缘之后使用CAShapeLayer将边缘绘制并显示

3、原理文章:https://kunnan.blog.csdn.net/article/details/117367345

3、付费文章:iOS Document Scanner:矩形边缘识别(边缘检测 ) CIDetectorTypeRectangle

本文限时免费

I 、矩形边缘识别

1.1  CoreImage的四种识别功能

CoreImageCIDetector.h自带了四种识别功能

/* 人脸识别 */
CORE_IMAGE_EXPORT NSString* const CIDetectorTypeFace NS_AVAILABLE(10_7, 5_0);
/* 矩形边缘识别 */
CORE_IMAGE_EXPORT NSString* const CIDetectorTypeRectangle NS_AVAILABLE(10_10, 8_0);
/* 二维码识别 */
CORE_IMAGE_EXPORT NSString* const CIDetectorTypeQRCode NS_AVAILABLE(10_10, 8_0);
/* 文本识别 */
#if __OBJC2__
CORE_IMAGE_EXPORT NSString* const CIDetectorTypeText NS_AVAILABLE(10_11, 9_0);

1.2 边缘检测思路

采用原生CoreImage框架下CIDetector可进行边缘检测

[CIDetector detectorOfType:CIDetectorTypeRectangle context:nil options:@{CIDetectorAccuracy : CIDetectorAccuracyHigh}];

识别到边缘之后使用CAShapeLayer将边缘绘制并显示

// 将图像空间的坐标系转换成uikit坐标系
TransformCIFeatureRect featureRect = [self transfromRealRectWithImageRect:imageRect topLeft:topLeft topRight:topRight bottomLeft:bottomLeft bottomRight:bottomRight];
// 边缘识别路径
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:featureRect.topLeft];
[path addLineToPoint:featureRect.topRight];
[path addLineToPoint:featureRect.bottomRight];
[path addLineToPoint:featureRect.bottomLeft];
[path closePath];
// 背景遮罩路径
UIBezierPath *rectPath  = [UIBezierPath bezierPathWithRect:CGRectMake(-5,
                                                                      -5,
                                                                      self.frame.size.width + 10,
                                                                      self.frame.size.height + 10)];
[rectPath setUsesEvenOddFillRule:YES];
[rectPath appendPath:path];
_rectOverlay.path = rectPath.CGPath;

image.png

  • 边缘识别器
// 高精度边缘识别器
- (CIDetector *)highAccuracyRectangleDetector
{
    static CIDetector *detector = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
                  {
                      detector = [CIDetector detectorOfType:CIDetectorTypeRectangle context:nil options:@{CIDetectorAccuracy : CIDetectorAccuracyHigh}];
                  });
    return detector;
}
// 低精度边缘识别器
- (CIDetector *)rectangleDetetor
{
    static CIDetector *detector = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^
                  {
                      detector = [CIDetector detectorOfType:CIDetectorTypeRectangle context:nil options:@{CIDetectorAccuracy : CIDetectorAccuracyLow,CIDetectorTracking : @(YES)}];
                  });
    return detector;
}

开启边缘检测

/// 开启边缘检测
@property (nonatomic,assign,getter=isBorderDetectionEnabled) BOOL enableBorderDetection;
// 拍照视图
@property (nonatomic, strong) KNCameraCaptureView *captureCameraView;
// 拍照视图
- (KNCameraCaptureView *)captureCameraView{
    if (!_captureCameraView) {
        _captureCameraView = [[KNCameraCaptureView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, 300)];
                //打开边缘检测
        [_captureCameraView setEnableBorderDetection:YES];
        _captureCameraView.backgroundColor = kBlackColor;
    }
    return _captureCameraView;
}

AVCaptureVideoDataOutputSampleBufferDelegate

#pragma mark - AVCaptureVideoDataOutputSampleBufferDelegate
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    if (self.forceStop || _isStopped || _isCapturing || !CMSampleBufferIsValid(sampleBuffer)) return;
    CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
    CIImage *image = [CIImage imageWithCVPixelBuffer:pixelBuffer];
    image = [self filteredImageUsingContrastFilterOnImage:image];
    if (self.isBorderDetectionEnabled)//开启了边缘检测
    {
        if (_borderDetectFrame)//开启了边缘识别
        {
            // 用高精度边缘识别器 识别特征
            NSArray <CIFeature *>*features = [[self highAccuracyRectangleDetector] featuresInImage:image];
            // 选取特征列表中最大的矩形
            _borderDetectLastRectangleFeature = [self biggestRectangleInRectangles:features];
            _borderDetectFrame = NO;
        }
        if (_borderDetectLastRectangleFeature)
        {
            _imageDedectionConfidence += .5;
//            image = [self drawHighlightOverlayForPoints:image topLeft:_borderDetectLastRectangleFeature.topLeft topRight:_borderDetectLastRectangleFeature.topRight bottomLeft:_borderDetectLastRectangleFeature.bottomLeft bottomRight:_borderDetectLastRectangleFeature.bottomRight];
            // draw border layer
            if (rectangleDetectionConfidenceHighEnough(_imageDedectionConfidence))
            {
                [self drawBorderDetectRectWithImageRect:image.extent topLeft:_borderDetectLastRectangleFeature.topLeft topRight:_borderDetectLastRectangleFeature.topRight bottomLeft:_borderDetectLastRectangleFeature.bottomLeft bottomRight:_borderDetectLastRectangleFeature.bottomRight];
            }
        }
        else
        {
            _imageDedectionConfidence = 0.0f;
            if (_rectOverlay) {
                _rectOverlay.path = nil;
            }
        }
    }
    if (self.context && _coreImageContext)
    {
        // 将捕获到的图片绘制进_coreImageContext
        [_coreImageContext drawImage:image inRect:self.bounds fromRect:image.extent];
        [self.context presentRenderbuffer:GL_RENDERBUFFER];
        [_glkView setNeedsDisplay];
    }
}

1.4 绘制边缘检测图层

// 绘制边缘检测图层
- (void)drawBorderDetectRectWithImageRect:(CGRect)imageRect topLeft:(CGPoint)topLeft topRight:(CGPoint)topRight bottomLeft:(CGPoint)bottomLeft bottomRight:(CGPoint)bottomRight
{
    if (!_rectOverlay) {
        _rectOverlay = [CAShapeLayer layer];
        _rectOverlay.fillRule = kCAFillRuleEvenOdd;
        _rectOverlay.fillColor = [UIColor colorWithRed:73/255.0 green:130/255.0 blue:180/255.0 alpha:0.4].CGColor;
        _rectOverlay.strokeColor = [UIColor whiteColor].CGColor;
        _rectOverlay.lineWidth = 5.0f;
    }
    if (!_rectOverlay.superlayer) {
        self.layer.masksToBounds = YES;
        [self.layer addSublayer:_rectOverlay];
    }
    // 将图像空间的坐标系转换成uikit坐标系
    TransformCIFeatureRect featureRect = [self transfromRealRectWithImageRect:imageRect topLeft:topLeft topRight:topRight bottomLeft:bottomLeft bottomRight:bottomRight];
    // 边缘识别路径
    UIBezierPath *path = [UIBezierPath new];
    [path moveToPoint:featureRect.topLeft];
    [path addLineToPoint:featureRect.topRight];
    [path addLineToPoint:featureRect.bottomRight];
    [path addLineToPoint:featureRect.bottomLeft];
    [path closePath];
    // 背景遮罩路径
    UIBezierPath *rectPath  = [UIBezierPath bezierPathWithRect:CGRectMake(-5,
                                                                          -5,
                                                                          self.frame.size.width + 10,
                                                                          self.frame.size.height + 10)];
    [rectPath setUsesEvenOddFillRule:YES];
    [rectPath appendPath:path];
    _rectOverlay.path = rectPath.CGPath;
}

1.5 Swift 版本

  • swift
//
import CoreImage
import UIKit
public final class CIImageRectangleDetector: ImageRectangleDetector {
 public func detect(image: UIImage, completion: @escaping Completion) {
  DispatchQueue.global().async {
   guard let ciImage = CIImage(image: image) else { return }
   guard let detector = CIDetector(ofType: CIDetectorTypeRectangle, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh]) else { return }
   let results = detector.features(in: ciImage)
   let sortedBySize = results.sorted { $0.bounds.area > $1.bounds.area }
   if let feature = sortedBySize.first as? CIRectangleFeature {
    let size = ciImage.extent.size
    let points = [feature.topLeft, feature.topRight, feature.bottomRight, feature.bottomLeft]
    let normalized = points.map { $0.scaledRelative(size: size) }
    let quad = Quad(clockwise: normalized)
    DispatchQueue.main.async {
     completion(quad)
    }
   } else {
    DispatchQueue.main.async {
     completion(nil)
    }
   }
  }
 }
}

II、二维码

从iOS7开始集成了二维码的生成和读取功能

2.1 生成二维码

步骤;

1.导入CoreImage框架 2.通过滤镜CIFilter生成二维码

二维码的内容(传统的条形码只能放数字):纯文本、名片、URL

/**
 生成QR二维码
 @param text 字符串
 @param size 二维码大小
 @return 返回二维码图像
 */
+ (UIImage*)createQRWithString:(NSString*)text QRSize:(CGSize)size;
/**
 生成QR二维码
 @param text 字符串
 @param size 大小
 @param qrColor 二维码前景色
 @param bkColor 二维码背景色
 @return 二维码图像
 */
+ (UIImage*)createQRWithString:(NSString*)text QRSize:(CGSize)size QRColor:(UIColor*)qrColor bkColor:(UIColor*)bkColor;
  • 基本用法
#pragma mark - QRCodeGenerator
+ (CIImage *)createQRForString:(NSString *)qrString {
    NSData *stringData = [qrString dataUsingEncoding:NSUTF8StringEncoding];
    // 创建filter
    CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    // 设置内容和纠错级别
    [qrFilter setValue:stringData forKey:@"inputMessage"];
    [qrFilter setValue:@"H" forKey:@"inputCorrectionLevel"];
    // 返回CIImage
    return qrFilter.outputImage;
}

支持 二维码大小设置

+ (UIImage*)createQRWithString:(NSString*)text QRSize:(CGSize)size
{
    NSData *stringData = [text dataUsingEncoding: NSUTF8StringEncoding];
    //生成
    CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    [qrFilter setValue:stringData forKey:@"inputMessage"];
    [qrFilter setValue:@"H" forKey:@"inputCorrectionLevel"];
    CIImage *qrImage = qrFilter.outputImage;
    //绘制
    CGImageRef cgImage = [[CIContext contextWithOptions:nil] createCGImage:qrImage fromRect:qrImage.extent];
    UIGraphicsBeginImageContext(size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetInterpolationQuality(context, kCGInterpolationNone);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextDrawImage(context, CGContextGetClipBoundingBox(context), cgImage);
    UIImage *codeImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    CGImageRelease(cgImage);
    return codeImage;
}

支持二维码前/背景色设置

+ (UIImage*)createQRWithString:(NSString*)text QRSize:(CGSize)size QRColor:(UIColor*)qrColor bkColor:(UIColor*)bkColor
{
    NSData *stringData = [text dataUsingEncoding: NSUTF8StringEncoding];
    //生成
    CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
    [qrFilter setValue:stringData forKey:@"inputMessage"];
    [qrFilter setValue:@"H" forKey:@"inputCorrectionLevel"];
    //上色
    CIFilter *colorFilter = [CIFilter filterWithName:@"CIFalseColor"
                                       keysAndValues:
                             @"inputImage",qrFilter.outputImage,
                             @"inputColor0",[CIColor colorWithCGColor:qrColor.CGColor],
                             @"inputColor1",[CIColor colorWithCGColor:bkColor.CGColor],
                             nil];
    CIImage *qrImage = colorFilter.outputImage;
    //绘制
    CGImageRef cgImage = [[CIContext contextWithOptions:nil] createCGImage:qrImage fromRect:qrImage.extent];
    UIGraphicsBeginImageContext(size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetInterpolationQuality(context, kCGInterpolationNone);
    CGContextScaleCTM(context, 1.0, -1.0);
    CGContextDrawImage(context, CGContextGetClipBoundingBox(context), cgImage);
    UIImage *codeImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    CGImageRelease(cgImage);
    return codeImage;
}

2.2  生成条码

利用CIFilter生成条码

/**
 生成条形码
 @param text 字符串
 @param size 大小
 @return 返回条码图像
 */
+ (UIImage*)createBarCodeWithString:(NSString*)text QRSize:(CGSize)size
{
    NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:false];
    CIFilter *filter = [CIFilter filterWithName:@"CICode128BarcodeGenerator"];
    [filter setValue:data forKey:@"inputMessage"];
     CIImage *barcodeImage = [filter outputImage];
    // 消除模糊
    CGFloat scaleX = size.width / barcodeImage.extent.size.width; // extent 返回图片的frame
    CGFloat scaleY = size.height / barcodeImage.extent.size.height;
    CIImage *transformedImage = [barcodeImage imageByApplyingTransform:CGAffineTransformScale(CGAffineTransformIdentity, scaleX, scaleY)];
    return [UIImage imageWithCIImage:transformedImage];
}

2.3 读取二维码(二维码识别)

需要导入AVFoundation框架,利用摄像头识别二维码中的内容(模拟器不行)

1.输入(摄像头) 2.由会话将摄像头采集到的二维码图像转换成字符串数据 3.输出(数据) 4.由预览图层显示扫描场景

  • detectorOfType:CIDetectorTypeQRCode
// 声明一个 CIDetector,并设定识别类型 CIDetectorTypeQRCode
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
    // 取得识别结果
    NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
  • 识别条码图片
#pragma mark --识别条码图片
+ (void)recognizeImage:(UIImage*)image success:(void(^)(NSArray<LBXScanResult*> *array))block;
{
    if (!image) {
        block(nil);
        return;
    }
    if (@available(iOS 8.0, *)) {
        CIImage * cimg = [CIImage imageWithCGImage:image.CGImage];
        if (!cimg) {
            block(nil);
            return;
        }
        NSArray *features = nil;
        @try {
            CIDetector*detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh }];
            features = [detector featuresInImage:cimg];
        } @catch (NSException *exception) {
            block(nil);
            return;
        } @finally {
        }
        NSMutableArray<LBXScanResult*> *mutableArray = [[NSMutableArray alloc]initWithCapacity:1];
        for (int index = 0; index < [features count]; index ++)
        {
            CIQRCodeFeature *feature = [features objectAtIndex:index];
            NSString *scannedResult = feature.messageString;
            LBXScanResult *item = [[LBXScanResult alloc]init];
            item.strScanned = scannedResult;
            item.strBarCodeType = CIDetectorTypeQRCode;
            item.imgScanned = image;
            [mutableArray addObject:item];
        }
        if (block) {
            block(mutableArray);
        }
    }else{
        if (block) {
            LBXScanResult *result = [[LBXScanResult alloc]init];
            result.strScanned = @"只支持ios8.0之后系统";
            block(@[result]);
        }
    }
}

2.4  第三方框架

LBXZBarSDK

pod 'LBXScan/LBXNative','~> 2.5.1' #系统原生API封装库
pod 'LBXScan/LBXZXing','~> 2.5.1'
pod 'LBXScan/UI','~> 2.5.1'
#pod 'LBXZBarSDK','~> 1.3'# 删除UIWebView,相机采集分辨率设置高分辨率

iOS安全之UIWebView 被拒的解决方案:用更安全的WKWebView替代UIWebView| 蓄力计划

https://kunnan.blog.csdn.net/article/details/115673455

see also

目录
相关文章
|
iOS开发
ios获取Document文件夹路径
ios获取Document文件夹路径
213 0
|
20小时前
|
编解码 Android开发 iOS开发
深入探索Android与iOS开发的差异与挑战
【6月更文挑战第24天】在移动应用开发的广阔舞台上,Android和iOS两大操作系统扮演着主角。它们各自拥有独特的开发环境、工具集、用户基础及市场策略。本文将深度剖析这两个平台的开发差异,并探讨开发者面临的挑战,旨在为即将踏入或已在移动开发领域奋斗的开发者提供一份实用指南。
18 13
|
3天前
|
iOS开发 开发者 容器
探索iOS开发中的SwiftUI框架
【6月更文挑战第21天】本文深入探讨了苹果在iOS开发中推出的SwiftUI框架,旨在为开发者提供一种声明式、更简洁的界面设计方法。文章首先概述了SwiftUI的核心概念和优势,接着通过一个天气预报应用实例,详细讲解了如何使用SwiftUI进行布局和用户界面的设计。此外,还讨论了SwiftUI与UIKit的差异,以及如何将SwiftUI集成到现有的项目中。最后,文章展望了SwiftUI的未来发展方向,包括潜在的改进和新特性。
|
3天前
|
Java 开发工具 Android开发
安卓与iOS开发差异解析
【6月更文挑战第21天】本文旨在深入探讨安卓和iOS两大移动操作系统在应用开发过程中的主要差异。通过对比分析,揭示各自的设计哲学、编程语言选择、用户界面构建、性能优化策略以及发布流程的异同。文章将提供开发者视角下的实用信息,帮助他们更好地理解各自平台的特点和挑战,从而做出更明智的开发决策。
|
4天前
|
Java 开发工具 Android开发
探索安卓与iOS开发的核心差异
【6月更文挑战第20天】在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文将深入探讨这两大操作系统在开发过程中的主要区别,包括编程语言、开发工具、用户界面设计哲学、系统架构以及市场分布等方面。通过对这些关键差异的分析,旨在为开发者提供一份实用的指南,帮助他们在面对项目决策时,能够更加明智地选择合适的平台,并针对特定平台优化他们的应用。
|
4天前
|
开发工具 Android开发 iOS开发
探索安卓与iOS开发的差异:从工具到用户体验
【6月更文挑战第20天】在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文将深入探讨这两个操作系统在开发环境、编程语言、用户界面设计以及性能优化等方面的关键差异。我们将通过比较分析,揭示各自平台的独特优势和面临的挑战,为开发者提供决策参考,并为最终用户提供更深层次的用户体验洞察。
|
6天前
|
Java Android开发 Swift
探索Android与iOS开发的差异:平台选择对项目成功的影响
【6月更文挑战第18天】在移动应用开发的广阔天地中,Android和iOS两大平台各据一方,它们在市场份额、用户群体及开发环境上各有千秋。本文将深入分析这两个操作系统的开发差异,探讨如何根据项目需求选择合适的平台,并讨论跨平台解决方案的可行性与挑战。我们将通过实际案例,揭示平台选择对项目成功的关键性影响,为开发者提供决策支持。
|
6天前
|
Swift iOS开发 开发者
探索iOS开发中的SwiftUI框架
在苹果的生态系统中,SwiftUI代表了iOS应用开发的一次重大飞跃。作为一项现代化的UI工具集,它旨在简化和加速界面设计过程,同时确保代码的清晰度与可维护性。本文将深入探讨SwiftUI的核心概念、优势以及在实际开发中的应用案例,为开发者提供全面而实用的指南。
|
7天前
|
安全 IDE Android开发
探索Android与iOS开发的差异:平台特性与编程实践
【6月更文挑战第17天】在移动应用开发的广阔天地中,Android和iOS两大平台各自占据半壁江山。它们在用户群体、系统架构以及开发环境上的差异,为开发者带来了不同的挑战和机遇。本文深入探讨了这两个平台在技术实现、界面设计、性能优化等方面的主要区别,并提供了实用的开发建议,旨在帮助开发者更好地理解各自平台的特性,从而创造出更加优秀的移动应用。
|
10天前
|
安全 Android开发 iOS开发
探索Android与iOS开发的差异:平台特性与用户体验的对比分析
在移动应用开发的广阔天地中,Android和iOS两大阵营各据一方。本文将深入探讨这两个操作系统在开发环境、编程语言、用户界面设计及市场分布等方面的主要区别。通过比较分析,我们将揭示各自平台的特有优势,并讨论如何根据目标受众和业务需求选择适合的开发平台。