Tesseract iOSへの導入について

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