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文件夹路径
211 0
|
16天前
|
编解码 iOS开发 开发者
探索iOS开发中的SwiftUI框架
【5月更文挑战第31天】本文将深入探讨SwiftUI框架,这是Apple为iOS应用开发推出的最新用户界面工具包。我们将分析其核心概念、优势以及如何利用SwiftUI简化和加速开发流程,同时也会触及一些常见的挑战和解决方案。
|
1月前
|
前端开发 Android开发 iOS开发
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
【4月更文挑战第30天】Flutter 框架实现跨平台移动应用,通过一致的 UI 渲染(Skia 引擎)、热重载功能和响应式框架提高开发效率和用户体验。然而,Android 和 iOS 的系统差异、渲染机制及编译过程影响性能。性能对比显示,iOS 可能因硬件优化提供更流畅体验,而 Android 更具灵活性和广泛硬件支持。开发者可采用代码、资源优化和特定平台优化策略,利用性能分析工具提升应用性能。
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
|
1天前
|
安全 Android开发 iOS开发
探索Android与iOS开发的差异:平台特性与用户体验的对比分析
在移动应用开发的广阔天地中,Android和iOS两大阵营各据一方。本文将深入探讨这两个操作系统在开发环境、编程语言、用户界面设计及市场分布等方面的主要区别。通过比较分析,我们将揭示各自平台的特有优势,并讨论如何根据目标受众和业务需求选择适合的开发平台。
|
1天前
|
iOS开发 开发者
探索iOS开发中的SwiftUI框架
【6月更文挑战第14天】本文将深入探讨iOS开发领域的新星——SwiftUI框架。我们将从其设计理念出发,逐步解析其结构与核心组件,并通过实例展示如何利用SwiftUI简化界面构建流程,提升开发效率。同时,我们也将讨论SwiftUI在现有项目中的集成策略及其对iOS应用开发未来的可能影响。
7 1
|
2天前
|
安全 Java Android开发
探索Android与iOS开发的差异与挑战
在移动应用开发的广阔天地里,Android和iOS两大平台各自占据半壁江山。本文将深入探讨这两个平台的开发环境、工具、语言以及设计理念的差异,并分析这些差异给开发者带来的挑战。我们将从多个角度出发,包括用户界面设计、性能优化、安全性考量、以及市场分布等方面,为读者提供一个全面的视角,以理解在这两个平台上进行开发时需要考虑的关键因素。
|
2天前
|
Swift iOS开发 开发者
探索iOS开发中的SwiftUI框架
【6月更文挑战第13天】本文将深入探讨iOS开发中的一个重要工具——SwiftUI框架。我们将了解其基本概念,如何在实际项目中应用,以及它为开发者带来的优势和挑战。
|
4天前
|
iOS开发 开发者 UED
探索iOS开发中的SwiftUI框架
在移动应用开发的广阔天地中,苹果公司的SwiftUI框架以其声明式语法和直观布局管理,为iOS开发者带来了新的生产力工具。本文将深入探讨SwiftUI的设计哲学、核心概念以及在实际项目中如何高效运用该框架,旨在为读者提供一份全面的SwiftUI使用指南。
|
4天前
|
API Swift iOS开发
探索iOS开发中的SwiftUI框架
【6月更文挑战第11天】本文将深入探讨iOS开发中的一个重要工具——SwiftUI框架。我们将了解其基本概念,如何在实际项目中应用,以及它如何改变iOS应用的开发方式。
|
5天前
|
编解码 安全 Android开发
探索iOS与Android开发的差异:从界面到性能
【6月更文挑战第10天】在移动应用开发的广阔天地中,iOS和Android两大平台各占山头,它们在设计理念、用户体验、性能优化等方面展现出独特的魅力。本文将深入探讨这两大系统在开发过程中的主要差异,从用户界面设计到性能调优,揭示各自背后的技术逻辑与创新策略,为开发者提供全面的视角和实用的开发指南。