Android上图片文字识别

来源:互联网 发布:网络作家。乱弹琴 编辑:程序博客网 时间:2024/04/30 19:31

最近做了一款Android应用需要输入大量的数据,为了提高体验我想了很多种输入数据的方式,最终采用了两种:二维码扫描和图片识别。前者顾名思义有个短板,就是需要生成二维码,下面就介绍下图片文字识别实现。

本应用是基于是OCR引擎,故需要第三方的jar包 tess-two.tesseract3.01-leptonica1.68-LibJPEG6b.jar 下载链接:点击打开链接

另外tessdata是语言包(我只下载了中文和英语包)下载链接:点击打开链接,需要放到手机SD卡根目录,我的应用中直接打包进apk中,免得需要拷贝的麻烦,但是造成的结果就是apk体积变得非常大,各位根据各自的情况做取舍,后面我会贴出打包进apk的方法。

首先介绍下布局文件,本应用为一个简单地实现,界面上就没有多做处理,主界面如下图:

如上图,可以选择是否二值化处理图片再识别文字,然后选泽需要识别的文字种类,紧接着可以选择拍摄或者相片选取,识别后文字显示在编辑框内,可修改识别错误后,点击复制到安卓粘贴板,具体的代码如下,就不多说了:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    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"    tools:context=".TesMainActivity"    android:background="@drawable/beijing6" ><LinearLayout        android:id="@+id/bottombar1"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:layout_alignParentBottom="true" >        <Button            android:id="@+id/btn_capy"            android:layout_weight="4"             android:layout_width="fill_parent"            android:layout_height="fill_parent"            android:text="复制" />        <Button            android:id="@+id/btn_select"            android:layout_weight="4"             android:layout_width="fill_parent"            android:layout_height="fill_parent"            android:text="相册选取" />        <Button            android:id="@+id/btn_camera"            android:layout_weight="4"             android:layout_width="fill_parent"            android:layout_height="fill_parent"            android:text="拍照" /> </LinearLayout>            <RelativeLayout        android:id="@+id/bottombar"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:layout_above="@id/bottombar1"         >        <RadioGroup            android:id="@+id/radiogroup"            android:layout_width="wrap_content"            android:layout_height="50dp"            android:layout_alignParentLeft="true"            android:orientation="horizontal" >            <RadioButton                android:id="@+id/rb_en"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center_vertical"                android:checked="true"                android:text="英" />            <RadioButton                android:id="@+id/rb_ch"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:layout_gravity="center_vertical"                android:text="中" />        </RadioGroup>          </RelativeLayout>        <ScrollView        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:layout_above="@id/bottombar"        android:layout_alignParentTop="true" >        <LinearLayout            android:layout_width="fill_parent"            android:layout_height="wrap_content"            android:orientation="vertical" >            <CheckBox                android:id="@+id/ch_pretreat"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:text="二值化处理" />            <TextView                android:id="@+id/tv_result1"                android:layout_width="fill_parent"                android:layout_height="wrap_content"                android:text="result" />            <LinearLayout      android:focusable="true"      android:focusableInTouchMode="true"      android:layout_width="0px"      android:layout_height="0px"/>            <EditText                android:id="@+id/tv_result"                android:layout_width="fill_parent"                android:layout_height="wrap_content"                 />            <TextView                android:layout_width="fill_parent"                android:layout_height="wrap_content"                android:text="选取的图片:" />            <ImageView                android:id="@+id/iv_selected"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:adjustViewBounds="true"                android:maxHeight="300dp" />            <TextView                android:layout_width="fill_parent"                android:layout_height="wrap_content"                android:text="预处理后的图片:" />            <ImageView                android:id="@+id/iv_treated"                android:layout_width="wrap_content"                android:layout_height="wrap_content"                android:adjustViewBounds="true"                android:maxHeight="300dp" />        </LinearLayout>    </ScrollView></RelativeLayout>

接着说明下Activity,在界面初始化是会对语言包文件夹进行判断,如果没有该文件进行复制,另外还会初始化各种控价,代码如下:

@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_tesmain);// 若文件夹不存在 首先创建文件夹File path = new File(IMG_PATH);if (!path.exists()) {path.mkdirs();}tvResult = (EditText) findViewById(R.id.tv_result);tvResult1 = (TextView) findViewById(R.id.tv_result1);ivSelected = (ImageView) findViewById(R.id.iv_selected);ivTreated = (ImageView) findViewById(R.id.iv_treated);btnCamera = (Button) findViewById(R.id.btn_camera);btnSelect = (Button) findViewById(R.id.btn_select);btnCapy = (Button) findViewById(R.id.btn_capy);chPreTreat = (CheckBox) findViewById(R.id.ch_pretreat);radioGroup = (RadioGroup) findViewById(R.id.radiogroup);btnCamera.setOnClickListener(new cameraButtonListener());btnSelect.setOnClickListener(new selectButtonListener());btnCapy.setOnClickListener(new capyButtonListener());    if(!isDirExist("tessdata")){    Toast.makeText(getApplicationContext(), "SD卡缺少语言包,复制中。。。",Toast.LENGTH_LONG).show();    new SaveFile_Thread().start();    }// 用于设置解析语言radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(RadioGroup group, int checkedId) {switch (checkedId) {case R.id.rb_en:LANGUAGE = "eng";break;case R.id.rb_ch:LANGUAGE = "chi_sim";break;}}});}

其中文件复制线程的代码如下:

 public boolean SaveFileToSDCard(){        SDUtils sdutils_Chinese = new SDUtils("tessdata","chi_sim.traineddata",this,R.raw.chi_sim);    SDUtils sdutils_English = new SDUtils("tessdata","eng.traineddata",this,R.raw.eng);    try {    sdutils_Chinese.getSQLiteDatabase();    sdutils_English.getSQLiteDatabase();} catch (IOException e) {return false;}    return true;    }    public class SaveFile_Thread extends Thread {    public SaveFile_Thread(){  }    public void run(){  synchronized (this) {  boolean iret;  do {  iret = SaveFileToSDCard();  } while (false);  if(iret){  ShowMsg(1);  }else  ShowMsg(2);  }  }  }      public void ShowMsg(int what) {  mLoadKeyHandler.sendEmptyMessage(what);  }    public Handler mLoadKeyHandler = new Handler() {  @Override  public void handleMessage(Message msg) {  if(msg.what==1){  Toast.makeText(getApplicationContext(), "复制成功",Toast.LENGTH_LONG).show();  }  else if(msg.what==2)  Toast.makeText(getApplicationContext(), "复制失败",Toast.LENGTH_LONG).show();  }  };

对SD卡进行文件操作我编辑了一个SDUtils 类,具体如下:

package com.mikewong.tool.tesseract;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.os.Environment;import android.util.Log;/*** 工具类 , 用于将RAW 目录下的文件写入到数据库中* * @author Administrator* */public class SDUtils {        private String file; // 设置文件存放路径        private String fileName; // 存放文件名称        private Context context; // 获取到Context 上下文        private int rawid; // 资源文件ID ,需要COPY 的文件        private String DATABASE_PATH = "";        private String DATABASE_NAME = "";        public String getFile() {                return file;        }        public void setFile(String file) {                this.file = file;                this.DATABASE_PATH = Environment.getExternalStorageDirectory()                                .getAbsolutePath() + "/" + file;        }        public String getFileName() {                return fileName;        }        public void setFileName(String fileName) {                this.fileName = fileName;                this.DATABASE_NAME = fileName;        }        public int getRawid() {                return rawid;        }        public void setRawid(int rawid) {                this.rawid = rawid;        }        public SDUtils() {        }        /**         *          * @param file         *            文件夹例如: aa/bb         * @param fileName         *            文件名         * @param context         *            上下文         * @param rawid         *            资源ID         */        public SDUtils(String file, String fileName, Context context, int rawid) {                super();                this.file = file;                this.fileName = fileName;                this.context = context;                this.rawid = rawid;                this.DATABASE_PATH = Environment.getExternalStorageDirectory()                                .getAbsolutePath() + "/" + file;                this.DATABASE_NAME = fileName;        }        /**         * 将文件复制到SD卡         *          * @return         * @throws IOException         */        public boolean getSQLiteDatabase() throws IOException {                // 首先判断该目录下的文件夹是否存在                File dir = new File(DATABASE_PATH);                String filename1 = DATABASE_PATH + "/" + DATABASE_NAME;                if (!dir.exists()) {                        // 文件夹不存在 , 则创建文件夹                        dir.mkdirs();                }                // 判断目标文件是否存在                File file1 = new File(dir, DATABASE_NAME);                if (!file1.exists()) {                        Log.i("msg", "没有文件,开始创建");                        file1.createNewFile(); // 创建文件                }                Log.i("msg", "准备开始进行文件的复制");                // 开始进行文件的复制                InputStream input = context.getResources().openRawResource(rawid); // 获取资源文件raw                                                                                                                                                        // 标号                try {                        FileOutputStream out = new FileOutputStream(file1); // 文件输出流、用于将文件写到SD卡中                                                                                                                                // -- 从内存出去                        byte[] buffer = new byte[1024];                        int len = 0;                        while ((len = (input.read(buffer))) != -1) { // 读取文件,-- 进到内存                                out.write(buffer, 0, len); // 写入数据 ,-- 从内存出                        }                        input.close();                        out.close(); // 关闭流                        return true;                } catch (Exception e) {                Log.i("msg", "复制异常");                return false;                }                        }}


三个按钮所对应的操作代码:

// 拍照识别class cameraButtonListener implements OnClickListener {@Overridepublic void onClick(View arg0) {Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(new File(IMG_PATH, "temp.jpg")));startActivityForResult(intent, PHOTO_CAPTURE);}};// 复制数据到剪切板class capyButtonListener implements OnClickListener {@Overridepublic void onClick(View arg0) {        ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);        // 将文本内容放到系统剪贴板里。        if(tvResult.length() == 0){        Toast.makeText(getApplicationContext(), "无数据", Toast.LENGTH_SHORT).show();        return;        }        cm.setText(tvResult.getText());        Toast.makeText(getApplicationContext(), "复制成功", Toast.LENGTH_SHORT).show();}};// 从相册选取照片并裁剪class selectButtonListener implements OnClickListener {@Overridepublic void onClick(View v) { // 激活系统图库,选择一张图片        Intent intent = new Intent(Intent.ACTION_PICK);        intent.setType("image/*");        // 开启一个带有返回值的Activity,请求码为PHOTO_REQUEST_GALLERY        boolean dele= delete(new File(IMG_PATH));        startActivityForResult(intent, PHOTO_REQUEST_GALLERY);}}

处理的回调函数

@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if (resultCode == Activity.RESULT_CANCELED)return;if (requestCode == PHOTO_CAPTURE) {tvResult1.setText("abc");startPhotoCrop(Uri.fromFile(new File(IMG_PATH, "temp.jpg")));}if (requestCode == PHOTO_REQUEST_GALLERY) {startPhotoCrop(data.getData());}// 处理结果if (requestCode == PHOTO_RESULT) {bitmapSelected = decodeUriAsBitmap(Uri.fromFile(new File(IMG_PATH,"temp_cropped.jpg")));if (chPreTreat.isChecked())tvResult1.setText("预处理中......");elsetvResult1.setText("识别中......");// 显示选择的图片showPicture(ivSelected, bitmapSelected);// 新线程来处理识别new Thread(new Runnable() {@Overridepublic void run() {if (chPreTreat.isChecked()) {bitmapTreated = ImgPretreatment.doPretreatment(bitmapSelected);Message msg = new Message();msg.what = SHOWTREATEDIMG;myHandler.sendMessage(msg);textResult = doOcr(bitmapTreated, LANGUAGE);} else {bitmapTreated = ImgPretreatment.converyToGrayImg(bitmapSelected);Message msg = new Message();msg.what = SHOWTREATEDIMG;myHandler.sendMessage(msg);textResult = doOcr(bitmapTreated, LANGUAGE);}Message msg2 = new Message();msg2.what = SHOWRESULT;myHandler.sendMessage(msg2);}}).start();}super.onActivityResult(requestCode, resultCode, data);}

负责剪切图片的函数

/** * 调用系统图片编辑进行裁剪 */public void startPhotoCrop(Uri uri) {Intent intent = new Intent("com.android.camera.action.CROP");intent.setDataAndType(uri, "image/*");intent.putExtra("crop", "true");intent.putExtra("scale", true);intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(new File(IMG_PATH, "temp_cropped.jpg")));intent.putExtra("return-data", false);intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());intent.putExtra("noFaceDetection", true); // no face detectionstartActivityForResult(intent, PHOTO_RESULT);}


主要的功能实现函数如上,代码源码贴上:点击打开链接(辛苦手打收两个积分大笑,如果积分不够可在下面留下邮箱,我看到后第一时间发送源码)


因上传源码有大小限制,故吧源码中的语言库删掉了,下载后只需把文章开始的tessdata语言包下的两个文件拷贝进res/raw下即可,如上图。

纯手打,有帮助就回复下,有什么问题也可留言,我们共同讨论下。

转载请注明出处!谢谢!

4 0
原创粉丝点击