Android opencv实现文字识别

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_35820350/article/details/78802276

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'

 

第二种比较繁琐,想了解的朋友可以看下,不想了解的朋友直接跳过。

1.从官网下载tess-two:Android tess-two

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文字识别tesseract ocr -训练样本库 识别字库

好了,一个简易的文字识别就完成了,希望能对大家有所帮助。

 



 

展开阅读全文

没有更多推荐了,返回首页