Tesseract iOSへの導入について

最初に
Tesseractは様々なオペレーティングシステムで動作する”optical character recognition(光学式文字認識)”エンジンです。これはフリーソフトウェアでApacheライセンスの元提供されております。そしてGoogleが2006年から2020年現在までスポンサーとなってます。これは最もポピュラーで最も正確なOCRライブラリです。テキスト検索や画像認識のための人工知能(AI)として利用されます。
これは様々なプラットフォームをサポートしています。MacOS, Window, LinuxだけでなくiOS and Androidも含まれます。
これはリポジトリのプログラムです。
https://github.com/tesseract-ocr/tesseract
100種類以上の言語をサポートしています。それぞれの”.traineddata”ファイルは言語トレーニングモデルです。
https://github.com/tesseract-ocr/tessdata
今回TesseractのiOS版の実施の仕方について説明したいと思います。
開発環境
- Macos Catalina 10.15.2
- Xcode 11.3, Swift 5
- Tesseract 4.1.1
- Leptonica 1.79.0
- OpenCV 4.2.0
関連のものをダウンロードします。
最初に、”Single View App mode”でプロジェクトを作成します。

tesseract 4.1.1 iOS版をダウンロード
(これはiOS向けのものはこちらから取得できます。 https://github.com/tesseract-ocr/tesseract)
https://github.com/kang298/Tesseract-builds-for-iOS/tree/tesseract-4.1.1
ダウンロードして解凍をすると“include”と“lib”の2つのフォルダができます。これらのフォルダをxcodeのプロジェクトの配下へドラッグアンドドロップを行います。

OpenCV iOS frameworkのダウンロード
https://opencv.org/releases/ ダウンロード後xcodeにドラッグアンドドロップを行います。

エラーがないことを確かめた後、command + Rを押下しBuildしてください。
Download languages’ trained model files
https://github.com/tesseract-ocr/tessdata
今回のチュートリアルでは3つの言語、英語、日本語、ベトナム語でテストを行います。そのため私たちはモデルファイルをダウンロードし、tessdataのフォルダを保存しなければなりません。:
- eng.traineddata
- jpn.traineddata
- vie.traineddata
xcodeプロジェクトにこれらのフォルダを移動させます。
NOTE: プロジェクトにフォルダを追加する時は”Create Group”の代わりに”Create folder references”を選択してください。

Coding
TesseractはC++で開発しています。そのためC++でのみコーディングができます。tesseract_wrapper.cppの名前でC++ファイルをプロジェクト内に作成してください。以下のような形です。


忘れずに“Also create a header file”にチェックをしてください。そうすればC++のヘッダーファイル((tesseract_wrapper.hpp))と共にC++のファイルが出来上がります。
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;
}
SwiftはC++を直接呼ぶことができないためこれらのプログラムをobjective-cのwropperファイルを使用します。
- TesseractWrapper.h
- TesseractWrapper.mm (not .m because this file is for C++ compilation)
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
“ViewController.swift”にTextViewとボタンのシンプルな画面を作ります。

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)
}
}
これは日本語でのテスト結果です。上記の方法を行えば英語やベトナム語でも確認ができます。

最後に
画像の文字認識は実現可能ですがいくつかの難しいところがあります。
一番は画像の品質(文字サイズ、光、コントラスト)です。それぞれの画像は認証を行う上で認証しにくい様々な問題があります。そのため画像にフィルターを手動でかけて問題を解決するようなオプションもあります。画像品質を改善するために下記のリンクを参照してください。
https://github.com/tesseract-ocr/tesseract/wiki/ImproveQuality
Vietnamese
English