使用 Vision 提取文档数据
Extract document data using Vision
2021年6月10日
一句话判断
Vision 框架的文档分析能力在 iOS 15 终于从「能 OCR」进化到「能理解文档结构」——表格识别和结构化文本提取让发票、收据类 app 的开发成本大幅降低。
这场 Session 讲了什么
Vision 框架在文本识别方面的能力持续增强。iOS 15 引入了 VNRecognizeTextRequest 2.0,除了识别文字内容,还能分析文档的版面结构。
最大的新功能是结构化数据提取。VNDocumentSegmentationRequest 可以将文档中的文本按逻辑区域分割:标题、正文、表格、页眉页脚等。配合 VNRecognizeTextRequest,你可以把一张发票照片解析成结构化的 JSON 数据(商家名称、日期、金额、商品列表),而不仅仅是原始文字。
文本识别的精度也有提升。中文识别的支持在 iOS 15 得到了显著改善,特别是手写体中文的识别率。还新增了对日文和韩文的支持。
Session 还介绍了 VNDocumentCameraViewController(属于 VisionKit),这是一个系统级的文档扫描 UI 组件。你只需要 present 这个控制器,就能获得完整的拍照、裁切、透视矫正流程。
值得深挖的点
表格识别的技术细节
Vision 的表格识别能力基于行列检测算法。它会分析文档中的线条和文字对齐模式,推断出表格结构。识别结果包含单元格的边界框和行列索引,你可以据此重建整个表格数据。
但局限性在于:不规则的表格(合并单元格、嵌套表格)识别率很低。如果你的目标文档包含复杂表格结构,建议先用 VNRecognizeTextRequest 提取所有文字,然后用自定义逻辑解析。
自定义数据提取管线
Session 推荐的处理管线是:先用 VNDocumentSegmentationRequest 分割文档区域,然后对每个区域分别运行 VNRecognizeTextRequest。这样可以针对不同区域使用不同的后处理逻辑——比如对表格区域做行列解析,对标题区域做命名实体提取。
代码片段
// 结构化文档识别管线
import Vision
class DocumentScanner {
// 步骤一:识别文本
func recognizeText(image: CGImage) throws -> [VNRecognizedTextObservation] {
let request = VNRecognizeTextRequest()
request.recognitionLevel = .accurate
request.recognitionLanguages = ["zh-Hans", "en"] // 支持中英文混合
request.usesLanguageCorrection = true
let handler = VNImageRequestHandler(cgImage: image, options: [:])
try handler.perform([request])
return request.results ?? []
}
// 提取发票信息
func extractInvoiceData(observations: [VNRecognizedTextObservation]) -> InvoiceData {
var invoice = InvoiceData()
for observation in observations {
guard let text = observation.topCandidates(1).first?.string else { continue }
// 根据关键词和位置提取字段
if text.contains("金额") || text.contains("合计") {
// 提取金额数字
if let amount = extractNumber(from: text) {
invoice.totalAmount = amount
}
}
if text.contains("日期") {
if let date = extractDate(from: text) {
invoice.date = date
}
}
if text.contains("公司") || text.contains("有限公司") {
invoice.companyName = text
}
}
return invoice
}
func extractNumber(from text: String) -> Double? {
let pattern = #"[\d,]+\.?\d*"#
guard let match = text.range(of: pattern, options: .regularExpression) else { return nil }
let numberStr = String(text[match]).replacingOccurrences(of: ",", with: "")
return Double(numberStr)
}
}
struct InvoiceData {
var companyName: String = ""
var date: String = ""
var totalAmount: Double = 0
var items: [(name: String, price: Double)] = []
}
// 使用 VisionKit 的文档扫描 UI
import VisionKit
class ScanViewController: UIViewController {
func presentDocumentScanner() {
// 检查设备是否支持文档扫描
guard VNDocumentCameraViewController.isSupported else {
print("设备不支持文档扫描")
return
}
let scanner = VNDocumentCameraViewController()
scanner.delegate = self
present(scanner, animated: true)
}
}
extension ScanViewController: VNDocumentCameraViewControllerDelegate {
func documentCameraViewController(
_ controller: VNDocumentCameraViewController,
didFinishWith scan: VNDocumentCameraScan
) {
// scan 包含用户拍摄的所有页面
for pageIndex in 0..<scan.pageCount {
let image = scan.imageOfPage(at: pageIndex)
// 对每页进行 OCR 处理
processPage(image: image)
}
controller.dismiss(animated: true)
}
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
controller.dismiss(animated: true)
}
}
最佳实践
如果你的 app 需要扫描多种类型的文档(发票、名片、身份证),不要用一个通用的 OCR 管线。为每种文档类型设计专门的提取逻辑,利用该文档类型的已知结构(比如发票的金额通常在右下角,名片的名字通常在顶部)。
中文识别建议同时设置 recognitionLanguages 为 ["zh-Hans", "en"]。大多数中文文档会包含一些英文(公司名称、金额),设置双语言支持可以避免英文字母被错误识别为中文字符。
还有什么值得关注
VNRecognizeTextRequest的minimumTextHeight参数可以过滤掉太小的文字(比如水印、注释),提高识别精度。- VisionKit 的扫描结果会自动做透视矫正和增强,不需要自己实现 OpenCV 的文档矫正算法。
- 如果你的 app 只需要简单的文字识别(不需要文档结构分析),用
UITextField的 Live Text 功能就够了——iOS 15 支持在相机取景器中直接选取文字。