前言:之前在外包网站看到身份证识别和车牌号的识别的需求,立马就想到了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文件夹中的原因。