美文网首页iOS开发心得技巧文字iOS开发
[译]iOS扩充--OCR光学字符识别

[译]iOS扩充--OCR光学字符识别

作者: simuty | 来源:发表于2016-07-29 17:15 被阅读4641次

    原博客地址Tesseract OCR tutorial

    之前在用扫描全能王的时候, 一直在思考这个功能是怎么实现的, 最近找到了一下资料, 和大家一起分享一下.

    1.OCR是什么?

    OCR(Optical Character Recognition) 光学字符识别, 是从图像中电子扫描提取文本的过程, 可以在文档编辑等多种形式重用它,例如: 文本搜索/压缩等用途。

    OCR这是广泛用于你的平板电脑进行扫描文件、手写涂鸦等, 新闻报道(2015/01/14)谷歌将此添加到他们的应用程序之中, 今天我们共同学习在自己的iPhone上如何使用它!

    OCR开源库地址---Tesseract;

    2. 介绍Tesseract

    Tesseract OCR 虽很牛逼,但有以下一些限制:

    
    1. Unlike some OCR engines (like those used by the U.S. Postal Service to sort mail), 
    Tesseract is unable to recognize handwriting and is limited to about 64 fonts in total.
    Tesseract不可以识别出手写的字, 并且字数仅限于约64个 <有些疑惑>;
    2. Tesseract需要一些预处理提高OCR结果;图像需要适当比例,尽可能多的形象对比,有水平排列的文本。
    3. 最后, Tesseract OCR 只能工作在Linux, Windows, and Mac OS X平台之上;
    
    
    Paste_Image.png

    (ーー゛) 那怎么才能在iOS设备上使用呢??

    
    幸运的是: 有Objective-C对Tesseract OCR包装一层的框架可以利用在swift和iOS设备上, 
    不要担心, swift兼容版本包括这个套装软件.
    
    

    3. Love In A Snap

    你怎么都不会想到Ray Wenderlich会在蓝色情人节(Valentine’s Day)前夕更新这篇文章, 对吧? 肯定不会! 我们设法找出万全方法打动你内心的欲望。与你共同创建爱的礼物! -_-

    在这片教程里, 你将会学到怎么使用Google开源的框架---Tesseract, 你可以通过Love In A Snap来让你拍照的爱情诗和“让它自己的”取代原诗人的缪斯女神的名字与自己的感情的对象的名称。准备留下深刻印象吧!

    3.1 开始

    你可以从github 下载初始项目到本地;

    这个压缩包包含几个文件:

    1. LoveInASnap: Xcode项目;
    2. Tesseract Resources: Tesseract框架和语言包;
    3. Image Resources: 几张样张图片包含的文字之后可以使用.
    
    
    Paste_Image.png

    打开当前工程 LoveinASnap.xcodeproj,你会发现在从Storyboard中关联到ViewController.swift中有一些@IBOutlets和空白的@IBAction方法;

    在代码中你看到有两个方法来处理展示和关闭View的活动状态;

    
    func addActivityIndicator() {
      activityIndicator = UIActivityIndicatorView(frame: view.bounds)
      activityIndicator.activityIndicatorViewStyle = .WhiteLarge
      activityIndicator.backgroundColor = UIColor(white: 0, alpha: 0.25)
      activityIndicator.startAnimating()
      view.addSubview(activityIndicator)
    }
     
    func removeActivityIndicator() {
      activityIndicator.removeFromSuperview()
      activityIndicator = nil
    }
    
    

    接下来有一些方法是移动View的位置防止键盘遮到输入框;

    func moveViewUp() {
      if topMarginConstraint.constant != originalTopMargin {
        return
      }
     
      topMarginConstraint.constant -= 135
      UIView.animateWithDuration(0.3, animations: { () -> Void in
        self.view.layoutIfNeeded()
      })
    }
     
    func moveViewDown() {
      if topMarginConstraint.constant == originalTopMargin {
        return
      }
     
      topMarginConstraint.constant = originalTopMargin
      UIView.animateWithDuration(0.3, animations: { () -> Void in
        self.view.layoutIfNeeded()
      })
     
    }
    
    

    最后, 剩余的方法依据用户的交互来处理键盘和View的位置, moveViewUp()moveViewDown()

    @IBAction func backgroundTapped(sender: AnyObject) {
      view.endEditing(true)
      moveViewDown()
    }
     
    func textFieldDidBeginEditing(textField: UITextField) {
      moveViewUp()
    }
     
    @IBAction func textFieldEndEditing(sender: AnyObject) {
      view.endEditing(true)
      moveViewDown()
    }
     
    func textViewDidBeginEditing(textView: UITextView) {
      moveViewDown()
    }
    
    
    

    尽管重要的是用户体验(UX--User Experience),但这些方法是本教程最关键的部分,所以提前准备好了界面, 这样你就可以马上进入有趣的编码阶段。

    在编写代码前, 先编译 运行一个项目, 点击一下界面, 感受一下UI, 目前TextView还不可以编辑, 点击输入框可以吊起和关闭键盘, 你可以为这个APP带来生命! Yes .You Can _

    Paste_Image.png
    3.2 添加Tesseract Framework

    3.2.1.第一步添加TesseractOCR.framework;

    在解压的文件中找到Tesseract Resources文件夹, 在Xcode中添加TesseractOCR.framework, 确保添加时选中的是Copy items if needed, 最终点击Finish;

    3.2.2.第二步添加tessdata

    同样的方式添加tessdata文件夹;

    3.2.3.添加系统依赖库, 包括

    1. libstdc++.6.0.9.dylib(or libstdc++.6.0.9.tbd);
    2. CoreImage.framework;
    3. TesseractOCR.framework
    
    

    3.2.4.配置

    1.选中Build Settings利用上边的搜索框搜索Other Linker Flags, 在已经存在的keys中拼接上-lstdc++<亲测没改没问题>, 然后使用同样的方法在Build Settings下找到C++ Standard Library, 并将之设置为Compiler Default; 在找到Enable Bitcode将之设置为NO;

    2.因为Tesseract是一个OC框架, 所以需要创建一个桥接头文件,

    关于iOS OC与Swift混编的问题就不在此描述了;

    LoveInASnap-Bridging-Header.h中添加

    #import <TesseractOCR/TesseractOCR.h>
    
    
    Paste_Image.png

    至此你就可以在项目中使用该框架了.

    3.3 加载图片

    第一步骤需要在OCR APP 中添加被扫描的图片, 最简单的方法是利用UIImagePickerController来从相册或相机中获取.

    打开ViewController.swifttakePhoto()添加代码, 如下:

    
    @IBAction func takePhoto(sender: AnyObject) {
      // 1. 关闭键盘, 使得view回到初始位置
      view.endEditing(true)
      moveViewDown()
      // 2. 创建一个sheet样式的UIAlertController
      let imagePickerActionSheet = UIAlertController(title: "Snap/Upload Photo",
        message: nil, preferredStyle: .ActionSheet)
      // 3. 打开相机, 如果在模拟器上,将不会出现该选项
      if UIImagePickerController.isSourceTypeAvailable(.Camera) {
        let cameraButton = UIAlertAction(title: "Take Photo",
          style: .Default) { (alert) -> Void in
            let imagePicker = UIImagePickerController()
            imagePicker.delegate = self
            imagePicker.sourceType = .Camera
            self.presentViewController(imagePicker,
              animated: true,
              completion: nil)
        }
        imagePickerActionSheet.addAction(cameraButton)
      }
      // 4 取照片
      let libraryButton = UIAlertAction(title: "Choose Existing",
        style: .Default) { (alert) -> Void in
          let imagePicker = UIImagePickerController()
          imagePicker.delegate = self
          imagePicker.sourceType = .PhotoLibrary
          self.presentViewController(imagePicker,
            animated: true,
            completion: nil)
      }
      imagePickerActionSheet.addAction(libraryButton)
      // 5 取消
      let cancelButton = UIAlertAction(title: "Cancel",
        style: .Cancel) { (alert) -> Void in
      }
      imagePickerActionSheet.addAction(cancelButton)
      // 6 展示提示框
      presentViewController(imagePickerActionSheet, animated: true,
        completion: nil)
    }
    
    

    如前所述列表中的限制条件,必须在一定位置约束才会出现最优OCR图像结果。如果图像太大或太小,Tesseract都可能返回不理想的结果,奇怪的是,崩溃的时候会报出EXC_BAD_ACCESS错误。

    为此,需要写个方法来调整图像在尽可能不扭曲图像的前提下改变其宽高比。

    3.4缩放图像保持长宽比

    图像的宽高比例关系离不开width and height, 在数学中, 减少原始图像的大小不影响长宽比, 但是你必须保持宽高比不变。

    Paste_Image.png

    当你知道一个图片的宽和高, 你所期望的最终宽高可以通过公式重新排列比例来计算:

    Paste_Image.png

    由此产生两个公式:

    1. Height1/Width1 * width2 = height2;
    2. Width1/Height1 * height2 = width2
     
    

    你将使用这两个公式(formulas)来保证宽高比例;

    ViewController.swift,添加如下方法

    func scaleImage(image: UIImage, maxDimension: CGFloat) -> UIImage {
     
      var scaledSize = CGSize(width: maxDimension, height: maxDimension)
      var scaleFactor: CGFloat
     
      if image.size.width > image.size.height {
        scaleFactor = image.size.height / image.size.width
        scaledSize.width = maxDimension
        scaledSize.height = scaledSize.width * scaleFactor
      } else {
        scaleFactor = image.size.width / image.size.height
        scaledSize.height = maxDimension
        scaledSize.width = scaledSize.height * scaleFactor
      }
     
      UIGraphicsBeginImageContext(scaledSize)
      image.drawInRect(CGRectMake(0, 0, scaledSize.width, scaledSize.height))
      let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
      UIGraphicsEndImageContext()
     
      return scaledImage
    }
    
    

    传入maxDimension,该方法以图像的高度和宽度,哪个更大,设置最大的等于maxDimension。然后计算另一边图像基于比例适当,重绘计算原始图像以适应新框架,最后将新扩展的图像返回给调用的方法。

    3.5 实现Tesseract OCR

    ViewController.swift的下侧找到延展的类UIImagePickerControllerDelegate, 加入代码, 如下:

    func imagePickerController(picker: UIImagePickerController,
      didFinishPickingMediaWithInfo info: [String : AnyObject]) {
        let selectedPhoto = info[UIImagePickerControllerOriginalImage] as! UIImage
        let scaledImage = scaleImage(selectedPhoto, maxDimension: 640)
     
        addActivityIndicator()
     
        dismissViewControllerAnimated(true, completion: {
          self.performImageRecognition(scaledImage)
        })
    }
    
    

    imagePickerController(_:didFinishPickingMediaWithInfo:)UIImagePickerDelegate的代理方法, 返回的是选择图片的信息(一个字典), 从info中利用key--UIImagePickerControllerOriginalImage获取到原始图片, 在利用刚写过的方法scaleImage(_:maxDimension:)来重新缩放图片;

    当Tesseract工作时, 可以通过调用addActivityIndicator()来关闭用户交互, 显示活动状态; 然后可以关闭UIImagePicker, 通过performImageRecognition()进行加工;

    添加如下方法, 这里将展示OCR的神奇之处:

    
    func performImageRecognition(image: UIImage) {
      // 1 初始化G8Tesseract的对象tesseract
      let tesseract = G8Tesseract()
      // 2 Tesseract将会从 .traineddata 文件搜索你指定的语言, 将会搜索fra.traineddata<法语>和eng.traineddata<英语>
      tesseract.language = "eng+fra"
      // 3 可以指定三种不同的engineMode类型, TesseractCubeCombined: 最快最不精确, .CubeOnly: 慢点但更加精确, 采用人工智能; .TesseractCubeCombined: 同时包含前两者, 最精确也最慢.
      tesseract.engineMode = .TesseractCubeCombined
      // 4 因为字段不确定, 所以告诉引擎自动判断分段
      tesseract.pageSegmentationMode = .Auto
      // 5 只有TesseractCubeCombined限制,最长扫描时间
      tesseract.maximumRecognitionTime = 60.0
      // 6 为了确保图片的对比度满足要求, Tesseract的过滤器<filter>g8_blackAndWhite()会增大对比度, 已达到更好的效果
      tesseract.image = image.g8_blackAndWhite()
      tesseract.recognize()
      // 7 将获取到的文字展示到textView上
      textView.text = tesseract.recognizedText
      textView.editable = true
      // 8 最后移除指示器表示扫描完成
      removeActivityIndicator()
    }
    
    

    被扫描的文字语言设置位置---第二条 [tesseract.language = "eng+fra"]


    3.6 见证奇迹的时刻到了

    找到之前主备的图片<包含英文和法文>, 打开模拟器, 直接将两张测试图片拖入模拟器, 将自动加入相册, 待稍后使用;

    运行模拟器, Choose Existing--选择相册中的测试图片, 出现啦! 编译出来的文字在Textview上展示出来了, Tesseract确实完成了一个伟大的工作!!! NB

    Paste_Image.png Paste_Image.png
    3.7 替换文字

    其实吧, 主要获得到文字, 接下来的操作岂不任你摆布^_-, 原文中是一首爱情诗词, 一来在情人节之际表示一下爱慕之情, 二来再补充修改一下, 接下来就开始工作吧!

    打开ViewController.swift, 找到已存在的swapText(), 修改代码如下:

    
    @IBAction func swapText(sender: AnyObject) {
      // 1 保证替换和被替换代码不为空
      if let text = textView.text, let findText = findTextField.text, 
        let replaceText = replaceTextField.text {
        // 2 UITextView 的替换方法
        textView.text =
        textView.text.stringByReplacingOccurrencesOfString(findTextField.text,
          withString: replaceTextField.text, options: [], range: nil)
        // 3 将输入框置为空
        findTextField.text = nil
        replaceTextField.text = nil
        // 4 结束任务, 取消键盘相应, View复位
        view.endEditing(true)
        moveViewDown()
      }
    }
    
    

    自己可以随便试试, 替换个你喜欢的ta的名字 _;

    3.8 分享

    可以获取, 可以更改, 怎么发给喜欢的人呢!!

    最后这一段就交给分享了!

    创建UIActivityViewController, 来进行操作分享,替换当前的方法sharePoem();

    @IBAction func sharePoem(sender: AnyObject) {
      // 1 判断时候为空
      if textView.text.isEmpty {
        return
      }
      // 2 初始化并赋值文字
      let activityViewController = UIActivityViewController(activityItems:
        [textView.text], applicationActivities: nil)
      // 3 
      let excludeActivities = [
        UIActivityTypeAssignToContact,
        UIActivityTypeSaveToCameraRoll,
        UIActivityTypeAddToReadingList,
        UIActivityTypePostToFlickr,
        UIActivityTypePostToVimeo]
      activityViewController.excludedActivityTypes = excludeActivities
      // 4 展示
      presentViewController(activityViewController, animated: true,
        completion: nil)
    }
    
    

    注释第三步骤: UIActivityViewController很多的类型, 进入苹果官方文档进行选择;

    最终的项目地址

    Complete! 去赢得一个你崇拜的Ta吧。

    你可以把Lenore的名字换成你自己的,把这首诗通过邮件发给自己, 睡眼朦胧的痴呆的在情人节晚上, 举一杯清酒望着明月, 然后假装收到优雅成熟英国女王给你发来的邮件、浪漫、温馨、舒适....
    突然电话响了, 就那么一句程序猿顿时从梦中惊醒....
    你猜会是啥?

    Paste_Image.png
    资源下载地址:

    1.GitHub地址----Tesseract-OCR-iOS ---- Framework
    2. GitHub地址----更多语言包----tesseract-ocr, 请使用3.0.2或更高版本
    3. 初始项目地址GitHub---OCR-Tutorial-start
    4. 最终项目地址GitHub----OCR-Tutorial-end
    由于某些小伙伴下载不了项目,个人就将之上传之GitHub,大家可以在OCR-Tutorial---start/end中【下载压缩包】。

    如果觉得好,就东东小手点个赞O(∩_∩)O哈!

    结束

    Tesseract是很强大,但OCR的潜力是无限的。记住当你使用或提高OCR的功能时,作为传感,思维,如果你能破译字符使用你的眼睛或耳朵甚至指尖,那么字符识别中你是当之无愧的专家,已经完全有能力教学你的电脑去学习更多知识。

    英文能力对不起看那么多美剧呀 !

    原博客地址Tesseract OCR tutorial

    更多精彩内容请关注“IT实战联盟”哦~~~


    IT实战联盟.jpg

    相关文章

      网友评论

      • hehtao:楼主做过字库训练不? 请问训练素材有什么具体要求? 我这边想训练身份证号码的字库,可是训练结果识别率极低,训练过程生成的 box 文件也会大面积数据出错,求指导
        simuty:@hehtao 期待你的分享:smile:
        hehtao:@Mugworts 感谢,等我弄明白了做分享,这两天也没顾上弄了:smile:
        simuty:@hehtao 不好意思回复晚了,这一块我没涉猎过:smile:
      • 菜鸟晋升路:识别率太低了
      • 印度a三:下载地址指向的不是github啊 下载不了
        simuty:文章结尾已更新。
        地址: https://github.com/mugworts/iOS-Study
        simuty:@印度a三 今天工作忙了点,明天我下载后放girhub上:smile:
      • 2017小目标:mark下,中文识别崩溃,按楼主说的去改下试试
        simuty:@2017小目标 真对中文可以参考下之前回复里的issues 祝好:smile:
      • 谢衣丶:那个好像是 字体限制 不是字数限制吧..
      • Terrnce:楼主你好,我把这个[tesseract.language = "eng+fra"]改成识别中文的@“chi_sim”,会报allow_blob_division错误,请问你解决没有
        me007:怎么解决的,,看不懂
        Terrnce:@Mugworts 谢谢。问题已解决。
        simuty:@陈伟鑫 https://github.com/charlesw/tesseract/issues/215
        github上显示的issues:

        On 7 Mar 2016 10:21, "Shivan Raptor" ***@***.***> wrote:
        @DLlearn <https://github.com/DLlearn&gt; Just use apt-get install
        tesseract-ocr-chi-sim and other similar packages to install the trained
        data. Do not use the latest version of trained data available in Github, as
        it will cause the parse error above.


        Reply to this email directly or view it on GitHub
        <#215 (comment)>.


      • 不知是为谁_CZQ:弱弱的问下楼主 有遇到 当调用recognize 出现奔溃的情况
        在 returnCode = _tesseract->Recognize(_monitor); 这一行显示EXC_BAD_ACCESS
        深渊漫步者亚尔特留斯:请问层主解决了么,最近我的工程也出现这个问题,但我新建一个工程就没有,真奇怪
        不知是为谁_CZQ:@Mugworts 好的 有劳....
        simuty:@CZQonly 之前测试的时候不记得有这个错误,改日我再测试一下

      本文标题:[译]iOS扩充--OCR光学字符识别

      本文链接:https://www.haomeiwen.com/subject/hwpzjttx.html