• Swift如何使用Vision来识别获取图片中的文字(OCR),通过SwiftUI视图和终端命令行,以及一系列注意事项


    在过去的一年里,我发现苹果系统中的“文字搜图片”功能非常好用,这个功能不光 iPhone/iPad,Mac 也有,找一些图片真的很好用。但是遇到了一个问题:这个功能需要一段时间才能找到新的图片,而且没法手动刷新,这对于外接硬盘里的图片来说不方便。所以就想自己能不能写一个类似的程序来查找一些图片。

    这个程序的功能还挺好实现的:就是通过图片中的文字或者物体进行查找,而这两个功能苹果都替我们做好了,我们可以做到苹果演示的文本识别和相册识别所能达到的效果。不过本文只讲述如何使用 Vision 识别图片中的文字,因为识别图像和本文类似,存放这些数据到数据库中我也写过如何使用 Core Data 的博客:SwiftUI——Core Data数据库的使用(在纯SwiftUI生命周期中)

    本文较长,建议通过侧边栏跳转阅读。

    简单介绍 Vision

    首先简单介绍一下 Vision:

    Vision 是一个计算机视觉算法的架构,可以对图像和视频执行多种任务。支持 iOS 11/iPad OS 11/macOS 10.13/tvOS 11 或更新系统。支持 ISO 语言代码中的所有语言。
    需要注意由于汉字的复杂性,自定义单词(customWords)功能和语言矫正功能对于中文不可用。

    需要注意 Vision 是包含在这些系统中的,而不是程序里,所以编译出来的程序本身并不会很大,并且结果精度和系统版本挂钩,后续会有演示。但很可惜的是对于中文手写的识别不太好,精度不是很好,但是对于英文的识别还是不错的。

    比如这样的一张手写英文+汉字的图像:
    请添加图片描述

    在最新的 iPad OS 16 中识别出来为:

    请添加图片描述

    中文识别精度可见非常不行。

    测试图片

    测试图片是一张系统截图,不使用手写图的原因上面你也看到了,中文识别很难说达到了可用的程度。

    请添加图片描述

    将其命名为info,放在一个你喜欢的位置和放在 Assets 中,方便后续使用,如下:

    请添加图片描述

    不同平台的代码实现

    接下来将会介绍如何在 iOS/iPad OS 和 macOS 上识别获取图像中的文本,将会分为两部分来说。(按需求来说不应该有 iOS/iPad OS,但是想都试试看,万一用的到呢)

    分为两部分是因为在 iOS/iPad OS 系统上,使用的图像格式为UIImage,而 macOS 中使用的是NSImage,不过二者只有一小部分不一样。

    这里的NS前缀表示“NeXTSTEP”,这是当年乔布斯回到苹果带回来的成果。

    iOS/iPad OS

    这里使用 SwiftUI 来进行布局。

    首先导入框架和库:

    import SwiftUI
    import Vision
    
    • 1
    • 2

    然后新建一个视图,内容如下(为了阅读和复制代码的体验,在注释中解释代码的含义):

    struct ContentView: View {
        //这个字符串数组是为了存放获取的文本
        @State var textStrings = [String]()
        //这个name用来指定使用哪个图像,如果想用其他图像修改这个变量就行
        @State var name = "info"
        
        var body: some View {
            VStack {
                Image(uiImage: UIImage(named: name)!)
                //这个循环是显示获取的文本
                ForEach(textStrings, id: \.self) { testString in
                    Text(testString)
                }
            }
            .padding()
            //这样一打开App就自动识别了
            .onAppear(perform: {
                //生成执行需求的CGImage,也就是对这个图片进行OCR文本识别
                guard let cgImage = UIImage(named: name)?.cgImage else { return }
    
                //创建一个新的图像请求处理器
                let requestHandler = VNImageRequestHandler(cgImage: cgImage)
    
                //创建一个新的识别文本请求
                let request = VNRecognizeTextRequest(completionHandler: handleDetectedText)
                //使用accurate模式识别,不推荐使用fast模式,因为这是采用传统OCR的,精度太差了
                request.recognitionLevel = .accurate
                //设置偏向语言,不加的话会全按照英文和数字识别
        		//中文一起能识别的其他文字只有英文
       		 	//繁体中文为zh-Hant,其他语言码请见https://www.loc.gov/standards/iso639-2/php/English_list.php
                request.recognitionLanguages = ["zh-Hans"]
    
                do {
                    //执行文本识别的请求
                    try requestHandler.perform([request])
                } catch {
                    print("Unable to perform the requests: \(error).")
                }
            })
        }
        //这个函数用来处理获取的文本
        func handleDetectedText(request: VNRequest?, error: Error?) {
            if let error = error {
                print("ERROR: \(error)")
                return
            }
            //results就是获取的结果
            guard let results = request?.results, results.count > 0 else {
                print("No text found")
                return
            }
            
    		//通过循环将results的结果放到textStrings数组中
    		//你可以在这里进行一些处理,比如说创建一个数据结构来获取获取文本区域的位置和大小,或者一些其他的功能。!!!通过observation的属性就可以获取这些信息!!!
            for result in results {
                if let observation = result as? VNRecognizedTextObservation {
                	//topCandidates(1)表示在候选结果里选择第一个,最多有十个,你也可以在这里进行一些处理
                    for text in observation.topCandidates(1) {
                    	//将results的结果放到textStrings数组中
                        let string = text.string
                        textStrings.append(string)
                    }
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    这时候运行就能看到结果了:

    请添加图片描述

    可以看到除了最开始的“展开”符号被识别成v之外,几乎没有识别错误。

    macOS

    接下来先介绍一下如何在 macOS 上实现这个功能。

    首先新建一个空白文本文件ocr.swift,然后输入以下内容:

    import SwiftUI
    import Vision
    import Foundation
    
    func handleDetectedText(request: VNRequest?, error: Error?) {
        if let error = error {
            print("ERROR: \(error)")
            return
        }
        guard let results = request?.results, results.count > 0 else {
            print("No text found")
            return
        }
    
        //通过循环将results的结果全部打印
        //你可以在这里进行一些处理,比如说创建一个数据结构来获取获取文本区域的位置和大小,或者一些其他的功能。!!!通过observation的属性就可以获取这些信息!!!
        for result in results {
            if let observation = result as? VNRecognizedTextObservation {
                //topCandidates(1)表示在候选结果里选择第一个,最多有十个,你也可以在这里进行一些处理
                for text in observation.topCandidates(1) {
                    //打印识别的文本字符串
                    let string = text.string
                    print(string)
                }
            }
        }
    }
    
    func ocrImage(path: String) {
        let cgImage = NSImage(byReferencingFile: path)?.ciImage()?.cgImage
    
        //创建一个新的图像请求处理器
        let requestHandler = VNImageRequestHandler(cgImage: cgImage!)
        //创建一个新的识别文本请求
        let request = VNRecognizeTextRequest(completionHandler: handleDetectedText)
        //使用accurate模式识别,不推荐使用fast模式,因为这是采用传统OCR的,精度太差了
        request.recognitionLevel = .accurate
        //设置偏向语言,不加的话会全按照英文和数字识别
        //中文一起能识别的其他文字只有英文
        //繁体中文为zh-Hant,其他语言码请见https://www.loc.gov/standards/iso639-2/php/English_list.php
        request.recognitionLanguages = ["zh-Hans"]
    
        do {
            //执行文本识别的请求
            try requestHandler.perform([request])
        } catch {
            print("Unable to perform the requests: \(error).")
        }
    }
    
    extension NSImage {
        //NSImage转CIImage
        func ciImage() -> CIImage? {
            guard let data = self.tiffRepresentation,
                  let bitmap = NSBitmapImageRep(data: data) else {
                return nil
            }
            let ci = CIImage(bitmapImageRep: bitmap)
            return ci
        }
    }
    
    //执行函数,从命令行参数中获取图片的地址
    ocrImage(path: CommandLine.arguments[1])
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    然后编译:

    $ swiftc -o ocr ocr.swift
    
    • 1

    运行就可以看到这样的结果:

    $ ./ocr ../info.png 
    通用:
    种类:宗卷
    创建时间:1970年1月1日星期四 08:00
    修改时间:1980年1月1日星期二 00:00
    格式:EXFAT
    容量:511.88 GB
    可用:300.78GB
    已使用:211,106,529,280字节 (磁盘上的
    211.11 GB)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    你可能会发现开头的v不见了,这是因为我使用的 macOS 是 12,而不是最新的,所以和 iOS 16 的结果不一样。

    这个代码你还可以将其放到 Playground 中,可以看到每一步的状况。

    建议你尝试用这个命令识别一些其他的图像,精度还是可以的。

    识别对比和测试

    上面是最理想的情况下测试,接下来进行一些不同设置或情形的识别结果对比,算是一种实验记录了。

    新旧系统对比

    macOS 12 对应的是 iOS 15。上文提到了macOS 12 和 iPadOS 16 的对比,这里记录一下手写文本的识别情况。

    请添加图片描述

    对于上面这张图来说,最新的 iPad OS 16 的结果为:

    请添加图片描述

    很完美。

    而 macOS 12 的结果为:

    $ ./ocr ../hand.jpeg 
    这王-个不焙的决注
    请坚持做下去,别放奔!
    
    • 1
    • 2
    • 3

    可以看到新系统虽然在文章开始的例子表现不是很好,但有时还是很精准的。

    多语言测试

    介绍 Vision 的时候提到中文只能搭配着英文使用,不能和其他语言套用,那么套用了会如何呢?

    请添加图片描述

    上图中是中文、英语、日语的“你好”,如果是在 macOS 12,无论是将识别语言设置成中文、日语或者不设置,都无法将日语识别成日语假名,而是将其识别成数字和英文字母或汉字。比如设置为jajpn

    $ ./ocr ../5.png 
    11$7
    Hello
    Zh-sla
    
    • 1
    • 2
    • 3
    • 4

    但是在 iPadOS 16 上,如果设置为jajpn,那么三种语言都可以识别到(因为日语中也有汉字,所以这样其实不太对,但是应付可以):
    请添加图片描述

    但是如果设置为zh_Hans,那么日语部分根本不显示:
    请添加图片描述

    你可以用俄语ru也做一做测试,可以感觉到中文是被单独拎出来做的,不光不能搭配其他语言,其他语言也不能搭配中文。

    倾斜测试

    我很好奇文本倾斜还能识别出来吗?因为很多 CV 都是要找一个固定对象的,比如识别猫先定位猫胡子(水平的线)。那么 Vision 面对旋转过的文本还能识别出来吗?如果识别不出来,临界值大概是什么角度呢?

    用下面这个图进行测试:

    请添加图片描述

    测试结果发现在旋转 25 到 30 度的时候,开始出现识别错误。当到达 45 度的时候基本上就不可用了。

    这整个项目和后续更新我都放在 https://github.com/ZhongUncle/Swift-Vision-OCR.git,希望能帮到有需要的人~

  • 相关阅读:
    亚商投资顾问 早餐FM/0920 苹果涨2.51%,领涨道指
    小程序源码:修复登录大河盲盒小程序源码,实现运营“玩法自由”,超多功能的盲盒型抽奖挖矿程序源码下载
    【算法题】309. 买卖股票的最佳时机含冷冻期
    龙芯2K0500核心板可以在智能水表产品上的解决方案-迅为电子
    我国数据安全治理研究
    【Lilishop商城】No2-4.确定软件架构搭建三(本篇包括ES检索)
    MySQL的日志
    Java NIO 关键概念之 Buffer
    mysql—多表查询
    REDIS09_HyperLogLog的概述、基本命令、UV、PV、DAU、MAU、首页UV如何进行统计处理
  • 原文地址:https://blog.csdn.net/qq_33919450/article/details/132819164