"起初我写这篇教程是在情人节,OCR能够带给你一全年的爱"。 你以前确定已经见过,OCR技术被应用于在平板电脑上将扫描文件处理成手写字迹,还被应用于谷歌最近添加到他们的Translate app上的"Word Len"功能。如今你将学习在本身的iPhone app上使用OCR,很是酷,不是吗? 那么…OCR是什么? 什么是OCR 光学字符识别,或着说OCR,是指用电子的方式从图片中取出文字而后重用于其余领域,如文档编辑、自由文本搜索,或文本比对。 本教程中,你将学习怎样使用Tesseract,谷歌维护的一个开源OCR引擎。 Tesseract介绍 Tesseract十分强大,但有如下几点局限性: 不像其余OCR引擎(例如美国邮政业用于分类邮件的),Tesseract不能识别手写,并且只能识别一共大约64中字体的文本。 Tesseract须要一些处理来改善OCR结果,图像须要被放缩,图像有很是多的差别,另外还有水平排布的文字。 最后,Tesseract仅仅支持Liuux,Windows,Mac OS X。 wait_what.jpg 怎么样在iOS上使用它呢?幸运的是,有一套Tesseract OCR的Objective-C封装,也能够被用于swift和iOS上,适配swift版本的已经被包含在咱们的示例工程中了:] 咱们的app:一见倾心 你相信咱们Ray Wenderlich团队不会让你在这即将到来的情人节失望,对吗?固然不会!咱们支持你。咱们成功找到一个有效的办法来打动你的真爱的心,你将经过编写一个app来实现这一点。 love-320x320.png U + OCR = LUV 本教程中,你将学习怎样使用Tesseract--谷歌维护的一个开源OCR引擎。你将编写"一见倾心"应用,能够针对一张浪漫爱情诗的照片,将原诗人密切思念的名字替换为你本身喜欢的人的名字,从而将这首诗变成"你本身的"。很是好,准备好去感动你的Ta吧。 开始 从这里下载开始工程,解压到方便的地方。 压缩包中包含如下文件夹: LoveInASnap:本教程的Xcode起步工程 Tesseract Resources:Tesseract框架和语言数据 Image Resources:样例图片,包含着一会你将用到的文字。 看一下如今的LoveinASnap.xcodeproj,能够注意到一个预先准备好的ViewController.swift和Main.storyboard界面,以及几个链接到控制器上的@IBOutlet和空的@IBAction方法。 在这些空方法下,能够看到两个已经写好的函数,用来处理展现和隐藏视图的active indicator: 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 } 接下来还有一些方法来移动视图,防止键盘挡住text fields: 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() }) } 最后,剩下的方法根据用户的操做来触发键盘操做、调用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() } 尽管app的用户体验很重要,但这些方法与本教程基本无关,所以实现为你准备好了,为了让你马上开始真正的有趣的编程。 可是在你写第一行代码以前,编译并运行起步工程,处处点击一下app,感觉一下UI。目前text view是不可编辑的,点击text filed只会简单地调出或关闭键盘,你的工做是补全完善这款app。 ocr-first-run-281x500.png 添加Tesseract框架 在你解压好的压缩包中有一个Tesseract Resources文件夹,包含着Tesseract框架和tessdata文件夹,里面有英语和法语识别数据。 在Finder中打开这一文件夹,将Tesseract.framework拖到Xcode的项目导航栏中,添加该框架,注意选中Copy items if needed。 Create_Groups_screenshot-463x320.png 最后点击Finish结束添加。 接下来你须要将tessdata文件夹做为"引用文件夹(referenced folder)"添加以便维持整个文件结构。将tessdata文件夹从Finder中拖到项目导航栏中Supporting Files组中。 一样,确保Copy items if needed选中,同时将Adding Folders选项设置为Create folder references。 08.png Adding tessdata as a referenced folder 最后,点击Finish将数据文件添加到工程中,能够看到在项目导航栏中出现了一个蓝色的tessdata文件夹,蓝色说明这个文件夹是引用而不是Xcode中的group。 因为Tesseract须要libstdc++.6.0.9.dylib 和 CoreImage.framework,你还须要把这些库链接到项目中来。 选中LoveInASnap项目文件,选择LoveInASnap目标,在General栏中找到Linked Frameworks and Libraries。 ocr-frameworks-700x319.png 如今这里应该只有一个文件:TesseractOCR.framework,就是你刚刚添加的,点击列表下面的+按钮,找到libstdc++.dylib和CoreImage.framework,把它们添加到你的工程中。 ocr-addlibs.png 接下来在顶部菜单栏的Build Phases旁边,点击Build Settings,经过列表顶部的搜索栏能够方便地找到Other Linker Flags,在Other Linker Flags的全部已有的key后面添加-lstdc++,而后依旧是在Build Settings中,找到C++ Standard Library并选择"Compiler Default"。 差很少了,只剩最后一步了… happy-epic-win.png Wipe away those happy tears, Champ! Almost there! One step to go… 最后,由于Tesseract是一个Objective-C库,你须要建立"Objective-C桥接头文件"(Objective-C bridging header)来在你的swift app中使用该库。 建立桥接头文件而且使其符合全部项目配置的最简单的方法是把任意Objective-C文件添加到你的工程中。 选择File\New\File…,选中iOS\Source\Cocoa Touch Class而后点击Next,键入FakeObjectiveCClass做为类名,选择NSObject做为其超类,固然,确保语言选择为Objective-C!点击Next,而后点击Create。 当提示Would you like to configure an Objective-C bridging header时选择YES。 你已经成功建立了Objective-C桥接头文件,如今你能够从工程中删除FakeObjectiveCClass.m和FakeObjectiveCClass.h了,由于你只须要桥接头文件。:] TrashObjC.jpg Toss out those Objective-c classes! 接下来把Tesseract库导入你新建的桥接头文件中,在项目导航栏中找到LoveInASnap-Bridging-Header.h,打开并添加下面这一行: 15.png 如今你能够经过你的工程访问Tesseract库了,编译并运行你的项目,确保一切都能正常经过编译。 一切正常吗?那么如今你能够开始作有趣的事了! 加载图像 固然,在你的OCR app中首先须要一个能从程序中加载一张图片的机制,最简单的方式是使用UIImagePickerController实例从相机或图片库中选择一张图片。 打开ViewController.swift,找到takePhoto(),将该方法的实现替换为下面的代码: // 1 view.endEditing(true) moveViewDown() // 2 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) 这段代码给用户展示两个或三个选项,这取决于他们设备的能力,下面是这段代码的具体讲解: 一、若是你当前正在编辑textView或者textField,那么关闭键盘并将视图移动到原来的位置。 二、使用actionSheet样式建立一个UIAlertController来为用户提供一组选项。 三、若是用户的设备有相机,那么在imagePickerActionSheet上添加Take Photo按钮,选中该按钮时将建立并展现一个UIImagePickerController实例,而且类型为sourceType .Camera。 四、为imagePickerActionSheet添加Choose Existing按钮,选中该按钮时将建立并展现一个UIImagePickerController实例,而且类型为sourceType .PhotoLibrary。 五、为imagePickerActionSheet添加Cancel按钮,设置其样式为.Cancel后,即便你不指定任何actions,选中该按钮依旧会关闭UIImagePickerController(译者注:我的感受这里做者的意思是想说关闭UIAlertController)。 六、最后,展现UIAlertController实例。 编译并运行你的app,点击Snap/Upload a picture of your Poem按钮,你将看到你新添加的UIAlertController,以下图: ocr-action-sheet-281x500.png 若是你运行在虚拟机上,那么没有能够用的物理相机,因此你不会看到Take Photo选项。 正如以前讲Tesseract的局限时提到的那样,为优化OCR结果,图片必须有必定的大小限制。若是一张图片太大或者过小,Tesseract可能返回一个错误的结果,甚至直接使整个程序崩掉并抛出EXC_BAD_ACCESS错误。 所以,你须要一个方法来从新设定图片大小,固然了不能改变图片的总体比例,这是为了使图片尽量不失真。 保持比例放缩图片 图像的总体比例(aspect ratio)是指它的宽高比,从数学定义上来讲,为了减少原图像的大小而不改变总体比例,你必须维持宽高比为一常数。 Aspect_Ratio-369x320.png 当你知道原图像的宽和高,以及最终图像的目标宽度或者高度时,你能够像下面同样从新组织总体比例的等式: 12.jpg 这样就有了两个公式:Height1/Width1 * width2 = height2和Width1/Height1 * height2 = width2。你将在你的缩放方法中使用这两个公式来维持图片的总体比例。 依然是在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参数的值,接下来基于原总体比例适当地放缩另外一边,重绘原图像到新计算的frame中,最后将新建立的放缩好了的图片返回给调用者。 呼~ 既然咱们已经获得了咱们须要的一切(此处应有掌声…),接下来能够开始实现Tesseract部分了。 实现Tesseract OCR 在ViewController.swift的底部找到UIImagePickerControllerDelegate扩展,在扩展中添加下面的方法: func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) { let selectedPhoto = info[UIImagePickerControllerOriginalImage] as! UIImage let scaledImage = scaleImage(selectedPhoto, maxDimension: 640) addActivityIndicator() dismissViewControllerAnimated(true, completion: { self.performImageRecognition(scaledImage) }) } imagePickerController(_:didFinishPickingMediaWithInfo:) 是 UIImagePickerDelegate的一个代理方法,在info字典对象中返回选中图片的信息。你能够从info字典中使用UIImagePickerControllerOriginalImage键来获取选中图片,而后使用scaleImage(_:maxDimension:)来放缩图片。 经过调用addActivityIndicator()来在Tesseract工做时打断用户交互,向用户展现活动指示器。接下来关闭UIImagePicker视图控制器,将图片传给performImageRecognition()(稍后你将实现这一方法)来处理。 下面,在类的主声明中添加下面方法: func performImageRecognition(image: UIImage) { // 1 let tesseract = G8Tesseract() // 2 tesseract.language = "eng+fra" // 3 tesseract.engineMode = .TesseractCubeCombined // 4 tesseract.pageSegmentationMode = .Auto // 5 tesseract.maximumRecognitionTime = 60.0 // 6 tesseract.image = image.g8_blackAndWhite() tesseract.recognize() // 7 textView.text = tesseract.recognizedText textView.editable = true // 8 removeActivityIndicator() } 这里就是OCR变魔法的地方!由于这是本教程中的核心部分,接下来是每一部分的详细讲解: French1.png Your poem vil impress vith French! Ze language ov love! *Haugh* *Haugh* *Haugh* 一、初始化tesseract为一个新的G8Tesseract对象。 二、Tesseract将从.traineddata文件中寻找你在该参数中指定的语言,指定为eng和fra将从"eng.traineddata" 和 "fra.traineddata"包含的数据中分别检测英文和法文,法语转换数据(trained data)已经被包含到该工程中了,由于本教程中你将使用的示例诗词中包含一部分法语(Très romantique!),法语中的重读符号不在英语字母集中,所以为了能展现出这些重读符号,你须要链接法语的.traineddata文件。将法语数据包含进来也是很好的,由于.traineddata中有一部分涉及到了语言词汇。 三、你能够指定三种不一样的OCR工做模式:.TesseractOnly是最快但最不精确的方法;.CubeOnly要慢一些,但更精确,由于它使用了更多的人工智能;.TesseractCubeCombined同时使用.TesseractOnly和.CubeOnly来提供最精确的结果,不过这也致使了它成为三种工做方式中最慢的一种。 四、Tesseract假定处理的文字是均匀的一段文字,可是你的样例诗中分了多段。Tesseract的pageSegmentationMode可让它知道文字是怎么样被划分的。因此这里设置pageSegmentationMode为.Auto来支持自动页划分(automatic page segmentation),这样Tesseract就有能力识别段落分割了。 五、这里你经过设定maximumRecognitionTime来限制Tesseract识别图片的时间为一有限的时间。不过这样设定之后,只有Tesseract引擎被限制了,若是你正在使用.CubeOnly 或 .TesseractCubeCombined工做模式,那么即便Tesseract已经达到了maximumRecognitionTime指定的时间,立体引擎(Cube engine)依然会继续处理。 六、若是文字和背景相差很大,那么你将获得Tesseract处理的最好结果。Tesseract有一个内置的滤镜,g8_blackAndWhite(),下降图片颜色的饱和度,增长对比度,减小亮度。这里你在Tesseract图像识别过程开始以前,将滤镜处理后的图像赋值给Tesseract对象的image属性。 七、须要注意的是,图像识别是一个同步的过程,因此此时识别后的文本已是可用的了。你将识别出来的文本填充到textView中,同时设置textView为可编辑,这样你的用户就能够照他(她)喜欢来编辑了。 八、最后,移除活动指示器来代表OCR已经完成识别图像的过程,可让用户编辑他们的诗词了。 如今是时候检测一下你写的这段代码,看看会发生些什么了。 处理你的第一张图像 本教程的示例图像,能够从Image Resources\Lenore.png中找到。找到下面这一张图片: QQ截图20150708105208.png Lenore.png图片包含一首写给"Lenore"的情诗--不过稍做修改后你能够获得一首足以引发你喜欢的人的注意的诗了!:] 尽管你能够手动打出这张图片的一份拷贝,而后使用app来截一张图后执行OCR,不过何不简化一下呢?把图片添加到设备的相册中,以减小其中潜在的人为错误、光照变换、文本扭曲、打印瑕疵等问题。若是你使用模拟器,仅仅把图片文件拖到模拟器上便可。 编译并运行你的app,选择Snap/Upload a picture of your Poem 而后选择Choose Existing从图片库中选取示例图片并开始Tesseract解析,首次运行时你须要容许app访问图片库,你将看到在你选择完图片以后活动指示器开始旋转。 而后…成了!最终破译后的文本出如今textView中--看上去Tesseract的OCR成功了。 QQ截图20150708105233.png 可是若是你的爱人不叫Lenore,他或她可能不会很感谢你的这首诗,而且他们可能想要知道这个Lenore是谁!:] 考虑的Lenore频繁地出如今扫描的文本中,把这首诗自定成你爱人喜欢地那样可能要花上一番功夫… 你会问,那怎么办?固然了,你能够实现一个节省时间的方法来找到并替换这些单词!很是棒的主意!下一部分将向你展现怎么样作到这一点。 找到并替换文本 既然OCR引擎已经把图像转换为textView中的文本,你能够将其当作其余普通字符串同样对待。 打开ViewController.swift,能够看到已经为你准备好了一个swapText()方法,这个方法被链接到Swap按钮上,很是方便。:] 将swapText()的实现替换为下面的代码: @IBAction func swapText(sender: AnyObject) { // 1 if textView.text.isEmpty { return } // 2 textView.text = textView.text.stringByReplacingOccurrencesOfString(findTextField.text, withString: replaceTextField.text, options: nil, range: nil) // 3 findTextField.text = nil replaceTextField.text = nil // 4 view.endEditing(true) moveViewDown() } 上面的代码很是直接,接下来花一点时间一步一步地看一遍代码: 一、若是textView为空,说明没有须要替换的文本,那么简单地跳出该方法便可。 二、不然,在textView中找到你在findTextField中键入的字符串,将它们替换为你在replaceTextField中输入的字符串。 三、接下来, 一旦替换完成,清空findTextField 和 replaceTextField里的值。 四、最后,关闭键盘,将视图移回到原来的位置,就像以前在takePhoto()中同样,确保当键盘关闭后视图被正确放置。 注意:点击背景也会关闭键盘并将视图移动到原来的位置,这是借由在界面上其余元素之下的一个UIButton实现的,该按钮会触发ViewController.swift中的backgroundTapped()方法。 编译并运行app,再次选中示例图片,让Tesseract工做。一旦文本出现后,在Find this…输入框中输入Lenore(注意待替换文本是大小写敏感的),而后在Replace with…输入框中输入你爱人的名字,点击Swap按钮完成替换。 13.png 说变就变--你已经创造了为你的甜心量身定作、私人订制的一首情诗。 随便试着替换一下其余须要替换的单词和名字,完成以后--呃,完成以后接下来作些什么呢?如此一首有创造性的、勇敢的、充满艺术气息的诗不该该孤零零地在你的设备上,你须要一种方式来与世界分享你的杰做。 分享最终做品 在最后的这一部分,你将建立一个UIActivityViewController来容许你的用户分享他们的新做品。 在ViewController.swift中,将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) } 依次看一下上面的数字注释: 一、若是textView是空的,那就不分享任何东西。 二、不然,建立一个UIActivityViewController的实例,将textView中的文本放在数组中做为要被分享的项目。 三、UIActivityViewController有一长串内置的活动类型,你能够不包含UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll, UIActivityTypeAddToReadingList, UIActivityTypePostToFlickr, 和 UIActivityTypePostToVimeo,由于这里它们没有什么意义。 四、最后,展现你的UIActivityViewController来让用户分享他们的做品。 再次编译并运行app,通Tesseract识别图片,若是你想,你能够再次执行查找和替换过程。当你对文本满意以后,点击share按钮。 14.png 好了,你的"一见倾心"app已经完成了--确定能够赢得你爱慕的人的心。 若是你像我同样,你可能会把Lenore的名字替换为本身的名字,用其余帐号把这首诗发到本身的邮件箱中,独自一人度过情人节的夜晚。吃一碗拌饭,喝一杯红酒,视线逐渐模糊,而后伪装这封信是来自英国的女王,来邀请你去豪华的,充满浪漫、温馨、神秘和惊喜的夜晚。不过可能也只有我了… 何去何从 你能够从这里下载工程的最终版本。 你能够在github上找到Tesseract的iOS版的压缩包,地址是https://github.com/gali8/Tesseract-OCR-iOS,你也能够从谷歌的Tesseract OCR网站上下载更多的语言包,使用版本3.02及以上来保证与当前库的兼容性。 使用其余的诗、歌以及文本片断来试一下;试着使用你的照相机照几张相;或者使用你的图片库中的图片。你将看到Tesseract在不一样的图片中拥有怎样不一样的表现。 QualityIssues-700x212.png Examples of potentially problematic image inputs that can be corrected for improved results. Source: Google’s Tesseract OCR site 记住:"错误的输入必然致使错误的输出"。提升输出质量的最简单的方式是提升输入的质量。正如谷歌在他们的Tesseract网站上列出的那样,有不少方式能够提升你的输入质量:黑暗或者微弱的光照,图片干扰,歪曲文本方向,厚厚的黑边框都会致使低质量的结果。 你能够查找图片预处理的相关资料,或者实现本身的人工智能逻辑,例如神经网络或者充分利用Tesseract的优化工具,来帮助你的程序纠正错误,提升成功概率。又或者,由于即便是很小的图片亮度、颜色、对比度、曝光等的变化都会致使输出的不一样,你能够将图片通过多种滤镜处理,而后对比结果来决定最精确的输出。经过使用以上一个或几个策略,你可能获得最好的输出结果,因此试一下这些方法看一下哪个对你的应用最好。 Tesseract很是强大,可是OCR的潜能是无限的,当你在你的软件中使用Tesseract,提升Tesseract OCR的性能时,时刻思考你是否能经过你的眼睛、耳朵甚至指尖来破译这些字符。你已是一个字符识别的专家了,彻底能够教你的电脑更多它还不知道的东西。 ocr_expert-244x320.jpg 一样,若是你对本教程Tesseract,或者说OCR 策略,有任何评论或疑问,请加入下方的讨论! http://www.cocoachina.com/ios/20150708/12463.html