一、引言
????????什么是OCR?OCR(Optical Character Recognition,光學(xué)字符識(shí)別)是指電子設(shè)備(例如掃描儀或數(shù)碼相機(jī))檢查紙上打印的字符,通過檢測(cè)暗、亮的模式確定其形狀,然后用字符識(shí)別方法將形狀翻譯成計(jì)算機(jī)文字的過程。簡(jiǎn)單地說,OCR是一種技術(shù),該項(xiàng)技術(shù)采用光學(xué)的方式將紙質(zhì)文檔中的文字轉(zhuǎn)換為黑白點(diǎn)陣圖像,然后通過識(shí)別軟件將圖像中的文字轉(zhuǎn)換成文本格式,供文字處理軟件進(jìn)一步編輯加工。
????????什么是Tesseract?Tesseract was originally developed at Hewlett-Packard Laboratories Bristol UK and at Hewlett-Packard Co, Greeley Colorado USA between 1985 and 1994, with some more changes made in 1996 to port to Windows, and some C++izing in 1998. In 2005 Tesseract was open sourced by HP. From 2006 until November 2018 it was developed by Google.Tesseract最初是在英國(guó)布里斯托爾的惠普實(shí)驗(yàn)室和美國(guó)科羅拉多州格里利的惠普公司于1985年至1994年間開發(fā)的,1996年做了一些更改以移植到Windows,并在1998年進(jìn)行了一些c++化。2005年,Tesseract被惠普開源。從2006年到2018年11月,它由谷歌開發(fā)。簡(jiǎn)單地說,Tesseract 就是上面OCR所說的“識(shí)別軟件”的具體實(shí)現(xiàn)。
? ? ? ? OCR的識(shí)別對(duì)象(輸入)是一張圖片,而識(shí)別結(jié)果(輸出)是計(jì)算機(jī)文字。在Android手機(jī)端主要存在兩種圖片的獲取方式,一種是從相冊(cè)中選擇一個(gè),另一個(gè)是直接拍照獲得。因此,本文將實(shí)現(xiàn)最簡(jiǎn)單的OCR思路:首先從手機(jī)中獲得一張圖片,然后將其輸入到Tesseract庫(kù),最后通過該庫(kù)輸出識(shí)別結(jié)果。由于只是學(xué)習(xí)該庫(kù)的使用方式,所以博主忽略了其它輔助性的功能,比如拍照識(shí)別。
二、Android通過Tesseract實(shí)現(xiàn)OCR
1、在Module的build.gradle文件中添加以下依賴
implementation 'com.rmtheis:tess-two:9.1.0'
2、在AndroidManifest.xml文件中添加以下權(quán)限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
3、在MainActivity中申請(qǐng)權(quán)限
? ? ? ? 建議在onCreate方法中執(zhí)行下面的checkPermission方法
// 檢查應(yīng)用所需的權(quán)限,如不滿足則發(fā)出權(quán)限請(qǐng)求
private void checkPermission() {
if (ContextCompat.checkSelfPermission(getApplicationContext(),
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 120);
}
if (ContextCompat.checkSelfPermission(getApplicationContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 121);
}
}
4、從assets中讀取一張讀片
????????既用于顯示,又用于識(shí)別
// 從assets中讀取一張Bitmap類型的圖片
private Bitmap getBitmapFromAssets(Context context, String filename) {
Bitmap bitmap = null;
AssetManager assetManager = context.getAssets();
try {
InputStream is = assetManager.open(filename);
bitmap = BitmapFactory.decodeStream(is);
is.close();
Log.i(TAG, "圖片讀取成功。");
Toast.makeText(getApplicationContext(), "圖片讀取成功。", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
Log.i(TAG, "圖片讀取失敗。");
Toast.makeText(getApplicationContext(), "圖片讀取失敗。", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
return bitmap;
}
5、做好OCR的準(zhǔn)備工作
????????去https://github.com/tesseract-ocr/下載.traineddata語(yǔ)言包,然后放在項(xiàng)目assets目錄下,并通過以下代碼復(fù)制到Android文件系統(tǒng)中
// 為Tesserect復(fù)制(從assets中復(fù)制過去)所需的數(shù)據(jù)
private void prepareTess() {
try{
// 先創(chuàng)建必須的目錄
File dir = getExternalFilesDir(TESS_DATA);
if(!dir.exists()){
if (!dir.mkdir()) {
Toast.makeText(getApplicationContext(), "目錄" + dir.getPath() + "沒有創(chuàng)建成功", Toast.LENGTH_SHORT).show();
}
}
// 從assets中復(fù)制必須的數(shù)據(jù)
String pathToDataFile = dir + "/" + DATA_FILENAME;
if (!(new File(pathToDataFile)).exists()) {
InputStream in = getAssets().open(DATA_FILENAME);
OutputStream out = new FileOutputStream(pathToDataFile);
byte[] buff = new byte[1024];
int len;
while ((len = in.read(buff)) > 0) {
out.write(buff, 0, len);
}
in.close();
out.close();
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
6、點(diǎn)擊按鈕后調(diào)用以下方法,執(zhí)行OCR識(shí)別
// OCR識(shí)別的主程序
private void mainProgram() {
// 從assets中獲取一張Bitmap圖片
Bitmap bitmap = getBitmapFromAssets(MainActivity.this, TARGET_FILENAME);
// 同時(shí)顯示在界面
main_iv_image.setImageBitmap(bitmap);
if (bitmap != null) {
// 準(zhǔn)備工作:創(chuàng)建路徑和Tesserect的數(shù)據(jù)
prepareTess();
// 初始化Tesserect
TessBaseAPI tessBaseAPI = new TessBaseAPI();
String dataPath = getExternalFilesDir("/").getPath() + "/";
tessBaseAPI.init(dataPath, "eng");
// 識(shí)別并顯示結(jié)果
String result = getOCRResult(tessBaseAPI, bitmap);
main_tv_result.setText(result);
}
}
// 進(jìn)行OCR并返回識(shí)別結(jié)果
private String getOCRResult(TessBaseAPI tessBaseAPI, Bitmap bitmap) {
tessBaseAPI.setImage(bitmap);
String result = "-";
try{
result = tessBaseAPI.getUTF8Text();
}catch (Exception e){
Log.e(TAG, e.getMessage());
}
tessBaseAPI.end();
return result;
}
7、編譯運(yùn)行,效果如圖
? ? ? ? 個(gè)人感覺識(shí)別率不是很準(zhǔn),一般般。
8、源代碼貼一下
? ? ? ? MainActivity.java
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.googlecode.tesseract.android.TessBaseAPI;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class MainActivity extends AppCompatActivity {
public static final String TESS_DATA = "/tessdata";
private static final String TARGET_FILENAME = "vin_demo.png";
private static final String DATA_FILENAME = "eng.traineddata";
private static final String TAG = MainActivity.class.getSimpleName();
private Button main_bt_recognize;
private TextView main_tv_result;
private ImageView main_iv_image;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 設(shè)置布局文件
setContentView(R.layout.activity_main);
// 檢查并請(qǐng)求應(yīng)用所需權(quán)限
checkPermission();
// 獲取控件對(duì)象
initView();
// 設(shè)置控件的監(jiān)聽器
setListener();
}
private void setListener() {
// 設(shè)置識(shí)別按鈕的監(jiān)聽器
main_bt_recognize.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 識(shí)別之前需要再次檢查一遍權(quán)限
checkPermission();
// 點(diǎn)擊后的主程序
mainProgram();
}
});
}
// 獲得界面需要交互的控件
private void initView() {
main_bt_recognize = findViewById(R.id.main_bt_recognize);
main_tv_result = findViewById(R.id.main_tv_result);
main_iv_image = findViewById(R.id.main_iv_image);
}
// 檢查應(yīng)用所需的權(quán)限,如不滿足則發(fā)出權(quán)限請(qǐng)求
private void checkPermission() {
if (ContextCompat.checkSelfPermission(getApplicationContext(),
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 120);
}
if (ContextCompat.checkSelfPermission(getApplicationContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 121);
}
}
// OCR識(shí)別的主程序
private void mainProgram() {
// 從assets中獲取一張Bitmap圖片
Bitmap bitmap = getBitmapFromAssets(MainActivity.this, TARGET_FILENAME);
// 同時(shí)顯示在界面
main_iv_image.setImageBitmap(bitmap);
if (bitmap != null) {
// 準(zhǔn)備工作:創(chuàng)建路徑和Tesserect的數(shù)據(jù)
prepareTess();
// 初始化Tesserect
TessBaseAPI tessBaseAPI = new TessBaseAPI();
String dataPath = getExternalFilesDir("/").getPath() + "/";
tessBaseAPI.init(dataPath, "eng");
// 識(shí)別并顯示結(jié)果
String result = getOCRResult(tessBaseAPI, bitmap);
main_tv_result.setText(result);
}
}
// 從assets中讀取一張Bitmap類型的圖片
private Bitmap getBitmapFromAssets(Context context, String filename) {
Bitmap bitmap = null;
AssetManager assetManager = context.getAssets();
try {
InputStream is = assetManager.open(filename);
bitmap = BitmapFactory.decodeStream(is);
is.close();
Log.i(TAG, "圖片讀取成功。");
Toast.makeText(getApplicationContext(), "圖片讀取成功。", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
Log.i(TAG, "圖片讀取失敗。");
Toast.makeText(getApplicationContext(), "圖片讀取失敗。", Toast.LENGTH_SHORT).show();
e.printStackTrace();
}
return bitmap;
}
// 為Tesserect復(fù)制(從assets中復(fù)制過去)所需的數(shù)據(jù)
private void prepareTess() {
try{
// 先創(chuàng)建必須的目錄
File dir = getExternalFilesDir(TESS_DATA);
if(!dir.exists()){
if (!dir.mkdir()) {
Toast.makeText(getApplicationContext(), "目錄" + dir.getPath() + "沒有創(chuàng)建成功", Toast.LENGTH_SHORT).show();
}
}
// 從assets中復(fù)制必須的數(shù)據(jù)
String pathToDataFile = dir + "/" + DATA_FILENAME;
if (!(new File(pathToDataFile)).exists()) {
InputStream in = getAssets().open(DATA_FILENAME);
OutputStream out = new FileOutputStream(pathToDataFile);
byte[] buff = new byte[1024];
int len;
while ((len = in.read(buff)) > 0) {
out.write(buff, 0, len);
}
in.close();
out.close();
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
// 進(jìn)行OCR并返回識(shí)別結(jié)果
private String getOCRResult(TessBaseAPI tessBaseAPI, Bitmap bitmap) {
tessBaseAPI.setImage(bitmap);
String result = "-";
try{
result = tessBaseAPI.getUTF8Text();
}catch (Exception e){
Log.e(TAG, e.getMessage());
}
tessBaseAPI.end();
return result;
}
}
? ? ? ? layout_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<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/main_iv_image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"/>
<Button
android:id="@+id/main_bt_recognize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_gravity="center_horizontal"
android:text="讀取一張圖片并識(shí)別" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_gravity="center_horizontal"
android:text="識(shí)別結(jié)果:" />
<TextView
android:id="@+id/main_tv_result"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_gravity="center_horizontal" />
</LinearLayout>
</ScrollView>
</LinearLayout>
? ? ? ? AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.cs.ocrdemo4csdn">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.OCRDemo4CSDN">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
? ? ? ? build.gradle(Module)
plugins {
id 'com.android.application'
}
android {
compileSdk 34
defaultConfig {
applicationId "com.cs.ocrdemo4csdn"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation 'com.rmtheis:tess-two:9.1.0'
}
? ? ? ? vin_demo.png
隨便從網(wǎng)上下的圖片(保證存在光學(xué)字符即可)。
????????eng.traineddata
從https://github.com/tesseract-ocr/下載的,如果不行從https://github.com/raykibul/Android-OCR-Testing/tree/main下載。
三、參考資料
? ? ? ? 1、光學(xué)字符識(shí)別
? ? ? ? 2、GitHub - tesseract-ocr/tesseract
? ? ? ? 3、GitHub - raykibul/Android-OCR-Testing
四、總結(jié)語(yǔ)
? ? ? ? 1、跟著別人的CSDN博客搗鼓了一天多,但是沒能調(diào)通,一直在報(bào)錯(cuò),比如遇到“Could not initialize Tesseract API with language=eng”、“getUTF8Text導(dǎo)致android tesseract崩潰”等等問題。后邊看了GitHub上邊比較新的代碼(諸位如果代碼參考了我的博客還是沒能調(diào)通,建議看看這份代碼),然后就跑通了。目前還不知道原因是啥。文章來源:http://www.zghlxwxcb.cn/news/detail-603305.html
? ? ? ? 2、我看Tesseract這個(gè)庫(kù)的識(shí)別結(jié)果并不是十分準(zhǔn)確,尤其是對(duì)于拍照出來的結(jié)果識(shí)別率很低,再?gòu)倪@個(gè)庫(kù)的發(fā)展歷史來看,好像現(xiàn)在都沒有什么人再維護(hù)它了(最后的維護(hù)者是谷歌,而且停留在2018年),所以,對(duì)于現(xiàn)在(今年是2023年)而言,我感覺它已經(jīng)有點(diǎn)屬于是過時(shí)技術(shù)。大家可以尋求一些比較新的技術(shù)方案,畢竟現(xiàn)在大模型都搞得這么牛了,OCR這種應(yīng)該搞得更好才是。文章來源地址http://www.zghlxwxcb.cn/news/detail-603305.html
到了這里,關(guān)于Android開發(fā):通過Tesseract第三方庫(kù)實(shí)現(xiàn)OCR的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!