用苹果官方 API 实现 iOS 备忘录的扫描文稿功能

2022-07-28 12:57:49 浏览数 (1)

用苹果官方 API 实现 iOS 备忘录的扫描文稿功能

访问我的博客 www.fatbobman.com[1] ,可以获得更好的阅读体验

iOS 系统自带的备忘录(Notes)在其质朴名称下提供了众多强大的功能,扫描文稿是我使用较多的功能之一。很早前便想在【健康笔记[2]】之中提供类似的功能,但考虑到其涉及的知识点较多,迟迟没有下手。最近在空闲时,将近年 WWDC 中涉及该功能实现的专题梳理、学习了一遍,受益匪浅。苹果官方早已为我们准备了所需的一切工具。本文将介绍如何通过 VisionKit、Vision、NaturalLanguage、CoreSpotlight 等系统框架实现与备忘录扫描文稿类似的功能。

用 VisionKit 拍摄适合识别的图片

VisionKit 介绍

VisionKit 是一个小框架,可以让你的应用程序使用系统的文档扫描仪。使用 VNDocumentCameraViewController 呈现覆盖整个屏幕的相机视图。通过在视图控制器中实现 VNDocumentCameraViewControllerDelegate,接收来自文档相机的回调,例如完成扫描。

通过同备忘录(Notes)一致的文档扫描外观,让开发者获得拍摄及图片处理能力(透视变换、颜色处理等)。

IMG_1938

VisionKit 使用方法

VisionKit 框架目标明确、无需配置,使用异常简单。

在 app 中申请相机的使用权限

在 info 中添加 NSCameraUsageDescription 键,填写使用相机的原因。

image-20211109184955837

创建 VNDocumentCameraViewController

VNDocumentCameraViewController 并没有提供任何的配置选项,只需要声明一个它的实例便可使用。

下面的代码为在 SwiftUI 中使用的方式:

代码语言:javascript复制
import VisionKit struct VNCameraView: UIViewControllerRepresentable {    @Binding var pages:[ScanPage]    @Environment(.dismiss) var dismiss     typealias UIViewControllerType = VNDocumentCameraViewController     func makeUIViewController(context: Context) -> VNDocumentCameraViewController {        let controller = VNDocumentCameraViewController()        controller.delegate = context.coordinator        return controller    }     func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: Context) {}     func makeCoordinator() -> VNCameraCoordinator {        VNCameraCoordinator(pages: $pages,dismiss: dismiss)    }} struct ScanPage: Identifiable {    let id = UUID()    let image: UIImage}
实现 VNDocumentCameraViewControllerDelegate

VNDocumentCameraViewControllerDelegate 提供了三个回调方法

•documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan)告诉委托,用户已成功从文档相机保存扫描的文档•documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController)告诉委托,用户已从文档扫描仪相机中取消。•documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error)告诉委托,当相机视图控制器处于活动状态时,文档扫描失败。

代码语言:javascript复制
final class VNCameraCoordinator: NSObject, VNDocumentCameraViewControllerDelegate {    @Binding var pages:[ScanPage]    var dismiss:DismissAction     func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {        for i in 0..<scan.pageCount{            let scanPage = ScanPage(image: scan.imageOfPage(at: i))            pages.append(scanPage)        }        dismiss()    }     func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {        dismiss()    }     func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) {        dismiss()    }     init(pages:Binding<[ScanPage]>,dismiss:DismissAction) {        self._pages = pages        self.dismiss = dismiss    }}

VisionKit 允许使用者连续扫描图片。通过 pageCount 可以查询图片数量,并用 imageOfPage 分别获取。

用户应将扫描图片的方向调整到正确的显示状态,便于下一步的文字识别。

在视图中调用
代码语言:javascript复制
struct ContentView: View {    @State var scanPages = [ScanPage]()    @State var scan = false    var body: some View {        VStack {            Button("Scan") {                scan.toggle()            }            List {                ForEach(scanPages, id: .id) { page in                    HStack{                    Image(uiImage: page.image)                        .resizable()                        .aspectRatio(contentMode: .fit)                        .frame(height: 100)                    }                }            }            .fullScreenCover(isPresented: $scan) {                VNCameraView(pages: $scanPages)                    .ignoresSafeArea()            }        }    }}

至此,你已经获得了同 Notes 完全一致的拍摄扫描图片的功能。

用 Vision 进行文字识别

Vision 介绍

相较 VisionKit 的小巧,Vision 则是一个功能强大、使用范围广泛的大型框架。它应用了计算机视觉算法,对输入的图像和视频执行各种任务。

Vision 框架可以执行人脸和人脸特征点检测、文本检测、条形码识别、图像配准和目标跟踪。Vision 还允许使用自定义的 Core ML 模型来完成分类或物体检测等任务。

在本例中,我们仅需使用 Vision 提供的文本检测(text detection)功能。

如何使用 Vision 进行文字识别

Vision 能够检测和识别图像中的多语言文本,识别过程完全在设备本地进行,保证了用户的隐私。Vision 提供了两种文本的检测路径(算法),分别为 Fast(快速)和 Accurate(精确)。快速非常适合实时读取号码之类的场景,在本例中,由于我们需要对整个文档进行文字处理,选择使用神经网络算法的精确路径更加合适。

在 Vision 中无论进行哪个种类的识别计算,大致的流程都差不太多。

•为 Vision 准备输入图像Vision 使用 VNImageRequestHandler 处理基于图像的请求,并假定图像是直立的,所以在传递图像时要考虑到方向。在本例中,我们将使用 VNDocumentCameraViewController 提供的图像进行处理。•创建 Vision Request首先使用要处理的图像创建一个 VNImageRequestHandler 对象。接下来创建 VNImageBasedRequest 提出识别需求(request)。针对每种识别类型都有对应的 VNImageBasedRequest 子类,本例中,识别文本对应的 request 为 VNRecognizeTextRequest。可以对同一张图片提出多个 request,只需创建并捆绑所有的请求到 VNImageRequestHandler 的实例即可。•解释检测结果可以通过两种方式访问检测结果:一、调用 perform 后检查 results 属性。二、在创建 request 对象时,设置回调方法检索识别信息。回调结果可能包含多个观察结果(observations),需要循环观察数组以处理每个观察结果。

大概的代码如下:

代码语言:javascript复制
import Vision func processImage(image: UIImage) -> String {    guard let cgImage = image.cgImage else {        fatalError()    }    var result = ""    let request = VNRecognizeTextRequest { request, _ in        guard let observations = request.results as? [VNRecognizedTextObservation] else { return }        let recognizedStrings = observations.compactMap { observation in            observation.topCandidates(1).first?.string        }        result = recognizedStrings.joined(separator: " ")    }    request.recognitionLevel = .accurate // 采用精确路径    request.recognitionLanguages = ["zh-Hans", "en-US"] // 设置识别的语言     let requestHandler = VNImageRequestHandler(cgImage: cgImage)    do {        try requestHandler.perform([request])    } catch {        print("error:(error)")    }    return result}

每个被识别的文本段可能包含多个识别结果,通过 topCandidates(n) 设置最多返回几个候选结果。

recognitionLanguages 定义了语言处理和文本识别过程中语言的使用顺序,识别中文时,需将中文设置在首位。

需要识别的文档:

截屏 2021-11-09 下午 4.37.28

此类文档并不适合进行自然语言处理(除非进行大量的深度学习),但健康笔记中将主要保存的此种类型的内容。

识别结果:

代码语言:javascript复制
InBody 身体水分 TinBody770) ID 15904113359 身高 年


	

0 人点赞