Android opencv實現文字識別
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'
第二種比較繁瑣,想了解的朋友可以看下,不想了解的朋友直接跳過。
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 opencv實現文字識別
Android 文字識別 因為公司下個專案要用到OCR(光學字元識別),我們組leader就讓我準備一下我的專案是主要參考的是tess_two Android圖片文字識別,選拍照或者從本地相簿選取照片,然後呼叫本地裁剪,最後開始識別,識別結果還可以,希望能對
android opencv實現人臉檢測 以及 年齡和性別識別
支援技術分享,轉載或複製,請指出文章來源此部落格作者為Jack__0023 1、背景 因為面對的場景不同,所以我上次使用 andr
Python調用OpenCV實現人臉識別
source display document down char name 實現 cvt config [硬件環境] Win10 64位 [軟件環境] Python版本:2.7.3 IDE:JetBrains PyCharm 2016.3.2 Python庫: 1.1)
用百度ocr+微信截圖實現文字識別
python 文字識別 百度api 作用:將圖片中的文字識別出來 一、調用微信截圖dll控件 將微信截圖插件復制到項目文件,使用ctypes加載(膠水語言就是給力) def capture(): try: dll = ctypes.cdll.LoadLibrary(‘PrS
Android 快速實現掃描識別二維碼(即掃碼登入功能)
IG牛逼l 零封G2!給RNG報仇了! 掃碼登入現在很流行,淘寶,京東,熊貓直播各大網站都有掃碼登入功能,其實呢對於客戶端來說掃碼登入很簡單,因為難點都集中在了前端身上,我簡單講下掃碼登入的流程: 1 客戶端掃碼然後開啟掃碼確認頁面(H5頁面,這個時候要把使用者的
Java呼叫百度API實現文字識別-羅紹崗-專題視訊課程
Java呼叫百度API實現文字識別—242人已學習 課程介紹 java呼叫百度AI文字識別SDK來實現一張圖片的文字資訊 課程收益  
使用opencv實現人臉識別及人眼識別
# 1 load 2 load jpg 3 hear gray 4 detect 5 draw import cv2 import numpy as np face_xml = cv2.CascadeClassifier('haarcascade_fronta
opencv實現人臉識別
import cv2 import matplotlib.pylab as plt import numpy as np def show(image): plt.imshow(image)
android:TextView實現文字走馬燈效果(欺騙系統獲取持久的焦點)
通常情況下我們想實現文字的走馬燈效果需要在xml檔案中這樣設定 <TextView android:layout_width="wrap_content"
opencv實現車牌識別之字元分割
簡介 在前一篇中,我們已經定位出來了在圖片中車牌號的位置,並且將車牌號圖片複製成了新圖片,並顯示出來,本章在這些被截取出來的圖片上繼續處理。 截取出來的新圖片如下: 影象灰階/二值化 首先也是選擇將影象進行灰階,然後採用以255一遍開始,取佔了總pixel
Android 輕鬆實現語音識別
<!--<br/ /><br/ />Code highlighting produced by Actipro CodeHighlighter (freeware)<br/ />http://www.CodeHighlighter.com/<br/ />
Java文字識別軟體-呼叫百度ocr實現文字識別
java_baidu_ocr Java呼叫百度OCR文字識別API實現圖片文字識別軟體 專案原始碼在文末,放到了GitHub上 - https://github.com/Ymy214/java_baidu_ocr 識別圖一 圖一識別結果 識別圖二 圖二識別結果 識別圖三
利用python、tensorflow、opencv實現人臉識別(包會)!
一,前言 本人是機械專業在讀碩士,在完成暑假實踐的時候接觸到了人臉識別,對這一實現很感興趣,所以花了大概十天時間做出了自己的人臉識別。這篇文章應該是很詳細的了所以幫你實現人臉識別應該沒什麼問題。 先說本博文的最終要達到的效果:通過一系列操作,在攝像頭的視訊流中識別特定
程式碼C++, opencv實現人臉識別,人臉檢測,人臉匹配,視訊中的人臉檢測,攝像頭下的人臉檢測等
前一段時間寫了一個人臉相關的演算法,包括視訊中的人臉檢測,相機的人臉檢測,影象中人臉檢測,還有人臉識別。使用的是VS2013和opencv。首先建立標頭檔案common.h#ifndef _COMMON_H #define _COMMON_H #include <op
OpenCV實現影象識別
最近參加了一個機器人比賽,本人負責影象識別和串列埠通訊方面的任務工作。串列埠通訊的教程可以見我的部落格;下面主要總結一下我對影象識別的整個學習過程。 開發環境 Mac OS Xcode C++ OpenCV 2.4.12 思考過程 實現影象識別
tp5引入百度ocr實現文字識別
一、登入百度AI開放平臺建立orc應用 選擇影象識別→建立應用 建立好的應用: 二、下載sdk包 我演示的是php的:https://ai.baidu.com/sdk#ocr 三、tp框架引入sdk包 我在最外層新建了一個Ocr下載好的sdk包放入到vendor第三
zxing和opencv實現身份識別
* **轉自: http://blog.csdn.net/sinat_28891771/article/details/71191777?locationNum=2&fps=1*** 實現原理分析 :通過zxing庫捕捉相機獲得影象,或者從相簿裡獲取圖片,再對影象進
Android開發實現人臉識別
之前看到有人在部落格寫用face++做人臉識別app,後來我也照著教程去試了一遍,發現根本行不通,原因在於他呼叫的庫是舊版本,face++已經全面更新了版本.後來我照著face++官網新版本的API文件打了一遍程式碼,發現識別的結果還算差強人意,但要識別多個人臉屬性,需要重複
ubuntu16.04配置opencv 實現人臉識別
GCC 4.4.x 或者更高版本 CMake 2.6 或者更高版本 Git GTK+2.x 或者更高, 包括標頭檔案 (libgtk2.0-dev) pkg-config Python Numpy ffmpeg or libav development
android端使用openCV與深度學習實現車牌識別
車牌識別的應用場景隨處可見:高速公路上超速抓拍、小區門口關卡、車庫入口關卡,甚至出現在車載裝置上。它的工作原理大致這樣:使用攝像頭充當“眼睛”,使用openCV與深度學習充當“大腦”。實時車牌識別工作