本文由CocoaChina翻譯小組成員 Mr_cyz(博客)翻譯自raywenderlich,作者:Lyndsey Scott
原文:Tesseract OCR Tutorial
"起初我寫這篇教程是在情人節,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。
怎麼樣在iOS上使用它呢?幸運的是,有一套Tesseract OCR的Objective-C封裝,也可以被用於swift和iOS上,適配swift版本的已經被包含在我們的示例工程中了:]
我們的app:一見鐘情
你相信我們Ray Wenderlich團隊不會讓你在這即將到來的情人節失望,對嗎?當然不會!我們支持你。我們成功找到一個有效的辦法來打動你的真愛的心,你將通過編寫一個app來實現這一點。
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。
添加Tesseract框架
在你解壓好的壓縮包中有一個Tesseract Resources文件夾,包含著Tesseract框架和tessdata文件夾,裡面有英語和法語識別數據。
在Finder中打開這一文件夾,將Tesseract.framework拖到Xcode的項目導航欄中,添加該框架,注意選中Copy items if needed。
最後點擊Finish結束添加。
接下來你需要將tessdata文件夾作為"引用文件夾(referenced folder)"添加以便維持整個文件結構。將tessdata文件夾從Finder中拖到項目導航欄中Supporting Files組中。
同樣,確保Copy items if needed選中,同時將Adding Folders選項設置為Create folder references。
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。
現在這裡應該只有一個文件:TesseractOCR.framework,就是你剛剛添加的,點擊列表下面的+按鈕,找到libstdc++.dylib和CoreImage.framework,把它們添加到你的工程中。
接下來在頂部菜單欄的Build Phases旁邊,點擊Build Settings,通過列表頂部的搜索欄可以方便地找到Other Linker Flags,在Other Linker Flags的所有已有的key後面添加-lstdc++,然後依舊是在Build Settings中,找到C++ Standard Library並選擇"Compiler Default"。
差不多了,只剩最後一步了…
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了,因為你只需要橋接頭文件。:]
Toss out those Objective-c classes!
接下來把Tesseract庫導入你新建的橋接頭文件中,在項目導航欄中找到LoveInASnap-Bridging-Header.h,打開並添加下面這一行:
現在你可以通過你的工程訪問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)
這段代碼給用戶展現兩個或三個選項,這取決於他們設備的能力,下面是這段代碼的具體講解:
1、如果你當前正在編輯textView或者textField,那麼關閉鍵盤並將視圖移動到原來的位置。
2、使用actionSheet樣式創建一個UIAlertController來為用戶提供一組選項。
3、如果用戶的設備有相機,那麼在imagePickerActionSheet上添加Take Photo按鈕,選中該按鈕時將創建並展示一個UIImagePickerController實例,並且類型為sourceType .Camera。
4、為imagePickerActionSheet添加Choose Existing按鈕,選中該按鈕時將創建並展示一個UIImagePickerController實例,並且類型為sourceType .PhotoLibrary。
5、為imagePickerActionSheet添加Cancel按鈕,設置其樣式為.Cancel後,即使你不指定任何actions,選中該按鈕依舊會關閉UIImagePickerController(譯者注:個人感覺這裡作者的意思是想說關閉UIAlertController)。
6、最後,展示UIAlertController實例。
編譯並運行你的app,點擊Snap/Upload a picture of your Poem按鈕,你將看到你新添加的UIAlertController,如下圖:
如果你運行在虛擬機上,那麼沒有可以用的物理相機,所以你不會看到Take Photo選項。
正如之前講Tesseract的局限時提到的那樣,為優化OCR結果,圖片必須有一定的大小限制。如果一張圖片太大或者太小,Tesseract可能返回一個錯誤的結果,甚至直接使整個程序崩掉並拋出EXC_BAD_ACCESS錯誤。
因此,你需要一個方法來重新設定圖片大小,當然了不能改變圖片的整體比例,這是為了使圖片盡可能不失真。
保持比例放縮圖片
圖像的整體比例(aspect ratio)是指它的寬高比,從數學定義上來說,為了減小原圖像的大小而不改變整體比例,你必須維持寬高比為一常數。
當你知道原圖像的寬和高,以及最終圖像的目標寬度或者高度時,你可以像下面一樣重新組織整體比例的等式:
這樣就有了兩個公式: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變魔法的地方!因為這是本教程中的核心部分,接下來是每一部分的詳細講解:
Your poem vil impress vith French! Ze language ov love! *Haugh* *Haugh* *Haugh*
1、初始化tesseract為一個新的G8Tesseract對象。
2、Tesseract將從.traineddata文件中尋找你在該參數中指定的語言,指定為eng和fra將從"eng.traineddata" 和 "fra.traineddata"包含的數據中分別檢測英文和法文,法語轉換數據(trained data)已經被包含到該工程中了,因為本教程中你將使用的示例詩詞中包含一部分法語(Très romantique!),法語中的重讀符號不在英語字母集中,因此為了能展示出這些重讀符號,你需要連接法語的.traineddata文件。將法語數據包含進來也是很好的,因為.traineddata中有一部分涉及到了語言詞匯。
3、你可以指定三種不同的OCR工作模式:.TesseractOnly是最快但最不精確的方法;.CubeOnly要慢一些,但更精確,因為它使用了更多的人工智能;.TesseractCubeCombined同時使用.TesseractOnly和.CubeOnly來提供最精確的結果,不過這也導致了它成為三種工作方式中最慢的一種。
4、Tesseract假定處理的文字是均勻的一段文字,但是你的樣例詩中分了多段。Tesseract的pageSegmentationMode可以讓它知道文字是怎麼樣被劃分的。所以這裡設置pageSegmentationMode為.Auto來支持自動頁劃分(automatic page segmentation),這樣Tesseract就有能力識別段落分割了。
5、這裡你通過設定maximumRecognitionTime來限制Tesseract識別圖片的時間為一有限的時間。不過這樣設定以後,只有Tesseract引擎被限制了,如果你正在使用.CubeOnly 或 .TesseractCubeCombined工作模式,那麼即使Tesseract已經達到了maximumRecognitionTime指定的時間,立體引擎(Cube engine)依然會繼續處理。
6、如果文字和背景相差很大,那麼你將得到Tesseract處理的最好結果。Tesseract有一個內置的濾鏡,g8_blackAndWhite(),降低圖片顏色的飽和度,增加對比度,減少亮度。這裡你在Tesseract圖像識別過程開始之前,將濾鏡處理後的圖像賦值給Tesseract對象的image屬性。
7、需要注意的是,圖像識別是一個同步的過程,所以此時識別後的文本已經是可用的了。你將識別出來的文本填充到textView中,同時設置textView為可編輯,這樣你的用戶就可以照他(她)喜歡來編輯了。
8、最後,移除活動指示器來表明OCR已經完成識別圖像的過程,可以讓用戶編輯他們的詩詞了。
現在是時候檢測一下你寫的這段代碼,看看會發生些什麼了。
處理你的第一張圖像
本教程的示例圖像,可以從Image Resources\Lenore.png中找到。找到下面這一張圖片:
Lenore.png圖片包含一首寫給"Lenore"的情詩--不過稍作修改後你可以得到一首足以引起你喜歡的人的注意的詩了!:]
盡管你可以手動打出這張圖片的一份拷貝,然後使用app來截一張圖後執行OCR,不過何不簡化一下呢?把圖片添加到設備的相冊中,以減少其中潛在的人為錯誤、光照變換、文本扭曲、打印瑕疵等問題。如果你使用模擬器,僅僅把圖片文件拖到模擬器上即可。
編譯並運行你的app,選擇Snap/Upload a picture of your Poem 然後選擇Choose Existing從圖片庫中選取示例圖片並開始Tesseract解析,首次運行時你需要允許app訪問圖片庫,你將看到在你選擇完圖片之後活動指示器開始旋轉。
然後…成了!最終破譯後的文本出現在textView中--看上去Tesseract的OCR成功了。
但是如果你的愛人不叫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() }
上面的代碼非常直接,接下來花一點時間一步一步地看一遍代碼:
1、如果textView為空,說明沒有需要替換的文本,那麼簡單地跳出該方法即可。
2、否則,在textView中找到你在findTextField中鍵入的字符串,將它們替換為你在replaceTextField中輸入的字符串。
3、接下來, 一旦替換完成,清空findTextField 和 replaceTextField裡的值。
4、最後,關閉鍵盤,將視圖移回到原來的位置,就像之前在takePhoto()中一樣,確保當鍵盤關閉後視圖被正確放置。
注意:點擊背景也會關閉鍵盤並將視圖移動到原來的位置,這是借由在界面上其他元素之下的一個UIButton實現的,該按鈕會觸發ViewController.swift中的backgroundTapped()方法。
編譯並運行app,再次選中示例圖片,讓Tesseract工作。一旦文本出現後,在Find this…輸入框中輸入Lenore(注意待替換文本是大小寫敏感的),然後在Replace with…輸入框中輸入你愛人的名字,點擊Swap按鈕完成替換。
說變就變--你已經創造了為你的甜心量身定做、私人訂制的一首情詩。
隨便試著替換一下其他需要替換的單詞和名字,完成之後--呃,完成之後接下來做些什麼呢?如此一首有創造性的、勇敢的、充滿藝術氣息的詩不應該孤零零地在你的設備上,你需要一種方式來與世界分享你的傑作。
分享最終作品
在最後的這一部分,你將創建一個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) }
依次看一下上面的數字注釋:
1、如果textView是空的,那就不分享任何東西。
2、否則,創建一個UIActivityViewController的實例,將textView中的文本放在數組中作為要被分享的項目。
3、UIActivityViewController有一長串內置的活動類型,你可以不包含UIActivityTypeAssignToContact, UIActivityTypeSaveToCameraRoll, UIActivityTypeAddToReadingList, UIActivityTypePostToFlickr, 和 UIActivityTypePostToVimeo,因為這裡它們沒有什麼意義。
4、最後,展示你的UIActivityViewController來讓用戶分享他們的作品。
再次編譯並運行app,通Tesseract識別圖片,如果你想,你可以再次執行查找和替換過程。當你對文本滿意之後,點擊share按鈕。
好了,你的"一見鐘情"app已經完成了--肯定可以贏得你愛慕的人的心。
如果你像我一樣,你可能會把Lenore的名字替換為自己的名字,用其他賬號把這首詩發到自己的郵件箱中,獨自一人度過情人節的夜晚。吃一碗拌飯,喝一杯紅酒,視線逐漸模糊,然後假裝這封信是來自英國的女王,來邀請你去豪華的,充滿浪漫、舒適、神秘和驚喜的夜晚。不過可能也只有我了…
何去何從
你可以從這裡下載工程的最終版本。
你可以在github上找到Tesseract的iOS版的壓縮包,地址是https://github.com/gali8/Tesseract-OCR-iOS,你也可以從谷歌的Tesseract OCR網站上下載更多的語言包,使用版本3.02及以上來保證與當前庫的兼容性。
使用其他的詩、歌以及文本片段來試一下;試著使用你的照相機照幾張相;或者使用你的圖片庫中的圖片。你將看到Tesseract在不同的圖片中擁有怎樣不同的表現。
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的性能時,時刻思考你是否能通過你的眼睛、耳朵甚至指尖來破譯這些字符。你已經是一個字符識別的專家了,完全可以教你的電腦更多它還不知道的東西。
同樣,如果你對本教程Tesseract,或者說OCR 策略,有任何評論或疑問,請加入下方的討論!
轉載請注明譯者信息和譯文鏈接!