Áp dụng Tesseract vào iOS
Giới thiệu
Tesseract là 1 OCR engine hỗ trợ trên nhiều nền tảng khác nhau, miễn phí, bản quyền Apache và được hỗ trợ phát triển bởi Google từ năm 2006 cho tới nay. Đây là 1 OCR engine phổ biến và hiệu quả nhất (cho tới hiện nay), sử dụng AI(trí tuệ nhân tạo) để xác định và nhận diện chữ. Các hệ điều hành được hỗ trợ: MacOS, Window, Linux, nhưng có thể biên dịch cho iOS và Android để chạy trên 2 nền tảng này.
Đây là souce của Tessearct.
https://github.com/tesseract-ocr/tesseract
Số lượng ngôn ngữ hỗ trợ lên đến 100, với mỗi file .traineddata là mô hình ngôn ngữ đã được huấn luyện.
https://github.com/tesseract-ocr/tessdata
Sau đây tôi sẽ trình bày cách áp dụng Tesseract trong iOS.
Môi trường phát triển
- Macos Catalina 10.15.2
- Xcode 11.3, Swift 5
- Tesseract 4.1.1
- Leptonica 1.79.0
- OpenCV 4.2.0
Tải xuống và nhúng các thư viện cần thiết
Trước tiên hãy tạo mới 1 project với chế độ Single View App
Tải xuống Tesseract 4.1.1 phiên bản dành cho iOS
(Đây là phiên bản đã được biên dịch CHỈ dành cho iOS từ nguồn https://github.com/tesseract-ocr/tesseract)
https://github.com/kang298/Tesseract-builds-for-iOS/tree/tesseract-4.1.1
Sau khi tải xuống và giải nén, chúng ta sẽ có 2 thư mục “include” và “lib”, hãy kéo thả chúng vào project của bạn.
Tải xuống OpenCV
Tải từ link sau https://opencv.org/releases/ và kéo thả chúng vào project của bạn.
Bấm command + R để chạy lại project để đảm bảo không có lỗi phát sinh.
Tải xuống các file ngôn ngữ đã được huấn luyện
Link tải xuống:
https://github.com/tesseract-ocr/tessdata
Trong bài viết này chúng ta sẽ kiểm tra thử với 3 ngôn ngữ: tiếng Anh, Nhật, Việt Nam. Do đó hãy tải xuống các mô hình sau đây và lưu chúng vào 1 thư mục đặt tên là tessdata.
- eng.traineddata
- jpn.traineddata
- vie.traineddata
Sau đó kéo thả thư mục trên vào xcode project. CHÚ Ý: chọn chế độ “Create folder references” thay vì “Create Group” khi thêm thư mục vào dự án.
Tiến hành lập trình
Bởi vì Tesseract được phát triển bằng ngôn ngử C++ cho nên chúng ta chỉ có thể lập trình bằng C++. Tạo 1 file C++ có tên là tesseract_wrapper.cpp như trong hình.
Lưu ý: nhớ chọn “Also create a header file” để Xcode tạo 1 file tesseract_wrapper.hpp sẵn.
tesseract_wrapper.hpp
// // tesseract_wrapper.hpp // TestTesseract // // Created by Briswell on 1/13/20. // Copyright © 2020 Briswell. All rights reserved. // #ifndef tesseract_wrapper_hpp #define tesseract_wrapper_hpp #include "opencv2/imgproc.hpp" #include "stdio.h" using namespace cv; String ocrUsingTesseractCPP(String image_path,String data_path,String language); #endif /* tesseract_wrapper_hpp */
tesseract_wrapper.cpp
// // tesseract_wrapper.cpp // TestTesseract // // Created by Briswell on 1/13/20. // Copyright © 2020 Briswell. All rights reserved. // #include "allheaders.h" #include "opencv2/imgproc.hpp" #include "opencv2/highgui.hpp" #include "baseapi.h" #include "tesseract_wrapper.hpp" using namespace cv; using namespace tesseract; /* matToPix(): convert from OpenCV Image Container to Leptonica's Pix Struct Params: mat: OpenCV Mat image Container Output Leptonica's Pix Struct */ Pix* matToPix(Mat *mat){ int image_depth = 8; //create a Leptonica's Pix Struct with width, height of OpenCV Image Container Pix *pixd = pixCreate(mat->size().width, mat->size().height, image_depth); for(int y=0; yrows; y++) { for(int x=0; xcols; x++) { pixSetPixel(pixd, x, y, (l_uint32) mat->at(y,x)); } } return pixd; } /* ocrUsingTesseractCPP(): Using Tesseract engine to read text from image Params: image_path: path to image data_path: path to folder containing .traineddata files language: expeted language to detect (eng,jpn,..) Output: String detected from image */ String ocrUsingTesseractCPP(String image_path,String data_path,String language){ //load a Mat Image Container from image's path and gray scale mode Mat image = imread(image_path,IMREAD_GRAYSCALE); TessBaseAPI* tessEngine = new TessBaseAPI(); //Tesseract 4 adds a new neural net (LSTM) based OCR engine which is focused on line recognition, but also still supports the legacy Tesseract OCR engine of Tesseract 3 which works by recognizing character patterns, in this tutorial we just focus on LSTM only OcrEngineMode mode = tesseract::OEM_LSTM_ONLY; //init Tesseract engine tessEngine->Init(data_path.c_str(), language.c_str(), mode); //Set mode for page layout analysis, refer for all modes supporting //https://tesseract.patagames.com/help/html/T_Patagames_Ocr_Enums_PageSegMode.htm PageSegMode pageSegMode = tesseract::PSM_SINGLE_BLOCK; tessEngine->SetPageSegMode(pageSegMode); //increase accuracy for japanese if(language.compare("jpn") == 0){ tessEngine->SetVariable("chop_enable", "true"); tessEngine->SetVariable("use_new_state_cost", "false"); tessEngine->SetVariable("segment_segcost_rating", "false"); tessEngine->SetVariable("enable_new_segsearch", "0"); tessEngine->SetVariable("language_model_ngram_on", "0"); tessEngine->SetVariable("textord_force_make_prop_words", "false"); tessEngine->SetVariable("edges_max_children_per_outline", "40"); } //convert from OpenCV Image Container to Leptonica's Pix Struct Pix *pixImage = matToPix(&image); //set Leptonica's Pix Struct to Tesseract engine tessEngine->SetImage(pixImage); //get recognized text in UTF8 encoding char *text = tessEngine->GetUTF8Text(); //release Tesseract's cache tessEngine->End(); pixDestroy(&pixImage); return text; }
Bởi vì Swift không thể gọi trực tiếp các hàm của C++ nên phải có file trung gian objective-C.
- TesseractWrapper.h
- TesseractWrapper.mm (đuôi file phải là .mm để cho viêc biên dịch C++)
TesseractWrapper.h
// // TesseractWrapper.h // TestTesseract // // Created by Briswell on 1/13/20. // Copyright © 2020 Briswell. All rights reserved. // #import "Foundation/Foundation.h" #import "UIKit/UIKit.h" @interface TesseractWrapper : NSObject +(NSString*)ocrUsingTesseractObjectiveC:(UIImage*)image language:(NSString*)language; @end
TesseractWrapper.mm
// // TesseractWrapper.m // TestTesseract // // Created by Briswell on 1/13/20. // Copyright © 2020 Briswell. All rights reserved. // #import "TesseractWrapper.h" #include "tesseract_wrapper.hpp" @implementation TesseractWrapper /* ocrUsingTesseractObjectiveC() call ocrUsingTesseractCPP() to recognize text from image params: image: image to recognize text language: eng/jpn/vie output: recognized string */ +(NSString*)ocrUsingTesseractObjectiveC:(UIImage*)image language:(NSString*)language{ //get path of folder containing .traineddata files NSString* data_path = [NSString stringWithFormat:@"%@/tessdata/",[[NSBundle mainBundle] bundlePath]]; //save image to app's cache directory NSString* cache_dir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString* image_path = [NSString stringWithFormat:@"%@/image.jpeg",cache_dir]; NSData* data = UIImageJPEGRepresentation(image, 0.5); NSURL* url = [NSURL fileURLWithPath:image_path]; [data writeToURL:url atomically:true]; //get text from image using ocrUsingTesseractCPP() from file tesseract_wrapper.hpp String str = ocrUsingTesseractCPP([image_path UTF8String], [data_path UTF8String], [language UTF8String]); NSString* result_string = [NSString stringWithCString:str.c_str() encoding:NSUTF8StringEncoding]; //remove cached image [[NSFileManager defaultManager] removeItemAtURL:url error:nil]; return result_string; } @end
Tạo 1 màn hình đơn giản chứa 1 textview và 1 button cho file ViewController.swift.
ViewController.swift
// // ViewController.swift // TestTesseract // // Created by Briswell on 1/13/20. // Copyright © 2020 Briswell. All rights reserved. // import UIKit import CropViewController class ViewController: UIViewController { @IBOutlet weak var txt: UITextView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } @IBAction func ocr(_ sender: Any) { //if camera not supported if !UIImagePickerController.isSourceTypeAvailable(.camera){ return } //present camera to take image let pickerController = UIImagePickerController() pickerController.delegate = self as UIImagePickerControllerDelegate & UINavigationControllerDelegate pickerController.sourceType = .camera self.present(pickerController, animated: true, completion: nil) } } extension ViewController: UIImagePickerControllerDelegate,UINavigationControllerDelegate{ func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { picker.dismiss(animated: true) { guard let image = info[.originalImage] as? UIImage else { return } //present a crop image frame to focus on text content let cropViewController = CropViewController.init(image: image) cropViewController.delegate = self self.present(cropViewController, animated: true, completion: nil) } } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true, completion: nil) } } extension ViewController:CropViewControllerDelegate{ func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) { cropViewController.dismiss(animated: true) { //call objective-c wrapper with expected language let str = TesseractWrapper.ocr(usingTesseract: image, language: "jpn") self.txt.text = str } } func cropViewController(_ cropViewController: CropViewController, didFinishCancelled cancelled: Bool) { cropViewController.dismiss(animated: true, completion: nil) } }
Sau đây là kết quả kiểm thử với ngôn ngữ tiếng Nhật, chúng ta có thể làm tương tự với 2 ngôn ngữ Anh và Việt.
Lời kết
Nhận diện chữ trong hình ảnh hoàn toàn có thể nhưng có nhiều vấn đề xung quanh. Vấn đề chủ yếu lớn nhất là chất lượng của hình (độ tương phản, độ sáng, kích cỡ,..). Và mỗi hình lại có những vấn đề khác nhau do đó chúng ta có thể tạo 1 công cụ lọc màu để người dùng tự xử lý hình ảnh trước khi đưa vào xử lý nhận diện. Dưới đây là link tham khảo cho việc cải thiện chất lượng hình ảnh.
https://github.com/tesseract-ocr/tesseract/wiki/ImproveQuality