tesseract4androidでの通常数字,7seg数字の認識テスト
16 KB page-sizeに対応

2025.9.29Coskx Lab  

1 はじめに

tesseract4androidAPIを利用して,通常数字・7seg数字の認識テストアプリをjavaで作成します。
テストアプリは,googleの推奨する16 KB page-sizeに対応します。

tesseractAPIはOCR(文字認識)ライブラリで,
 https://github.com/adaptech-cz/Tesseract4Android
で開発が継続,維持されていまして,tesseract4.9.0(202509現在)として公開されています。
 (READMEの先頭に[Jitpack 4.9.0]の表記があります。)
また4.8.0以降はgoogleの推奨する16 KB page-sizeに対応しています。
(https://github.com/adaptech-cz/Tesseract4Android/issues/80)

tesseractAPIでは文字を学習させたtraineddataファイルを使います。
ここでは通常の英数文字セットのための"eng.tesseract"と"7seg.tesseract"の2つのファイルを使用しています。
テストアプリでは,tesseractAPIにテスト画像のbitmapを与えて,認識ボタンで文字認識を起動し,認識結果文字列を画面に表示させます。

2 使用環境


3 AndroidStudioでの準備作業

(1) 新しいprojectを作る
(2) Gradle Scriptsの設定
(3) テスト用画像ファイルの埋め込み
(4) layout定義用ファイルの上書き
(5) traineddataファイルの取得と埋め込み

使用する主要ファイルのダウンロード

3.1 新しいprojectを作る

・(ファイルメニュー File -> New -> )New Project --> Empty Views Activity
・New Project の名前は testTesseract4 とします。(別の名前でもOK)
・AppおよびOPenCVモジュールでjavaを使用するので,
   Language → Java
   Build configuration language → Groovy DSL(build.gradle)
 とします。
・パッケージ名は「jp.gr.java_conf.coskx.testtessaract4」(この文書での)です。デフォルトのパッケージ名のままでOKです。
・他の設定はそのまま -> Finish

3.2 Gradle Scriptsの設定

(1) settings.gradle
dependencyResolutionManagement {}中の
repositories {}中に次の1行を追加します。
maven { url 'https://jitpack.io' }
(このことはgithub tesseract4androidのREADME中に記載があります。)

(2) build.gradle(Module:app)
dependencies {}中に次の1行を追加します。
implementation libs.cz.tesseract4android // standard flavor
(dependenciesへの追加に関しては,github tesseract4androidのREADME中に記載がありますが,README中の記載通りにStandard variantで記述しても,上記のように変更を促されます。)

(3) libs.versions.toml
[versions]中に次の1行を追加します。
tesseract4androidVersion = "4.9.0"
[libraries]中に次の1行を追加します。
cz-tesseract4android = { module = "cz.adaptech.tesseract4android:tesseract4android", version.ref = "tesseract4androidVersion" }
上記"4.9.0"のところは,公開バージョンが更新された場合,変更を促されます。
(ここの記述は追記しなくても,androidstudioが自動生成するかもしれません。)

3.3 テスト用画像ファイルの埋め込み

テスト用画像の提示方法はいろいろありますが,テストするだけなので,リソースとして与えることにしました。
ダウンロードしたtestTesseract4files.zip中の
sample_image.jpg
を app>res>drawableに入れてください。

3.4 layout定義ファイルの上書き

ダウンロードしたtestTesseract4files.zip中の
activity_main.xml
をapp>res>layoutに入れて,上書きしてください。

3.5 traineddataファイルの取得と埋め込み

通常数字,7seg数字の認識を行いたいため,英数用ファイル(eng.trainesdata)と7seg文字用ファイル(7seg.traineddata)をそれぞれダウンロードし,assetsに埋め込みます。
eng.traineddataは次のURLからダウンロードします。
 https://github.com/tesseract-ocr/tessdata/tree/4.0.0
7seg.traineddataは次のURLからダウンロードします。
 https://github.com/Shreeshrii/tessdata_ssd
★ダウンロードに当たっては,当該xxx.traineddataを開いて,ダウンロードボタン(Download raw file)でダウンロードしてください。それぞれ20MB,10MBほどのファイルです。ファイルサイズを確認してください。

取り出せた2つのファイルはフォルダassetsに保存します。
フォルダassetsは,標準では作られていないため,作成します。
場所はapp直下です。
左のandroid構成一覧で,appのアイコン上で右クリック>new>directoryで作成します。(出てきたフォルダ候補中で src\main\assets を選びます。)



フォルダassets内にフォルダtessdataを作って,その中にeng.traineddataと7seg.traineddataを埋め込みます。
次のように出来たら成功です。



4 MainActivity.java

MainActivity.javaは,ダウンロードしたtestTesseract4files.zip中に入っています。
tesseractAPIの利用では,tesseractAPIを初期化して,tesseractAPIに画像bitmapを与えて,tesseractAPIから認識結果を得るだけですので,面倒はありません。
しかしtraineddataファイルの扱いが面倒です。

androidアプリでは,assets領域に必要なファイル(traineddataファイル)をおいてコンパイルするため,出来たアプリでは,assets領域にファイルが置かれたままになっています。
実行時に,assets領域のファイル(traineddataファイル)をアプリ固有の内部ストレージにコピーしておき,tesseractAPI初期化の際に固有の内部ストレージにあるtraineddataファイルを与えるようになります。

そのため,onCreate()内で
(1)TessBaseAPIのインスタンスtessを生成
 tess = new TessBaseAPI();
(2)assets領域のファイル(traineddataファイル)をアプリ固有の内部ストレージにコピー
 setReadyTrainedFile();
(3)tesseractAPI初期化の際に固有の内部ストレージにあるtraineddataファイルを与える
 initTessWithTrainedFile();
を行っています。

なお,TessBaseAPIのインスタンスtessを生成直後に
TessBaseAPIのversionを取得したら5.5.1となっていました。4.9.0のはずではなかったのかな?

途中で,使用しているパスを確認のため表示しています。
アプリ固有の内部ストレージのパスは
「...../(パッケージ名)/files」
となっています。
traineddataファイルを置くためのパスは
「...../(パッケージ名)/files/tessdata」
となっています。「tessdata」の名前はこの名前で固定です。

TessBaseAPI init()では
アプリ固有の内部ストレージのパス(「...../(パッケージ名)/files」)と,traineddataの接頭語の「eng」と「7seg」を与えます。「eng+7seg」で両方使えという意味になります。
TessBaseAPI setPageSegMode()では,認識モードを与えることができます。
TessBaseAPI setVariable()では,ホワイトリストを与えることができます。ホワイトリストを与えると,数字に限った認識を行う場合にはご認識が少なくなります。


   MainActivity.java

package jp.gr.java_conf.coskx.testtessaract4;

import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;

import com.googlecode.tesseract.android.TessBaseAPI;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    TessBaseAPI tess;
    final String TAG = "MainActivity";
    private Button button1;
    private TextView textview1;
    private Bitmap bitmap1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        Log.i(TAG,"onCreate()");

        //Layout
        button1 = findViewById(R.id.button1);
        button1.setOnClickListener(this);
        textview1 = findViewById(R.id.textview1);
        bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.sample_image);
        ImageView imageview1 = findViewById(R.id.imageview1);
        imageview1.setImageBitmap(bitmap1); //テスト用imageを表示

        // TessBaseAPIインスタンスの作成
        tess = new TessBaseAPI();
        Log.i(TAG,"onCreate() new TessBaseAPI done");
        String tessversion = tess.getVersion();
        Log.i(TAG,"onCreate() TessBaseAPI version = " + tessversion);

        //Assetsからアプリ固有の内部storageへのファイルコピー
        setReadyTrainedFile();
        Log.i(TAG,"onCreate() setReadyTrainedFile() done");

        //tesseractAPIに学習ファイル(traineddata)を与えて初期化します
        initTessWithTrainedFile();
        Log.i(TAG,"onCreate() initTessWithTrainedFile() done");
    }

    @Override
    protected  void onResume() {
        super.onResume();
        Log.i(TAG,"onResume()");
        textview1.setText("Hello, world!");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.i(TAG,"onPause()");
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        tess.recycle();
    }

    public void onClick(View view) {
        if (view == button1) {
            Log.i(TAG,"onClick() view == button1");
            String recognizedText= "no data";

            //initTessWithTrainedFile();
            //Log.i(TAG,"onClick() initTessWithTrainedFile() done");

            tess.setImage(bitmap1);
            recognizedText = tess.getUTF8Text();
            textview1.setText(recognizedText);
            Log.i(TAG,"onClick() recognizedText = [" + recognizedText +"]");
        }
    }

    public void setReadyTrainedFile() {

        AssetManager assetManager = getAssets();

        /* 確認用
        String[] fileList = null;
        try {
            fileList = assetManager.list("tessdata");
            Log.i(TAG,"setReadyTrainedFile() fileList = " + Arrays.toString(fileList));
        } catch (IOException e) {
            e.printStackTrace();
        }
        */

        String owndatapath = this.getFilesDir() + "/"; //app specific internal storage path
        Log.i(TAG,"setReadyTrainedFile() owndatapath = " + owndatapath);
        //owndatapath = /data/user/0/jp.gr.java_conf.coskx.testtessaract4/files/

        //traineddataファイルをコピーするためのディレクトリを作成
        String[] paths = new String[] { owndatapath, owndatapath + "tessdata/" };

        for (String path : paths) {
            Log.i(TAG,"setReadyTrainedFile() path = " + path);
            File dir = new File(path);
            if (!dir.exists()) {
                if (!dir.mkdirs()) {
                    Log.v(TAG, "ERROR: on Creation of directory [" + path + "]");
                    return;
                } else {
                    Log.v(TAG, "Created directory [" + path + "]");
                }
            }

        }
        //traineddataファイルをコピー (eng.traineddataと7seg.traineddata)
        copyTessTrainedFile("eng", assetManager, owndatapath);
        copyTessTrainedFile("7seg", assetManager, owndatapath);
    }

    public void initTessWithTrainedFile() {

        String owndatapath = this.getFilesDir() + "/"; //app specific internal storage path

        /* ファイルが思った場所にあるかどうか確認用です
        String ldpass1 = owndatapath + "tessdata/eng.traineddata";
        if ((new File(ldpass1)).exists()) {
            Log.i(TAG,"initTessWithTrainedFile() exist [" + ldpass1 + "]");
        } else {
            Log.i(TAG,"initTessWithTrainedFile() NOT exist [" + ldpass1 + "]");
        }
        String ldpass2 = owndatapath + "tessdata/7seg.traineddata";
        if ((new File(ldpass2)).exists()) {
            Log.i(TAG,"initTessWithTrainedFile() exist [" + ldpass2 + "]");
        } else {
            Log.i(TAG,"initTessWithTrainedFile() NOT exist [" + ldpass2 + "]");
        }
        */

        String tessdataPath = owndatapath; //フォルダ名"tessdata"はinit()内で追加される
        Log.i(TAG,"initTessWithTrainedFile() tessdataPath = " + tessdataPath);
        // 初期化
        // "eng+7seg"はeng.traineddataと7seg.traineddataの2つを指示しています
        if (!tess.init(tessdataPath, "eng+7seg")) {
            Log.i(TAG,"initTessWithTrainedFile() tess.init failed");
            tess.recycle();
            return;
        }
        tess.setPageSegMode(TessBaseAPI.PageSegMode.PSM_SINGLE_BLOCK);
        //ホワイトリスト指示
        tess.setVariable(TessBaseAPI.VAR_CHAR_WHITELIST, "-01:23456789.");

    }

    //assets/tessdataにあるtraineddataファイルをアプリ固有の内部ストレージにコピーする
    //ただし,すでにアプリ固有の内部ストレージ内に当該ファイルが存在する場合はコピーしない
    private void copyTessTrainedFile(String lang, AssetManager assetManager, String owndatapath) {
        if (!(new File(owndatapath + "tessdata/" + lang + ".traineddata")).exists()) {
            try {

                //AssetManager assetManager = getAssets();
                InputStream in = assetManager.open("tessdata/" + lang + ".traineddata");
                //GZIPInputStream gin = new GZIPInputStream(in);
                OutputStream out = new FileOutputStream(owndatapath
                        + "tessdata/" + lang + ".traineddata");

                // Transfer bytes from in to out
                byte[] buf = new byte[1024];
                int len;
                //while ((lenf = gin.read(buff)) > 0) {
                while ((len = in.read(buf)) > 0) {
                    out.write(buf, 0, len);
                }
                in.close();
                //gin.close();
                out.close();

                Log.v(TAG, "Copied " + lang + " traineddata");
            } catch (IOException e) {
                Log.e(TAG, "Was unable to copy " + lang + " traineddata " + e.toString());
            }
        } else {
            Log.i(TAG, " already the file exists, quit copying " + lang + " traineddata ");
        }
    }
}

5 実行の様子

実機で実行すると左図のように表示され,ボタンを押すと認識結果が右図のように表示されます。

test1  test2

6 補足

このアプリはtesseractAPIの利用方法を調べるために作成しました。tesseractAPIの認識性能を検証したわけではありません。
検証に当たっては,認識しやすい画像を使用しました。
以前tesseract2を使ってみましたが,7seg文字の認識は苦手だと思っておりました。
その点はまだ解決していないようです。

試しに次のような画像で認識を試みましたが,7seg文字は認識できませんでした。
7seg文字の文字素間の隙間が苦手なようです。黒い部分を膨張させるなどの認識前処理が必要と思います。
sample_imageNG

https://github.com/adaptech-cz/Tesseract4Android のREADMEは参考になりましたが,tesseractAPI init()へ与えるパスに関しては,プログラムsampleそのままではうまくいきませんでした。
init()内で"tessdata"を付け加えているので,initへ与えるパスに"tessdata"を付け加える必要はありませんでした。

7 まとめ

tesseract4androidのテストアプリを作成し,動作を確認しました。
githubからtesseractAPIを直接利用する方法の説明がなかなか見つからない中で,時間はかかりましたが,目的を達成しました。