1. 程式人生 > >Android opencv實現文字識別

Android opencv實現文字識別

Android 文字識別

        因為公司下個專案要用到OCR(光學字元識別),我們組leader就讓我準備一下我的專案是主要參考的是tess_two Android圖片文字識別,選拍照或者從本地相簿選取照片,然後呼叫本地裁剪,最後開始識別,識別結果還可以,希望能對大家有幫助。先上圖再說:

OCR文字識別離不開tesseract,tesseract是Google開源的OCR識別工具,因為tesseract是用C/C++實現的,要封裝JavaAPI才能在Android上使用。tess-two就是前輩們封裝了Android開發環境的tesseract配置。所以我們直接用tess-two就可以了,使用tess-two有兩種辦法:

第一種比較簡單,直接在app的build.gradle下新增tess-two依賴庫就可以了:

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

第二種比較繁瑣,想了解的朋友可以看下,不想了解的朋友直接跳過。

2.給Androidstudio安裝NDK,開啟左上角File-->Settings,找到在 Appearance & Behavior下的System Settings,然後開啟Android SDK-->SDK Tools

找到下面的NDK,點選下載,下載成功後開啟File-->Project Structure,找到SDK Location,新增ndk-bundle路徑(找到你自己下載的ndk-bundle路徑),

3.此電腦右擊屬性-->高階系統設定-->環境變數,找到系統變數下的path路徑,單擊編輯-->新建,把你ndk-bundle路徑新增進去。

4.開啟終端(windows+R),輸入cmd,進入你下載的tess-two目錄下的jni資料夾下,執行ndk-build命令,會在tess-two資料夾下生成libs資料夾,libs資料夾裡面是生成的.so檔案。然後把tess-two生成的libs資料夾裡面的檔案拷貝到Androidstudio專案的app下,最後把tess-two\src下的com資料夾拷貝到自己專案src\main\java目錄下,至此tess-two就可以使用了。

opencv使用:

下載opencv:opencv下載,這裡有各種不同版本,你們可以到opencv官網下載

下載完成後,目錄下的內容是這樣的

opencv安裝可以參考:opencv安裝。注意:opencv下build.gradle下的引數設定必須和app下build.gradle引數設定一致,不然會報錯!

下面是程式碼部分

 1。開啟相簿

 private void openAlbum() {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        startActivityForResult(intent, PICK_PHOTO);
    }

2 。啟動相機

 private void openCamera() {
        imageUri = Uri.fromFile(new File(mFilePath));
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        //傳遞你要儲存的圖片的路徑
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        startActivityForResult(intent, TAKE_PHOTO);
    }

其中imgUri是你拍照的圖片的儲存路徑,DATAPATH是得到手機系統根目錄,然後把拍攝的圖片手機相簿目錄下,名字命名為photo.jpg

 private static final String DATAPATH = Environment.getExternalStorageDirectory()
            .getAbsolutePath() + File.separator;
mFilePath = DATAPATH + "/DCIM/Camera/" + "photo.jpg";

3 。執行startActivityForResult後的回撥函式

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            if (requestCode == PICK_PHOTO) {
                imageUri = data.getData();
                Intent intent = new Intent("com.android.camera.action.CROP");
                intent.setDataAndType(imageUri, "image/*");
                intent.putExtra("crop", true);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                startActivityForResult(intent, CROP_PHOTO); // 啟動裁剪程式
            } else if (requestCode == TAKE_PHOTO) {
                Intent intent = new Intent("com.android.camera.action.CROP");
                intent.setDataAndType(imageUri, "image/*");
                intent.putExtra("crop", true);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                startActivityForResult(intent, CROP_PHOTO); // 啟動裁剪程式
            } else if (requestCode == CROP_PHOTO) {
                try {
                    srcBitmap = BitmapFactory.decodeStream(getContentResolver().
                            openInputStream(imageUri));
                    proSrc2Gray();
                    saveImage(mBitmap, "photo.jpg");
                    sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
                            Uri.fromFile(new File(mFilePath))));
                    if (mBitmap != null) {
                        showPicFileByLuban(mFilePath);
                        imgView.setImageBitmap(mBitmap); // 將裁剪後的照片顯示出來
                        imgView.setVisibility(View.VISIBLE);
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
                            Uri.fromFile(new File(mFilePath))));

這個方法為更新相簿,得到最新圖片

4 。拍照影象處理,因為最後對影象二值化、腐蝕和膨脹的識別結果不滿意,所以我這隻先用了灰度化,以後有了進一步進展,會來更正。

  //影象處理
    public void proSrc2Gray() {
        Mat rgbMat = new Mat();
        Mat grayMat = new Mat();
        Mat binaryMat = new Mat();
        Mat cannyMat = new Mat();


//        Mat canny = new Mat();

        //獲取彩色影象所對應的畫素資料
        Utils.bitmapToMat(srcBitmap, rgbMat);
        //影象灰度化,將彩色影象資料轉換為灰度影象資料並存儲到grayMat中
        Imgproc.cvtColor(rgbMat, grayMat, Imgproc.COLOR_RGB2GRAY);
        //得到邊緣圖,這裡最後兩個引數控制著選擇邊緣的閥值上限和下限
//        Imgproc.Canny(grayMat, cannyMat, 50, 300);
        //二值化
//        Imgproc.threshold(grayMat, binaryMat, 100, 255, Imgproc.THRESH_BINARY);
        //獲取自定義核,引數MORPH_RECT表示矩形的卷積核,當然還可以選擇橢圓形的、交叉型的
//        Mat strElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT,
//                new Size(2, 2));
//        //腐蝕
//        Imgproc.dilate(binaryMat,cannyMat,strElement);

//        Imgproc.HoughLinesP(binaryMat,cannyMat,1,);
        //建立一個影象
        mBitmap = Bitmap.createBitmap(grayMat.cols(), grayMat.rows(),
                Bitmap.Config.RGB_565);
        //將矩陣binaryMat轉換為影象

        Utils.matToBitmap(grayMat, mBitmap);
    }

5 。程式碼中使用opencv時,我們可以進入Imgproc.cvtColor原始碼中看到opencv中方法都是在本地。

  //javadoc: cvtColor(src, dst, code)
    public static void cvtColor(Mat src, Mat dst, int code)
    {
        
        cvtColor_1(src.nativeObj, dst.nativeObj, code);
        
        return;
    }

再點選 cvtColor_1,

 private static native void cvtColor_1(long src_nativeObj, long dst_nativeObj, int code);

因為使用的是本地方法,所以我們載入本地庫檔案,不論是JNI庫檔案還是非JNI庫檔案,在任何本地方法被呼叫之前必須先用System.load或者 System.loadLibrary把相應的JNI庫檔案裝載。而opencv提供了載入本地庫檔案的介面。

public class OpenCVNativeLoader implements OpenCVInterface {

    public void init() {
        System.loadLibrary("opencv_java3");
        Logger.getLogger("org.opencv.osgi").log(Level.INFO, "Successfully loaded OpenCV native library.");
    }
}

所以只要呼叫這個封裝的類就可以。,在mainActivity中新增

 private OpenCVNativeLoader loader = new OpenCVNativeLoader();
 loader.init();

呼叫loader.init()方法,就可以使用opencv。

6. 。用opencv處理完圖片之後,使用了saveImage方法將原圖覆蓋

 public void saveImage(Bitmap bitmap, String fileName) {
        File appDir = new File(DATAPATH + "/DCIM/Camera/");
        if (!appDir.exists()) {
            appDir.mkdirs();
        }
        File file = new File(DATAPATH + "/DCIM/Camera/", fileName);
        try {
            // 建立一個向指定 File 物件表示的檔案中寫入資料的檔案輸出流
            FileOutputStream fos = new FileOutputStream(file);
            //壓縮圖片,按指定的圖片格式以及畫質,將圖片轉換為輸出流。
            //quality:畫質,0-100.0表示最低畫質壓縮,100以最高畫質壓縮,不壓縮。
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            //flush()強制將緩衝區中的資料傳送出去,不必等到緩衝區滿
            fos.flush();
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

7 。影象裁剪後,會得到一個灰度化的影象,但是由於現在手機畫素越來越高,拍照的記憶體越來越大,如果直接識別拍照的圖片耗費時間很長,所以我在這對裁剪後的圖片進行了壓縮處理。

壓縮圖片我使用的是魯班(Luban),在build.gradle下新增依賴

compile 'top.zibin:Luban:1.1.3'
  private void showPicFileByLuban(String path) {
        Luban.with(this)
                .load(new File(path))
                .setCompressListener(new OnCompressListener() {
                    @Override
                    public void onStart() {
                        // TODO 壓縮開始前呼叫,可以在方法內啟動 loading UI
                    }

                    @Override
                    public void onSuccess(File file) {
                        // TODO 壓縮成功後呼叫,返回壓縮後的圖片檔案
                        ToastUtil.showToast(MainActivity.this, "hah");
                        mBitmap = BitmapFactory.decodeFile(file.getPath());
//                        imgUri=Uri.fromFile(file);
//                        txtSize2.setText(file.length() / 1024 + "K");
                        ToastUtil.showToast(MainActivity.this,
                                file.length() / 1024 + "K");
                    }

                    @Override
                    public void onError(Throwable e) {
                        // TODO 當壓縮過去出現問題時呼叫
                    }
                }).launch();//啟動壓縮
    }

8 。壓縮完成後開始識別,tessBaseAPI.init方法第一個引數是手機根目錄,第二個引數是識別庫的名字,不帶字尾名

TessBaseAPI tessBaseAPI = new TessBaseAPI();
tessBaseAPI.init(DATAPATH, DEFAULT_LANGUAGE);
//識別的圖片
tessBaseAPI.setImage(bitmap);
//獲得識別後的字串
text = "識別結果:" + "\n" + tessBaseAPI.getUTF8Text();

因為我們要做的是識別身份證號碼,所以我對識別結果進行處理,根據ASCII碼錶,從字串中提取字母和數字,其中65~90對應A~Z,48~57對應0~9,97~122對應a~z

 for (int i = 0; i < finalText.length(); i++) {
                            if ((finalText.charAt(i) >= 48 && finalText.charAt(i) <= 57) ||
                                    (finalText.charAt(i) >= 65 && finalText.charAt(i) <= 90)) {
                                str += finalText.charAt(i);
                            }
                        }
                        txtFinal.setText(str);

至此,我們已經能夠進行基本的識別了

MainActivity完整程式碼:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    //TessBaseAPI初始化用到的第一個引數,是個目錄
    private static final String DATAPATH = Environment.getExternalStorageDirectory()
            .getAbsolutePath() + File.separator;
    //在DATAPATH中新建這個目錄,TessBaseAPI初始化要求必須有這個目錄
    private static final String tessdata = DATAPATH + File.separator + "tessdata";
    //TessBaseAPI初始化測第二個引數,就是識別庫的名字不要字尾名。
    private static String DEFAULT_LANGUAGE = "chi_sim";
    //assets中的檔名
    private static String DEFAULT_LANGUAGE_NAME = DEFAULT_LANGUAGE + ".traineddata";
    //儲存到SD卡中的完整檔名
    private static String LANGUAGE_PATH = tessdata + File.separator + DEFAULT_LANGUAGE_NAME;

    private static final int PICK_PHOTO = 1;
    private static final int TAKE_PHOTO = 2;
    private static final int CROP_PHOTO = 3;

    private OpenCVNativeLoader loader = new OpenCVNativeLoader();

    private Button recBtn;
    private TextView resultTv;
    private TextView txtFinal;
    private Button pickBtn;
    private Button takePhoto;
    private ImageView imgView;
    private Spinner spinner;

    private String mFilePath;
    private Uri imageUri;

    private Bitmap srcBitmap;
    private Bitmap mBitmap;

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

        recBtn = (Button) findViewById(R.id.btn_rec);
        pickBtn = (Button) findViewById(R.id.btn_pick);
        takePhoto = (Button) findViewById(R.id.btn_take);
        resultTv = (TextView) findViewById(R.id.result);
        txtFinal = (TextView) findViewById(R.id.finalResult);
        imgView = (ImageView) findViewById(R.id.img);
        spinner = (Spinner) findViewById(R.id.spinner);

        recBtn.setOnClickListener(this);
        pickBtn.setOnClickListener(this);
        takePhoto.setOnClickListener(this);
        imgView.setVisibility(View.INVISIBLE);

        mFilePath = DATAPATH + "/DCIM/Camera/" + "photo.jpg";

        spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                String array[] = getResources().getStringArray(R.array.trainedData);
                if (position == 0) {
                    DEFAULT_LANGUAGE = array[0];
                } else {
                    DEFAULT_LANGUAGE = array[position];
                }
                DEFAULT_LANGUAGE_NAME = DEFAULT_LANGUAGE + ".traineddata";
                LANGUAGE_PATH = tessdata + File.separator + DEFAULT_LANGUAGE_NAME;
            }

            @Override
            public void onNothingSelected(AdapterView<?> parent) {

            }
        });
        loader.init();
        requestPermissions();
    }

    private void requestPermissions() {
        if (Build.VERSION.SDK_INT >= 23) {
            if ((ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) &&
                    (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)) {
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, 1);
            }
        }
    }

    //開啟相簿
    private void openAlbum() {
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        startActivityForResult(intent, PICK_PHOTO);
    }

    //啟動相機
    private void openCamera() {
        imageUri = Uri.fromFile(new File(mFilePath));
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//        Intent intent = new Intent(this, Camera2Activity.class);
        //傳遞你要儲存的圖片的路徑
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
        startActivityForResult(intent, TAKE_PHOTO);
    }

    //影象處理
    public void proSrc2Gray() {
        Mat rgbMat = new Mat();
        Mat grayMat = new Mat();
        Mat binaryMat = new Mat();
        Mat cannyMat = new Mat();


//        Mat canny = new Mat();

        //獲取彩色影象所對應的畫素資料
        Utils.bitmapToMat(srcBitmap, rgbMat);
        //影象灰度化,將彩色影象資料轉換為灰度影象資料並存儲到grayMat中
        Imgproc.cvtColor(rgbMat, grayMat, Imgproc.COLOR_RGB2GRAY);
        //得到邊緣圖,這裡最後兩個引數控制著選擇邊緣的閥值上限和下限
//        Imgproc.Canny(grayMat, cannyMat, 50, 300);
        //二值化
//        Imgproc.threshold(grayMat, binaryMat, 100, 255, Imgproc.THRESH_BINARY);
        //獲取自定義核,引數MORPH_RECT表示矩形的卷積核,當然還可以選擇橢圓形的、交叉型的
//        Mat strElement = Imgproc.getStructuringElement(Imgproc.MORPH_RECT,
//                new Size(2, 2));
//        //腐蝕
//        Imgproc.dilate(binaryMat,cannyMat,strElement);

//        Imgproc.HoughLinesP(binaryMat,cannyMat,1,);
        //建立一個影象
        mBitmap = Bitmap.createBitmap(grayMat.cols(), grayMat.rows(),
                Bitmap.Config.RGB_565);
        //將矩陣binaryMat轉換為影象

        Utils.matToBitmap(grayMat, mBitmap);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (resultCode == RESULT_OK) {
            if (requestCode == PICK_PHOTO) {
                imageUri = data.getData();
                Intent intent = new Intent("com.android.camera.action.CROP");
                intent.setDataAndType(imageUri, "image/*");
                intent.putExtra("crop", true);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                startActivityForResult(intent, CROP_PHOTO); // 啟動裁剪程式
            } else if (requestCode == TAKE_PHOTO) {
                Intent intent = new Intent("com.android.camera.action.CROP");
                intent.setDataAndType(imageUri, "image/*");
                intent.putExtra("crop", true);
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
                startActivityForResult(intent, CROP_PHOTO); // 啟動裁剪程式
            } else if (requestCode == CROP_PHOTO) {
                try {
                    srcBitmap = BitmapFactory.decodeStream(getContentResolver().
                            openInputStream(imageUri));
                    proSrc2Gray();
                    saveImage(mBitmap, "photo.jpg");
                    sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
                            Uri.fromFile(new File(mFilePath))));
                    if (mBitmap != null) {
                        showPicFileByLuban(mFilePath);
                        imgView.setImageBitmap(mBitmap); // 將裁剪後的照片顯示出來
                        imgView.setVisibility(View.VISIBLE);
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void showPicFileByLuban(String path) {
        Luban.with(this)
                .load(new File(path))
                .setCompressListener(new OnCompressListener() {
                    @Override
                    public void onStart() {
                        // TODO 壓縮開始前呼叫,可以在方法內啟動 loading UI
                    }

                    @Override
                    public void onSuccess(File file) {
                        // TODO 壓縮成功後呼叫,返回壓縮後的圖片檔案
                        ToastUtil.showToast(MainActivity.this, "hah");
                        mBitmap = BitmapFactory.decodeFile(file.getPath());
//                        imgUri=Uri.fromFile(file);
//                        txtSize2.setText(file.length() / 1024 + "K");
                        ToastUtil.showToast(MainActivity.this,
                                file.length() / 1024 + "K");
                    }

                    @Override
                    public void onError(Throwable e) {
                        // TODO 當壓縮過去出現問題時呼叫
                    }
                }).launch();//啟動壓縮
    }

    public void saveImage(Bitmap bitmap, String fileName) {
        File appDir = new File(DATAPATH + "/DCIM/Camera/");
        if (!appDir.exists()) {
            appDir.mkdirs();
        }
        File file = new File(DATAPATH + "/DCIM/Camera/", fileName);
        try {
            // 建立一個向指定 File 物件表示的檔案中寫入資料的檔案輸出流
            FileOutputStream fos = new FileOutputStream(file);
            //壓縮圖片,按指定的圖片格式以及畫質,將圖片轉換為輸出流。
            //quality:畫質,0-100.0表示最低畫質壓縮,100以最高畫質壓縮,不壓縮。
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            //flush()強制將緩衝區中的資料傳送出去,不必等到緩衝區滿
            fos.flush();
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_take:
                openCamera();
                break;
            case R.id.btn_pick:
                openAlbum();
                break;
            case R.id.btn_rec:
                if (imgView.getVisibility() != View.VISIBLE) {
                    Toast.makeText(getApplicationContext(), "請先拍照或者選一張圖片", Toast.LENGTH_SHORT).show();
                    return;
                } else {
                    resultTv.setText("");
                    txtFinal.setText("");
                    try {
                        mBitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                    recognition(mBitmap);
                }
                break;
        }
    }

    //許可權請求返回
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[]
            permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case PICK_PHOTO:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    openAlbum();
                } else {
                    Toast.makeText(this, "你拒絕了許可權!", Toast.LENGTH_SHORT).show();
                }
                break;
            case TAKE_PHOTO:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    openCamera();
                } else {
                    Toast.makeText(this, "你拒絕了許可權!", Toast.LENGTH_SHORT).show();
                }
                break;
            default:
                break;
        }
    }

    private boolean checkTrainedDataExists() {
        File file = new File(LANGUAGE_PATH);
        return file.exists();
    }

    //識別影象
    private void recognition(final Bitmap bitmap) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (!checkTrainedDataExists()) {
                    SDUtils.assets2SD(getApplicationContext(), LANGUAGE_PATH, DEFAULT_LANGUAGE_NAME);
                }
                TessBaseAPI tessBaseAPI = new TessBaseAPI();
                tessBaseAPI.setDebug(true);
                tessBaseAPI.init(DATAPATH, DEFAULT_LANGUAGE);
                //識別的圖片
                tessBaseAPI.setImage(bitmap);
                //獲得識別後的字串
                String text = "";
                text = "識別結果:" + "\n" + tessBaseAPI.getUTF8Text();
                final String finalText = text;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        resultTv.setText(finalText);
                        String str = "";
                        for (int i = 0; i < finalText.length(); i++) {
                            if ((finalText.charAt(i) >= 48 && finalText.charAt(i) <= 57) ||
                                    (finalText.charAt(i) >= 65 && finalText.charAt(i) <= 90)) {
                                str += finalText.charAt(i);
                            }
                        }
                        txtFinal.setText(str);
                    }
                });
                tessBaseAPI.end();
            }
        }).start();
    }

activity_main佈局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <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/img"
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:scaleType="centerInside" />

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

                <Button
                    android:id="@+id/btn_take"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="拍照" />

                <Button
                    android:id="@+id/btn_pick"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="選擇圖片" />

                <Button
                    android:id="@+id/btn_rec"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="識別" />

                <Spinner
                    android:id="@+id/spinner"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:entries="@array/trainedData" />
            </LinearLayout>

            <TextView
                android:id="@+id/result"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />

            <TextView
                android:id="@+id/finalResult"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>
    </ScrollView>
</LinearLayout>

SDUitils程式碼

public class SDUtils {
    /**
     * 將assets中的識別庫複製到SD卡中
     *
     * @param path 要存放在SD卡中的 完整的檔名。這裡是"/storage/emulated/0//tessdata/chi_sim.traineddata"
     * @param name assets中的檔名 這裡是 "chi_sim.traineddata"
     */
    public static void assets2SD(Context context, String path, String name) {
        //如果存在就刪掉
        File f = new File(path);
        if (f.exists()) {
            f.delete();
        }
        if (!f.exists()) {
            File p = new File(f.getParent());//返回此抽象路徑名父目錄的路徑名字串
            if (!p.exists()) {
                p.mkdirs();//建立多級資料夾
            }
            try {
                f.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        InputStream is = null;
        OutputStream os = null;
        try {
            //開啟assets檔案獲得一個InputStream位元組輸入流
            is = context.getAssets().open(name);
            File file = new File(path);
            // 建立一個向指定 File 物件表示的檔案中寫入資料的檔案輸出流
            os = new FileOutputStream(file);
            byte[] bytes = new byte[2048];
            int len = 0;
            //從輸入流中讀取一定數量的位元組,並將其儲存在緩衝區陣列bytes中
            //如果因為流位於檔案末尾而沒有可用的位元組,則返回值-1
            while ((len = is.read(bytes)) != -1) {
                //將指定byte陣列中從偏移量off開始的len個位元組寫入此緩衝的輸出流
                os.write(bytes, 0, len);
            }
            //java在使用流時,都會有一個緩衝區,按一種它認為比較高效的方法來發資料:把要發的資料先放到緩衝區,
            //緩衝區放滿以後再一次性發過去,而不是分開一次一次地發
            //flush()強制將緩衝區中的資料傳送出去,不必等到緩衝區滿
            os.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                //關閉輸入流和輸出流
                if (is != null)
                    is.close();
                if (os != null)
                    os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

array檔案

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="trainedData">
        <item>chi_sim</item>
        <item>eng</item>
    </string-array>
</resources>

 

識別庫用到兩個,一個是chi_sim 代表中文,一個是eng代表英文,資源中assets下沒有識別庫,需要自己新增chi_sim和eng

識別庫下載:識別庫

如果你手上有很多張圖片資源,你可以嘗試製作自己的識別庫,可以提高識別率,沒有圖片資源的,大家也可以簡單瞭解下

好了,一個簡易的文字識別就完成了,希望能對大家有所幫助。

相關推薦

Android opencv實現文字識別

Android 文字識別         因為公司下個專案要用到OCR(光學字元識別),我們組leader就讓我準備一下我的專案是主要參考的是tess_two Android圖片文字識別,選拍照或者從本地相簿選取照片,然後呼叫本地裁剪,最後開始識別,識別結果還可以,希望能對

android opencv實現人臉檢測 以及 年齡和性別識別

支援技術分享,轉載或複製,請指出文章來源此部落格作者為Jack__0023 1、背景 因為面對的場景不同,所以我上次使用 andr

Python調用OpenCV實現人臉識別

source display document down char name 實現 cvt config [硬件環境] Win10 64位 [軟件環境] Python版本:2.7.3 IDE:JetBrains PyCharm 2016.3.2 Python庫: 1.1)

用百度ocr+微信截圖實現文字識別

python 文字識別 百度api 作用:將圖片中的文字識別出來 一、調用微信截圖dll控件 將微信截圖插件復制到項目文件,使用ctypes加載(膠水語言就是給力) def capture(): try: dll = ctypes.cdll.LoadLibrary(‘PrS

Android 快速實現掃描識別二維碼(即掃碼登入功能)

IG牛逼l 零封G2!給RNG報仇了! 掃碼登入現在很流行,淘寶,京東,熊貓直播各大網站都有掃碼登入功能,其實呢對於客戶端來說掃碼登入很簡單,因為難點都集中在了前端身上,我簡單講下掃碼登入的流程: 1 客戶端掃碼然後開啟掃碼確認頁面(H5頁面,這個時候要把使用者的

Java呼叫百度API實現文字識別-羅紹崗-專題視訊課程

Java呼叫百度API實現文字識別—242人已學習 課程介紹         java呼叫百度AI文字識別SDK來實現一張圖片的文字資訊 課程收益     

使用opencv實現人臉識別及人眼識別

# 1 load 2 load jpg 3 hear gray 4 detect 5 draw import cv2 import numpy as np face_xml = cv2.CascadeClassifier('haarcascade_fronta

opencv實現人臉識別

import cv2 import matplotlib.pylab as plt import numpy as np def show(image): plt.imshow(image)

android:TextView實現文字走馬燈效果(欺騙系統獲取持久的焦點)

通常情況下我們想實現文字的走馬燈效果需要在xml檔案中這樣設定 <TextView android:layout_width="wrap_content"

opencv實現車牌識別之字元分割

簡介   在前一篇中,我們已經定位出來了在圖片中車牌號的位置,並且將車牌號圖片複製成了新圖片,並顯示出來,本章在這些被截取出來的圖片上繼續處理。 截取出來的新圖片如下: 影象灰階/二值化   首先也是選擇將影象進行灰階,然後採用以255一遍開始,取佔了總pixel

Android 輕鬆實現語音識別

<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ />

Java文字識別軟體-呼叫百度ocr實現文字識別

java_baidu_ocr Java呼叫百度OCR文字識別API實現圖片文字識別軟體 專案原始碼在文末,放到了GitHub上 - https://github.com/Ymy214/java_baidu_ocr 識別圖一 圖一識別結果 識別圖二 圖二識別結果 識別圖三

利用python、tensorflow、opencv實現人臉識別(包會)!

一,前言 本人是機械專業在讀碩士,在完成暑假實踐的時候接觸到了人臉識別,對這一實現很感興趣,所以花了大概十天時間做出了自己的人臉識別。這篇文章應該是很詳細的了所以幫你實現人臉識別應該沒什麼問題。 先說本博文的最終要達到的效果:通過一系列操作,在攝像頭的視訊流中識別特定

程式碼C++, opencv實現人臉識別,人臉檢測,人臉匹配,視訊中的人臉檢測,攝像頭下的人臉檢測等

前一段時間寫了一個人臉相關的演算法,包括視訊中的人臉檢測,相機的人臉檢測,影象中人臉檢測,還有人臉識別。使用的是VS2013和opencv。首先建立標頭檔案common.h#ifndef _COMMON_H #define _COMMON_H #include <op

OpenCV實現影象識別

最近參加了一個機器人比賽,本人負責影象識別和串列埠通訊方面的任務工作。串列埠通訊的教程可以見我的部落格;下面主要總結一下我對影象識別的整個學習過程。 開發環境 Mac OS Xcode C++ OpenCV 2.4.12 思考過程 實現影象識別

tp5引入百度ocr實現文字識別

一、登入百度AI開放平臺建立orc應用 選擇影象識別→建立應用 建立好的應用: 二、下載sdk包 我演示的是php的:https://ai.baidu.com/sdk#ocr 三、tp框架引入sdk包 我在最外層新建了一個Ocr下載好的sdk包放入到vendor第三

zxing和opencv實現身份識別

* **轉自: http://blog.csdn.net/sinat_28891771/article/details/71191777?locationNum=2&fps=1*** 實現原理分析 :通過zxing庫捕捉相機獲得影象,或者從相簿裡獲取圖片,再對影象進

Android開發實現人臉識別

之前看到有人在部落格寫用face++做人臉識別app,後來我也照著教程去試了一遍,發現根本行不通,原因在於他呼叫的庫是舊版本,face++已經全面更新了版本.後來我照著face++官網新版本的API文件打了一遍程式碼,發現識別的結果還算差強人意,但要識別多個人臉屬性,需要重複

ubuntu16.04配置opencv 實現人臉識別

GCC 4.4.x 或者更高版本 CMake 2.6 或者更高版本 Git GTK+2.x 或者更高, 包括標頭檔案 (libgtk2.0-dev) pkg-config Python Numpy ffmpeg or libav development

android端使用openCV與深度學習實現車牌識別

車牌識別的應用場景隨處可見:高速公路上超速抓拍、小區門口關卡、車庫入口關卡,甚至出現在車載裝置上。它的工作原理大致這樣:使用攝像頭充當“眼睛”,使用openCV與深度學習充當“大腦”。實時車牌識別工作