1. 程式人生 > >OCR 圖片識別 Tesseract基於Android Studio的示例演示搭建

OCR 圖片識別 Tesseract基於Android Studio的示例演示搭建

前言:之前在外包網站看到身份證識別和車牌號的識別的需求,立馬就想到了OCR技術。國內三巨頭BAT的雲端計算都提供了OCR技術服務,但他們的API大都收費;如何自己實現OCR呢?google開源的Tesseract就是今天的主題,tess-two是Tesseract在Android上的應用。

所需環境:
- Android Studio 2.2.2
- JDK1.8
- tesseract中文簡體字型檔chi_sim.traineddata download

一、新建專案TesserAct

開啟Android Studio,New Project 專案名暫定為TesserActOCR,Next後選擇Empty Activity,預設名為MainActivity,然後Finish;
在app的build.gradle中新增依賴:

    compile 'com.rmtheis:tess-two:8.0.0'

重新build成功後就可以在External Libraries中找到tess-two-8.0.0

二、專案設定和程式碼編寫

1、新增許可權:

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

若字型檔路徑的建立和複製是非程式完成的,可以去掉MOUNT_UNMOUNT_FILESYSTEMS

2、編寫佈局檔案activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.example.administrator.tesseractocr.MainActivity">

<ImageView
    android:id="@+id/img_ocr_source"
    android:src="@drawable/time"
    android:layout_gravity="center_horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

<TextView
    android:layout_marginTop="20dp"
    android:layout_width="match_parent"
    android:layout_height="30dp"
    android:text="識別結果:" />

<TextView
    android:id="@+id/ocr_result"
    android:layout_marginTop="20dp"
    android:layout_width="match_parent"
    android:layout_height="40dp" />

<Button
    android:text="識別時間"
    android:layout_marginTop="20dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="recognition" />
</LinearLayout>

4、複製目標檔案到res/drawable目錄

這裡寫圖片描述

5、MainActivity.java

public class MainActivity extends AppCompatActivity {

    private TextView tvOCRResult;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvOCRResult = (TextView) findViewById(R.id.ocr_result);
        getStorageAccessPermission();
    }
    /**
     * 識別按鈕
     * @param v
     */
    public void recognition(View v) {
        TessBaseAPI lvBaseAPI = null;
        try {
            Bitmap lvBitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.time);

            // 核心預設定程式碼
            lvBaseAPI = new TessBaseAPI();
            String path = Environment.getExternalStorageDirectory().getPath();
            lvBaseAPI.init(path + path + "/tesseract/", "chi_sim");

            lvBaseAPI.setPageSegMode(TessBaseAPI.PageSegMode.PSM_AUTO);
            lvBaseAPI.setImage(lvBitmap);

            // 獲取並顯示識別結果
            String result = lvBaseAPI.getUTF8Text();
            tvOCRResult.setText(result);
        } catch (Exception e) {
            Log.e("OCR", e.getMessage());
        } finally {
            lvBaseAPI.clear();
            lvBaseAPI.end();
        }
    }

    /**
     * 申請授權
     */
    private void getStorageAccessPermission() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            // 沒有獲得授權,申請授權
            if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                Toast.makeText(MainActivity.this, "請先授權!", Toast.LENGTH_LONG).show();
            } else {
                // 不需要解釋為何需要該許可權,直接請求授權
                ActivityCompat.requestPermissions(MainActivity.this,
                        new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
            }
            return;
        }
}

6、將下載好的字型檔放在手機的/storage/emulated/0/tesseract/tessdata/目錄下;

三、程式真機執行

執行授權後,點選識別時間按鈕,識別後的文字顯示在TextView中;

這裡寫圖片描述

可以看到圖片的部分文字識別是錯誤的,我們可以對字型庫進行訓練提高識別準確度,以後有時間再訓練吧。

四、Data path does not exist!路徑報錯解決

核心程式有下面兩句程式碼,path的輸出為:/storage/emulated/0,那我為什麼重複加了兩次path呢?

String path = Environment.getExternalStorageDirectory().getPath();
lvBaseAPI.init(path + path + "/tesseract/", "chi_sim");

在電腦上看字型檔的路徑確實是:
/storage/emulated/0/tesseract/tessdata/chi_sim.traineddata

但是使用手機檢視確是:
/storage/emulated/0/storage/emulated/0/tesseract/tessdata/chi_sim.traineddata
不是忽悠你,有圖有真相

這裡寫圖片描述

至於為什麼會發生這種情況,我暫時也沒有深入研究。所以先檢查一下自己字型檔的真實路徑保持和程式中的路徑一致就好。

五、原始碼解讀

進入init中找到原始碼init()方法

    if (!datapathFile.exists())
        throw new IllegalArgumentException("Data path does not exist!");

    File tessdata = new File(datapath + "tessdata");
    if (!tessdata.exists() || !tessdata.isDirectory())
        throw new IllegalArgumentException("Data path must contain subfolder tessdata!");

    //noinspection deprecation
    if (ocrEngineMode != OEM_CUBE_ONLY) {
        for (String languageCode : language.split("\\+")) {
            if (!languageCode.startsWith("~")) {
                File datafile = new File(tessdata + File.separator + 
                        languageCode + ".traineddata");
                if (!datafile.exists())
                    throw new IllegalArgumentException("Data file not found at " + datafile);
            }
        }
    }

可以看到Data path does not exist!是在這裡觸發的,同時還可以看到程式會在datapath的後面追加”tessdata”目錄和languageCode + “.traineddata”,這樣就能找到字型檔,這也是為什麼字型檔一定要放在tessdata資料夾中的原因。