Android 개발: Tesseract 타사 라이브러리를 통한 OCR

I. 소개

OCR         이란 무엇입니까 ? OCR(Optical Character Recognition, 광학 문자 인식)이란 전자 장치(예: 스캐너 또는 디지털 카메라)가 종이에 인쇄된 문자를 확인하고 어둡고 밝은 패턴을 감지하여 모양을 결정한 다음 문자 인식을 사용하여 모양을 변환하는 것을 의미합니다. 컴퓨터 텍스트 프로세스로 OCR은 간단히 말해서 종이 문서의 텍스트를 흑백 도트 매트릭스 이미지로 광학적으로 변환한 다음 이미지의 텍스트를 인식 소프트웨어를 통해 텍스트 형식으로 변환하여 워드 프로세싱 소프트웨어에서 추가 처리하는 기술입니다. .

        테 서랙트란 ? Tesseract는 원래 1985년에서 1994년 사이에 영국의 Hewlett-Packard Laboratories Bristol과 Hewlett-Packard Co, Greeley Colorado USA에서 개발되었으며, 1996년에 Windows로 포팅하기 위해 약간의 변경이 있었고 1998년에 일부 C++화되었습니다. 2005년 Tesseract HP에서 오픈 소스로 2006년부터 2018년 11월까지 Google에서 개발 Tesseract는 원래 1985년에서 1994년 사이 영국 브리스톨에 있는 HP 연구소와 1996년 미국 콜로라도 그릴리에 있는 HP에서 개발되었으며 1998년에 일부 변경이 이루어졌습니다. Windows로 포팅, 1998년 일부 C++화. 2005년에 HP는 Tesseract를 오픈 소스로 공개했습니다. 2006년부터 2018년 11월까지 Google에서 개발했습니다. 간단히 말해서 Tesseract는 OCR에서 위에서 언급한 "인식 소프트웨어"의 특정 구현입니다.

        OCR의 인식 대상(입력)은 그림이고 인식 결과(출력)는 컴퓨터 텍스트입니다. Android 휴대 전화 측에서 사진을 얻는 방법은 주로 두 가지가 있습니다. 하나는 사진 앨범에서 하나를 선택하는 것이고 다른 하나는 직접 사진을 찍는 것입니다. 따라서 이 기사에서는 가장 간단한 OCR 아이디어를 구현합니다. 먼저 휴대폰에서 사진을 얻은 다음 Tesseract 라이브러리에 입력하고 마지막으로 라이브러리를 통해 인식 결과를 출력합니다. 라이브러리 사용법을 배우는 단계이기 때문에 블로거는 사진 인식과 같은 다른 보조 기능을 무시합니다.

2. Android는 Tesseract를 통해 OCR을 구현합니다.

1. 모듈의 build.gradle 파일에 다음 종속성을 추가합니다.

implementation 'com.rmtheis:tess-two:9.1.0'

2. AndroidManifest.xml 파일에 다음 권한을 추가합니다.

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

3. MainActivity에서 권한 신청

        onCreate 메서드에서 다음 checkPermission 메서드를 실행하는 것이 좋습니다.

    // 检查应用所需的权限,如不满足则发出权限请求
    private void checkPermission() {
        if (ContextCompat.checkSelfPermission(getApplicationContext(),
                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 120);
        }
        if (ContextCompat.checkSelfPermission(getApplicationContext(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 121);
        }
    }

4. 자산에서 한 권 읽기

        표시용 및 식별용 모두

    // 从assets中读取一张Bitmap类型的图片
    private Bitmap getBitmapFromAssets(Context context, String filename) {
        Bitmap bitmap = null;
        AssetManager assetManager = context.getAssets();
        try {
            InputStream is = assetManager.open(filename);
            bitmap = BitmapFactory.decodeStream(is);
            is.close();
            Log.i(TAG, "图片读取成功。");
            Toast.makeText(getApplicationContext(), "图片读取成功。", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            Log.i(TAG, "图片读取失败。");
            Toast.makeText(getApplicationContext(), "图片读取失败。", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
        return bitmap;
    }

5. OCR 준비

https://github.com/tesseract-ocr/         로 이동하여 .traineddata 언어 팩을 다운로드한 다음 프로젝트 자산 디렉터리에 넣고 다음 코드를 통해 Android 파일 시스템에 복사합니다.

    // 为Tesserect复制(从assets中复制过去)所需的数据
    private void prepareTess() {
        try{
            // 先创建必须的目录
            File dir = getExternalFilesDir(TESS_DATA);
            if(!dir.exists()){
                if (!dir.mkdir()) {
                    Toast.makeText(getApplicationContext(), "目录" + dir.getPath() + "没有创建成功", Toast.LENGTH_SHORT).show();
                }
            }
            // 从assets中复制必须的数据
            String pathToDataFile = dir + "/" + DATA_FILENAME;
            if (!(new File(pathToDataFile)).exists()) {
                InputStream in = getAssets().open(DATA_FILENAME);
                OutputStream out = new FileOutputStream(pathToDataFile);
                byte[] buff = new byte[1024];
                int len;
                while ((len = in.read(buff)) > 0) {
                    out.write(buff, 0, len);
                }
                in.close();
                out.close();
            }
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
    }

6. 버튼 클릭 후 다음 메소드를 호출하여 OCR 인식을 수행합니다.

    // OCR识别的主程序
    private void mainProgram() {
        // 从assets中获取一张Bitmap图片
        Bitmap bitmap = getBitmapFromAssets(MainActivity.this, TARGET_FILENAME);
        // 同时显示在界面
        main_iv_image.setImageBitmap(bitmap);
        if (bitmap != null) {
            // 准备工作:创建路径和Tesserect的数据
            prepareTess();
            // 初始化Tesserect
            TessBaseAPI tessBaseAPI = new TessBaseAPI();
            String dataPath = getExternalFilesDir("/").getPath() + "/";
            tessBaseAPI.init(dataPath, "eng");
            // 识别并显示结果
            String result = getOCRResult(tessBaseAPI, bitmap);
            main_tv_result.setText(result);
        }
    }

    // 进行OCR并返回识别结果
    private String getOCRResult(TessBaseAPI tessBaseAPI, Bitmap bitmap) {
        tessBaseAPI.setImage(bitmap);
        String result = "-";
        try{
            result = tessBaseAPI.getUTF8Text();
        }catch (Exception e){
            Log.e(TAG, e.getMessage());
        }
        tessBaseAPI.end();
        return result;
    }

7. 컴파일하고 실행하면 그림과 같이 효과가 나타납니다.

        개인적으로 인식률이 그다지 정확하지 않다는 느낌이 듭니다.

8. 소스 코드 붙여넣기

        MainActivity.java

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
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 android.widget.Toast;

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;

public class MainActivity extends AppCompatActivity {

    public static final String TESS_DATA = "/tessdata";
    private static final String TARGET_FILENAME = "vin_demo.png";
    private static final String DATA_FILENAME = "eng.traineddata";
    private static final String TAG = MainActivity.class.getSimpleName();

    private Button main_bt_recognize;
    private TextView main_tv_result;
    private ImageView main_iv_image;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置布局文件
        setContentView(R.layout.activity_main);
        // 检查并请求应用所需权限
        checkPermission();
        // 获取控件对象
        initView();
        // 设置控件的监听器
        setListener();
    }

    private void setListener() {
        // 设置识别按钮的监听器
        main_bt_recognize.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 识别之前需要再次检查一遍权限
                checkPermission();
                // 点击后的主程序
                mainProgram();
            }
        });
    }

    // 获得界面需要交互的控件
    private void initView() {
        main_bt_recognize = findViewById(R.id.main_bt_recognize);
        main_tv_result = findViewById(R.id.main_tv_result);
        main_iv_image = findViewById(R.id.main_iv_image);
    }

    // 检查应用所需的权限,如不满足则发出权限请求
    private void checkPermission() {
        if (ContextCompat.checkSelfPermission(getApplicationContext(),
                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 120);
        }
        if (ContextCompat.checkSelfPermission(getApplicationContext(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 121);
        }
    }

    // OCR识别的主程序
    private void mainProgram() {
        // 从assets中获取一张Bitmap图片
        Bitmap bitmap = getBitmapFromAssets(MainActivity.this, TARGET_FILENAME);
        // 同时显示在界面
        main_iv_image.setImageBitmap(bitmap);
        if (bitmap != null) {
            // 准备工作:创建路径和Tesserect的数据
            prepareTess();
            // 初始化Tesserect
            TessBaseAPI tessBaseAPI = new TessBaseAPI();
            String dataPath = getExternalFilesDir("/").getPath() + "/";
            tessBaseAPI.init(dataPath, "eng");
            // 识别并显示结果
            String result = getOCRResult(tessBaseAPI, bitmap);
            main_tv_result.setText(result);
        }
    }

    // 从assets中读取一张Bitmap类型的图片
    private Bitmap getBitmapFromAssets(Context context, String filename) {
        Bitmap bitmap = null;
        AssetManager assetManager = context.getAssets();
        try {
            InputStream is = assetManager.open(filename);
            bitmap = BitmapFactory.decodeStream(is);
            is.close();
            Log.i(TAG, "图片读取成功。");
            Toast.makeText(getApplicationContext(), "图片读取成功。", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            Log.i(TAG, "图片读取失败。");
            Toast.makeText(getApplicationContext(), "图片读取失败。", Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
        return bitmap;
    }

    // 为Tesserect复制(从assets中复制过去)所需的数据
    private void prepareTess() {
        try{
            // 先创建必须的目录
            File dir = getExternalFilesDir(TESS_DATA);
            if(!dir.exists()){
                if (!dir.mkdir()) {
                    Toast.makeText(getApplicationContext(), "目录" + dir.getPath() + "没有创建成功", Toast.LENGTH_SHORT).show();
                }
            }
            // 从assets中复制必须的数据
            String pathToDataFile = dir + "/" + DATA_FILENAME;
            if (!(new File(pathToDataFile)).exists()) {
                InputStream in = getAssets().open(DATA_FILENAME);
                OutputStream out = new FileOutputStream(pathToDataFile);
                byte[] buff = new byte[1024];
                int len;
                while ((len = in.read(buff)) > 0) {
                    out.write(buff, 0, len);
                }
                in.close();
                out.close();
            }
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
    }

    // 进行OCR并返回识别结果
    private String getOCRResult(TessBaseAPI tessBaseAPI, Bitmap bitmap) {
        tessBaseAPI.setImage(bitmap);
        String result = "-";
        try{
            result = tessBaseAPI.getUTF8Text();
        }catch (Exception e){
            Log.e(TAG, e.getMessage());
        }
        tessBaseAPI.end();
        return result;
    }
}

        layout_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <ImageView
                android:id="@+id/main_iv_image"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_horizontal"
                android:layout_marginLeft="5dp"
                android:layout_marginRight="5dp"/>

            <Button
                android:id="@+id/main_bt_recognize"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_marginRight="5dp"
                android:layout_gravity="center_horizontal"
                android:text="读取一张图片并识别" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_marginRight="5dp"
                android:layout_gravity="center_horizontal"
                android:text="识别结果:" />

            <TextView
                android:id="@+id/main_tv_result"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginLeft="5dp"
                android:layout_marginRight="5dp"
                android:layout_gravity="center_horizontal" />
        </LinearLayout>
    </ScrollView>
</LinearLayout>

        AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.cs.ocrdemo4csdn">

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.OCRDemo4CSDN">

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

        build.gradle(모듈)

plugins {
    id 'com.android.application'
}

android {
    compileSdk 34

    defaultConfig {
        applicationId "com.cs.ocrdemo4csdn"
        minSdk 21
        targetSdk 34
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    implementation 'com.rmtheis:tess-two:9.1.0'
}

        vin_demo.png

인터넷에서 사진을 다운로드하기만 하면 됩니다(광학 문자가 있는지 확인).

        eng.traineddata

https://github.com/tesseract-ocr/ 에서 다운로드하고 , 그렇지 않은 경우 https://github.com/raykibul/Android-OCR-Testing/tree/main 에서 다운로드하세요 .

3. 참고 자료

        1. 광학 문자 인식

        2、GitHub - tesseract-ocr/tesseract

        3、GitHub - raykibul/Android-OCR-테스트

4. 결론

        1. 하루 넘게 남의 CSDN 블로그를 만지작거리고 있는데 통하지 못하고 "Could not initialize Tesseract API with language=eng", "getUTF8Text android tesseract 충돌" 및 기타 문제를 일으킵니다. 나중에 나는 GitHub에서 비교적 새로운 코드를 읽었고 (내 블로그를 참조했는데 여전히 코드를 통과할 수 없다면 이 코드를 살펴보는 것이 좋습니다), 그리고 나서 작동했습니다. 이유는 아직 알려지지 않았습니다.

        2. Tesseract 라이브러리의 인식 결과가 그다지 정확하지 않은 것 같고, 특히 사진 결과의 인식률이 매우 낮다고 생각합니다. Google이고 2018년에 멈춰 있습니다) 그래서 현재(올해는 2023년) 이미 약간 구식 기술이라는 느낌이 듭니다. 비교적 새로운 기술 솔루션을 찾을 수 있습니다.결국 대형 모델은 이제 매우 훌륭하고 OCR은 더 좋아야 합니다.

추천

출처blog.csdn.net/qq_36158230/article/details/131901162