Android开发如何轻松实现基于Tesseract的Android OCR应用程序
介绍 此应用程序使用Tesseract 3的Tesseract OCR引擎,该引擎通过识别字符模式( https://github.com/tesseract-ocr/tesseract )来工作。 Tesseract具有unicode(UTF-8)支持,可以开箱即用识别100多种语言。 背景 我尝试了Google文本识别API- https://deve
介绍
此应用程序使用Tesseract 3的Tesseract OCR引擎,该引擎通过识别字符模式(https://github.com/tesseract-ocr/tesseract)来工作。Tesseract具有unicode(UTF-8)支持,可以“开箱即用”识别100多种语言。
背景
我尝试了Google文本识别API- https://developers.google.com/vision/android/text-overview,但它不适合我,所以我找到了这个惊人的引擎。
使用代码
开始吧!在Android studio中创建一个新项目(我使用的是3.2.1版),或者您可以下载源文件并选择:File-New-Import项目。
添加到build.gradle
应用程序级别:
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' implementation 'com.rmtheis:tess-two:9.0.0'
我使用Butterknife
库,它非常有用,主库是 - ' tess-two:9.0.0
'' - 它包含一个Android的Tesseract工具(tesseract-android-tools)的分支,它增加了一些额外的功能。此外,我们需要相机和写入权限,因此将其添加到AndroidManifest.xml。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-feature android:name="android.hardware.camera" />
<uses-permission android:name="android.permission.CAMERA" />
做一个简单的布局文件Button
,TextView
并且ImageView
:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:fillViewport="true" tools:context=".MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <Button android:id="@+id/scan_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="scan" />
</LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="4dp" android:orientation="horizontal"> <TextView android:id="@+id/ocr_text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="fill" android:text=" text"> </TextView> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <ImageViewandroid:id="@+id/ocr_image"android:layout_width="match_parent"android:layout_height="wrap_content"/> </LinearLayout> </LinearLayout>
</ScrollView>
我们得到这样的东西:
编写一些代码来检查权限:
void checkPermissions() {
if (!hasPermissions(context, PERMISSIONS)) {
requestPermissions(PERMISSIONS,
PERMISSION_ALL);
flagPermissions = false;
}
flagPermissions = true;
} public static boolean hasPermissions(Context context, String... permissions) {
if (context != null && permissions != null) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(context, permission)
!= PackageManager.PERMISSION_GRANTED) {
return false;
}
}
}
return true;
}
和代码来创建一个文件:
public File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("MMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}
首先,我们需要写onClickScanButton
函数,它:
@OnClick(R.id.scan_button)
void onClickScanButton() {
// check permissions
if (!flagPermissions) {
checkPermissions();
return;
}
//prepare intent
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(context.getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
Toast.makeText(context, errorFileCreate, Toast.LENGTH_SHORT).show();
Log.i("File error", ex.toString());
}
// Continue only if the File was successfully created
if (photoFile != null) {
oldPhotoURI = photoURI1;
photoURI1 = Uri.fromFile(photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI1);
startActivityForResult(takePictureIntent, REQUEST_IMAGE1_CAPTURE);
}
}
}
我们可以在这里查看结果:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data); switch (requestCode) {
case REQUEST_IMAGE1_CAPTURE: {
if (resultCode == RESULT_OK) {
Bitmap bmp = null;
try {
InputStream is = context.getContentResolver().openInputStream(photoURI1);
BitmapFactory.Options options = new BitmapFactory.Options();
bmp = BitmapFactory.decodeStream(is, null, options); } catch (Exception ex) {
Log.i(getClass().getSimpleName(), ex.getMessage());
Toast.makeText(context, errorConvert, Toast.LENGTH_SHORT).show();
} firstImage.setImageBitmap(bmp);
doOCR(bmp); OutputStream os;
try {
os = new FileOutputStream(photoURI1.getPath());
if (bmp != null) {
bmp.compress(Bitmap.CompressFormat.JPEG, 100, os);
}
os.flush();
os.close();
} catch (Exception ex) {
Log.e(getClass().getSimpleName(), ex.getMessage());
Toast.makeText(context, errorFileCreate, Toast.LENGTH_SHORT).show();
} } else {
{
photoURI1 = oldPhotoURI;
firstImage.setImageURI(photoURI1);
}
}
}
}
}
接下来将Tesseract集成到我们的项目中,进行额外的课程:TesseractOCR
。
我在Assets文件夹中为英语语言添加了训练有素的数据文件“ eng.traineddata ” ,因此我们需要将其从APK复制到内部存储器文件目录,然后启动Tesseract系统:。mTess.init(dstInitPathDir, language)
public class TesseractOCR { private final TessBaseAPI mTess; public TesseractOCR(Context context, String language) {
mTess = new TessBaseAPI();
boolean fileExistFlag = false; AssetManager assetManager = context.getAssets(); String dstPathDir = "/tesseract/tessdata/"; String srcFile = "eng.traineddata";
InputStream inFile = null; dstPathDir = context.getFilesDir() + dstPathDir;
String dstInitPathDir = context.getFilesDir() + "/tesseract";
String dstPathFile = dstPathDir + srcFile;
FileOutputStream outFile = null; try {
inFile = assetManager.open(srcFile); File f = new File(dstPathDir); if (!f.exists()) {
if (!f.mkdirs()) {
Toast.makeText(context, srcFile + " can't be created.", Toast.LENGTH_SHORT).show();
}
outFile = new FileOutputStream(new File(dstPathFile));
} else {
fileExistFlag = true;
} } catch (Exception ex) {
Log.e(TAG, ex.getMessage()); } finally { if (fileExistFlag) {
try {
if (inFile != null) inFile.close();
mTess.init(dstInitPathDir, language);
return; } catch (Exception ex) {
Log.e(TAG, ex.getMessage());
}
} if (inFile != null && outFile != null) {
try {
//copy file
byte[] buf = new byte[1024];
int len;
while ((len = inFile.read(buf)) != -1) {
outFile.write(buf, 0, len);
}
inFile.close();
outFile.close();
mTess.init(dstInitPathDir, language);
} catch (Exception ex) {
Log.e(TAG, ex.getMessage());
}
} else {
Toast.makeText(context, srcFile + " can't be read.", Toast.LENGTH_SHORT).show();
}
}
} public String getOCRResult(Bitmap bitmap) {
mTess.setImage(bitmap);
return mTess.getUTF8Text();
} public void onDestroy() {
if (mTess != null) mTess.end();
}
}
OCR代码很简单 - 我们需要将图像(位图BMP)传递给该对象并获得结果:
public String getOCRResult(Bitmap bitmap) {
mTess.setImage(bitmap);
return mTess.getUTF8Text(); }
OCR可能需要很长时间,因此我们需要在另一个中进行Thread
:
private void doOCR(final Bitmap bitmap) {
if (mProgressDialog == null) {
mProgressDialog = ProgressDialog.show(this, "Processing",
"Doing OCR...", true);
} else {
mProgressDialog.show();
}
new Thread(new Runnable() {
public void run() {
final String srcText = mTessOCR.getOCRResult(bitmap);
runOnUiThread(new Runnable() {
@Override
public void run() { if (srcText != null && !srcText.equals("")) {
ocrText.setText(srcText);
}
mProgressDialog.dismiss();
}
});
}
}).start();
}
源图像如下:
OCR的结果如下:
Android开发如何轻松实现基于Tesseract的Android OCR应用程序的更多相关文章
- Android开发学习之路--基于vitamio的视频播放器(二)
终于把该忙的事情都忙得差不多了,接下来又可以开始good good study,day day up了.在Android开发学习之路–基于vitamio的视频播放器(一)中,主要讲了播放器的界面的 ...
- Android开发快速入门(环境配置、Android Studio安装)
Android是一种激动人心的开源移动平台,它像手机一样无处不在,得到了Google以及其他一些开放手机联盟成员(如三星.HTC.中国移动.Verizon和AT&T等)的支持,因而不能不加以学 ...
- 基于Tesseract组件的OCR识别
基于Tesseract组件的OCR识别 背景以及介绍 欲研究C#端如何进行图像的基本OCR识别,找到一款开源的OCR识别组件.该组件当前已经已经升级到了4.0版本.和传统的版本(3.x)比,4.0时代 ...
- Android开发学习之路-该怎么学Android(Service和Activity通信为例)
在大部分地方,比如书本或者学校和培训机构,教学Android的方式都基本类似,就是告诉先上原理方法,然后对着代码讲一下. 但是,这往往不是一个很好的方法,为什么? ① 学生要掌握这个方法的用途,只能通 ...
- Android开发学习总结——搭建最新版本的Android开发环境
原文出自:https://www.cnblogs.com/xdp-gacl/p/4322165.html#undefined 最近由于工作中要负责开发一款Android的App,之前都是做JavaWe ...
- android开发学习笔记系列(1)-android起航
前言 在学习安卓的过程中,我觉得非常有必要将自己所学的东西进行整理,因为每每当我知道我应该是如何去实现功能的时候,有许多细节问题我总是会遗漏,因此我也萌生了写一系列博客来描述自己学习的路线,让我的an ...
- Android开发——1轻松战胜开发环境
写在前头的话:鄙人乃2016年本科毕业的程序yuan一枚,大学阶段从未学过安卓,java也是一知半解,回想这一年半的开发生涯真的是相当悲壮.你要是问我喜欢开发吗,当然确定一定以及肯定地告诉你不喜欢啊! ...
- 【Android开发资料分享】自己整理的Android开发资料,非常全面
学习Android以来,不知不觉中收集了大量非常优秀的Android开发资料,一直没有系统的整理,最近抽时间把收藏夹中的资料做了一下整理,现在分享给大家,希望能够帮助到需要的人.这份资料我还会不断的更 ...
- 【Android开发】创建你的第一个Android项目
原文:http://android.eoe.cn/topic/summary 本文中你将了解到: 1. 使用Eclipse创建项目 2. 使用命令行创建项目 你还应该阅读: 1. 安装SDK(官网页面 ...
随机推荐
- C++ Opencv remap()重映射函数详解及使用示例
一.重映射及remap()函数介绍 重映射,就是把一幅图像中某位置的像素放置到另一图像指定位置的过程.即: 在重映射过程中,图像的大小也可以同时发生改变.此时像素与像素之间的关系就不是一一对应关系,因 ...
- [Swift]LeetCode273. 整数转换英文表示 | Integer to English Words
Convert a non-negative integer to its english words representation. Given input is guaranteed to be ...
- [Swift]LeetCode769. 最多能完成排序的块 | Max Chunks To Make Sorted
Given an array arr that is a permutation of [0, 1, ..., arr.length - 1], we split the array into som ...
- [Swift]LeetCode1032. 字符流 | Stream of Characters
Implement the StreamChecker class as follows: StreamChecker(words): Constructor, init the data struc ...
- vue实例属性的方法
1.$mount() 手动设置挂载点 eg:vm.$mount("#app") 2.$destroy() 销毁 eg:vm.$destroy(); 3.$forceUp ...
- 【Spark篇】---Spark中Master-HA和historyServer的搭建和应用
一.前述 本节讲述Spark Master的HA的搭建,为的是防止单点故障. Spark-UI 的使用介绍,可以更好的监控Spark应用程序的执行. 二.具体细节 1.Master HA 1.Mast ...
- CompletableFuture基本用法
异步计算 所谓异步调用其实就是实现一个可无需等待被调用函数的返回值而让操作继续运行的方法.在 Java 语言中,简单的讲就是另启一个线程来完成调用中的部分计算,使调用继续运行或返回,而不需要等待计算结 ...
- 从锅炉工到AI专家(6)
欠拟合和过拟合 几乎所有的复杂方程都存在结果跟预期差异的情况,越复杂的方程,这种情况就越严重.这里面通常都是算法造成的,当然也存在数据集的个体差异问题. 所以"欠拟合"和" ...
- JAVA内存分配与回收策略
对象的内存分配,大方向上讲,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配.少数情况下也可能会直接分配在老年代中,分配规则并不是百分百固定 ...
- C#线程安全使用(二)
刚才想了半天文章应该起什么名字,最后决定起名为<线程安全使用>,线程安全这个词很难理解,感觉就像托管这词一样,但是托管翻译成英文是managed,我通常把他翻译成被管理,这样就好理解多了, ...