& scale);
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..397e4e39fe35a541fd534634ac509b94dd4b2b86
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/AppCompatPreferenceActivity.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A {@link PreferenceActivity} which implements and proxies the necessary calls
+ * to be used with AppCompat.
+ *
+ * This technique can be used with an {@link android.app.Activity} class, not just
+ * {@link PreferenceActivity}.
+ */
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+ private AppCompatDelegate mDelegate;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ getDelegate().installViewFactory();
+ getDelegate().onCreate(savedInstanceState);
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ getDelegate().onPostCreate(savedInstanceState);
+ }
+
+ public ActionBar getSupportActionBar() {
+ return getDelegate().getSupportActionBar();
+ }
+
+ public void setSupportActionBar(@Nullable Toolbar toolbar) {
+ getDelegate().setSupportActionBar(toolbar);
+ }
+
+ @Override
+ public MenuInflater getMenuInflater() {
+ return getDelegate().getMenuInflater();
+ }
+
+ @Override
+ public void setContentView(@LayoutRes int layoutResID) {
+ getDelegate().setContentView(layoutResID);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ getDelegate().setContentView(view);
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().setContentView(view, params);
+ }
+
+ @Override
+ public void addContentView(View view, ViewGroup.LayoutParams params) {
+ getDelegate().addContentView(view, params);
+ }
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ getDelegate().onPostResume();
+ }
+
+ @Override
+ protected void onTitleChanged(CharSequence title, int color) {
+ super.onTitleChanged(title, color);
+ getDelegate().setTitle(title);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ getDelegate().onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ getDelegate().onStop();
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ getDelegate().onDestroy();
+ }
+
+ public void invalidateOptionsMenu() {
+ getDelegate().invalidateOptionsMenu();
+ }
+
+ private AppCompatDelegate getDelegate() {
+ if (mDelegate == null) {
+ mDelegate = AppCompatDelegate.create(this, null);
+ }
+ return mDelegate;
+ }
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..b72d72df47a3c6d769559230185c50823276fe85
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/MainActivity.java
@@ -0,0 +1,414 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.Manifest;
+import android.app.ProgressDialog;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.text.method.ScrollingMovementMethod;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class MainActivity extends AppCompatActivity {
+ private static final String TAG = MainActivity.class.getSimpleName();
+ public static final int OPEN_GALLERY_REQUEST_CODE = 0;
+ public static final int TAKE_PHOTO_REQUEST_CODE = 1;
+
+ public static final int REQUEST_LOAD_MODEL = 0;
+ public static final int REQUEST_RUN_MODEL = 1;
+ public static final int RESPONSE_LOAD_MODEL_SUCCESSED = 0;
+ public static final int RESPONSE_LOAD_MODEL_FAILED = 1;
+ public static final int RESPONSE_RUN_MODEL_SUCCESSED = 2;
+ public static final int RESPONSE_RUN_MODEL_FAILED = 3;
+
+ protected ProgressDialog pbLoadModel = null;
+ protected ProgressDialog pbRunModel = null;
+
+ protected Handler receiver = null; // Receive messages from worker thread
+ protected Handler sender = null; // Send command to worker thread
+ protected HandlerThread worker = null; // Worker thread to load&run model
+
+ // UI components of object detection
+ protected TextView tvInputSetting;
+ protected ImageView ivInputImage;
+ protected TextView tvOutputResult;
+ protected TextView tvInferenceTime;
+
+ // Model settings of object detection
+ protected String modelPath = "";
+ protected String labelPath = "";
+ protected String imagePath = "";
+ protected int cpuThreadNum = 1;
+ protected String cpuPowerMode = "";
+ protected String inputColorFormat = "";
+ protected long[] inputShape = new long[]{};
+ protected float[] inputMean = new float[]{};
+ protected float[] inputStd = new float[]{};
+ protected float scoreThreshold = 0.1f;
+
+ protected Predictor predictor = new Predictor();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ // Clear all setting items to avoid app crashing due to the incorrect settings
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.clear();
+ editor.commit();
+
+ // Prepare the worker thread for mode loading and inference
+ receiver = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case RESPONSE_LOAD_MODEL_SUCCESSED:
+ pbLoadModel.dismiss();
+ onLoadModelSuccessed();
+ break;
+ case RESPONSE_LOAD_MODEL_FAILED:
+ pbLoadModel.dismiss();
+ Toast.makeText(MainActivity.this, "Load model failed!", Toast.LENGTH_SHORT).show();
+ onLoadModelFailed();
+ break;
+ case RESPONSE_RUN_MODEL_SUCCESSED:
+ pbRunModel.dismiss();
+ onRunModelSuccessed();
+ break;
+ case RESPONSE_RUN_MODEL_FAILED:
+ pbRunModel.dismiss();
+ Toast.makeText(MainActivity.this, "Run model failed!", Toast.LENGTH_SHORT).show();
+ onRunModelFailed();
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ worker = new HandlerThread("Predictor Worker");
+ worker.start();
+ sender = new Handler(worker.getLooper()) {
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case REQUEST_LOAD_MODEL:
+ // Load model and reload test image
+ if (onLoadModel()) {
+ receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_SUCCESSED);
+ } else {
+ receiver.sendEmptyMessage(RESPONSE_LOAD_MODEL_FAILED);
+ }
+ break;
+ case REQUEST_RUN_MODEL:
+ // Run model if model is loaded
+ if (onRunModel()) {
+ receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_SUCCESSED);
+ } else {
+ receiver.sendEmptyMessage(RESPONSE_RUN_MODEL_FAILED);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ // Setup the UI components
+ tvInputSetting = findViewById(R.id.tv_input_setting);
+ ivInputImage = findViewById(R.id.iv_input_image);
+ tvInferenceTime = findViewById(R.id.tv_inference_time);
+ tvOutputResult = findViewById(R.id.tv_output_result);
+ tvInputSetting.setMovementMethod(ScrollingMovementMethod.getInstance());
+ tvOutputResult.setMovementMethod(ScrollingMovementMethod.getInstance());
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
+ boolean settingsChanged = false;
+ String model_path = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY),
+ getString(R.string.MODEL_PATH_DEFAULT));
+ String label_path = sharedPreferences.getString(getString(R.string.LABEL_PATH_KEY),
+ getString(R.string.LABEL_PATH_DEFAULT));
+ String image_path = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY),
+ getString(R.string.IMAGE_PATH_DEFAULT));
+ settingsChanged |= !model_path.equalsIgnoreCase(modelPath);
+ settingsChanged |= !label_path.equalsIgnoreCase(labelPath);
+ settingsChanged |= !image_path.equalsIgnoreCase(imagePath);
+ int cpu_thread_num = Integer.parseInt(sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY),
+ getString(R.string.CPU_THREAD_NUM_DEFAULT)));
+ settingsChanged |= cpu_thread_num != cpuThreadNum;
+ String cpu_power_mode =
+ sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY),
+ getString(R.string.CPU_POWER_MODE_DEFAULT));
+ settingsChanged |= !cpu_power_mode.equalsIgnoreCase(cpuPowerMode);
+ String input_color_format =
+ sharedPreferences.getString(getString(R.string.INPUT_COLOR_FORMAT_KEY),
+ getString(R.string.INPUT_COLOR_FORMAT_DEFAULT));
+ settingsChanged |= !input_color_format.equalsIgnoreCase(inputColorFormat);
+ long[] input_shape =
+ Utils.parseLongsFromString(sharedPreferences.getString(getString(R.string.INPUT_SHAPE_KEY),
+ getString(R.string.INPUT_SHAPE_DEFAULT)), ",");
+ float[] input_mean =
+ Utils.parseFloatsFromString(sharedPreferences.getString(getString(R.string.INPUT_MEAN_KEY),
+ getString(R.string.INPUT_MEAN_DEFAULT)), ",");
+ float[] input_std =
+ Utils.parseFloatsFromString(sharedPreferences.getString(getString(R.string.INPUT_STD_KEY)
+ , getString(R.string.INPUT_STD_DEFAULT)), ",");
+ settingsChanged |= input_shape.length != inputShape.length;
+ settingsChanged |= input_mean.length != inputMean.length;
+ settingsChanged |= input_std.length != inputStd.length;
+ if (!settingsChanged) {
+ for (int i = 0; i < input_shape.length; i++) {
+ settingsChanged |= input_shape[i] != inputShape[i];
+ }
+ for (int i = 0; i < input_mean.length; i++) {
+ settingsChanged |= input_mean[i] != inputMean[i];
+ }
+ for (int i = 0; i < input_std.length; i++) {
+ settingsChanged |= input_std[i] != inputStd[i];
+ }
+ }
+ float score_threshold =
+ Float.parseFloat(sharedPreferences.getString(getString(R.string.SCORE_THRESHOLD_KEY),
+ getString(R.string.SCORE_THRESHOLD_DEFAULT)));
+ settingsChanged |= scoreThreshold != score_threshold;
+ if (settingsChanged) {
+ modelPath = model_path;
+ labelPath = label_path;
+ imagePath = image_path;
+ cpuThreadNum = cpu_thread_num;
+ cpuPowerMode = cpu_power_mode;
+ inputColorFormat = input_color_format;
+ inputShape = input_shape;
+ inputMean = input_mean;
+ inputStd = input_std;
+ scoreThreshold = score_threshold;
+ // Update UI
+ tvInputSetting.setText("Model: " + modelPath.substring(modelPath.lastIndexOf("/") + 1) + "\n" + "CPU" +
+ " Thread Num: " + Integer.toString(cpuThreadNum) + "\n" + "CPU Power Mode: " + cpuPowerMode);
+ tvInputSetting.scrollTo(0, 0);
+ // Reload model if configure has been changed
+ loadModel();
+ }
+ }
+
+ public void loadModel() {
+ pbLoadModel = ProgressDialog.show(this, "", "Loading model...", false, false);
+ sender.sendEmptyMessage(REQUEST_LOAD_MODEL);
+ }
+
+ public void runModel() {
+ pbRunModel = ProgressDialog.show(this, "", "Running model...", false, false);
+ sender.sendEmptyMessage(REQUEST_RUN_MODEL);
+ }
+
+ public boolean onLoadModel() {
+ return predictor.init(MainActivity.this, modelPath, labelPath, cpuThreadNum,
+ cpuPowerMode,
+ inputColorFormat,
+ inputShape, inputMean,
+ inputStd, scoreThreshold);
+ }
+
+ public boolean onRunModel() {
+ return predictor.isLoaded() && predictor.runModel();
+ }
+
+ public void onLoadModelSuccessed() {
+ // Load test image from path and run model
+ try {
+ if (imagePath.isEmpty()) {
+ return;
+ }
+ Bitmap image = null;
+ // Read test image file from custom path if the first character of mode path is '/', otherwise read test
+ // image file from assets
+ if (!imagePath.substring(0, 1).equals("/")) {
+ InputStream imageStream = getAssets().open(imagePath);
+ image = BitmapFactory.decodeStream(imageStream);
+ } else {
+ if (!new File(imagePath).exists()) {
+ return;
+ }
+ image = BitmapFactory.decodeFile(imagePath);
+ }
+ if (image != null && predictor.isLoaded()) {
+ predictor.setInputImage(image);
+ runModel();
+ }
+ } catch (IOException e) {
+ Toast.makeText(MainActivity.this, "Load image failed!", Toast.LENGTH_SHORT).show();
+ e.printStackTrace();
+ }
+ }
+
+ public void onLoadModelFailed() {
+ }
+
+ public void onRunModelSuccessed() {
+ // Obtain results and update UI
+ tvInferenceTime.setText("Inference time: " + predictor.inferenceTime() + " ms");
+ Bitmap outputImage = predictor.outputImage();
+ if (outputImage != null) {
+ ivInputImage.setImageBitmap(outputImage);
+ }
+ tvOutputResult.setText(predictor.outputResult());
+ tvOutputResult.scrollTo(0, 0);
+ }
+
+ public void onRunModelFailed() {
+ }
+
+ public void onImageChanged(Bitmap image) {
+ // Rerun model if users pick test image from gallery or camera
+ if (image != null && predictor.isLoaded()) {
+ predictor.setInputImage(image);
+ runModel();
+ }
+ }
+
+ public void onSettingsClicked() {
+ startActivity(new Intent(MainActivity.this, SettingsActivity.class));
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.menu_action_options, menu);
+ return true;
+ }
+
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ boolean isLoaded = predictor.isLoaded();
+ menu.findItem(R.id.open_gallery).setEnabled(isLoaded);
+ menu.findItem(R.id.take_photo).setEnabled(isLoaded);
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ break;
+ case R.id.open_gallery:
+ if (requestAllPermissions()) {
+ openGallery();
+ }
+ break;
+ case R.id.take_photo:
+ if (requestAllPermissions()) {
+ takePhoto();
+ }
+ break;
+ case R.id.settings:
+ if (requestAllPermissions()) {
+ // Make sure we have SDCard r&w permissions to load model from SDCard
+ onSettingsClicked();
+ }
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) {
+ Toast.makeText(this, "Permission Denied", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private boolean requestAllPermissions() {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED || ContextCompat.checkSelfPermission(this,
+ Manifest.permission.CAMERA)
+ != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.CAMERA},
+ 0);
+ return false;
+ }
+ return true;
+ }
+
+ private void openGallery() {
+ Intent intent = new Intent(Intent.ACTION_PICK, null);
+ intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
+ startActivityForResult(intent, OPEN_GALLERY_REQUEST_CODE);
+ }
+
+ private void takePhoto() {
+ Intent takePhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ if (takePhotoIntent.resolveActivity(getPackageManager()) != null) {
+ startActivityForResult(takePhotoIntent, TAKE_PHOTO_REQUEST_CODE);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK && data != null) {
+ switch (requestCode) {
+ case OPEN_GALLERY_REQUEST_CODE:
+ try {
+ ContentResolver resolver = getContentResolver();
+ Uri uri = data.getData();
+ Bitmap image = MediaStore.Images.Media.getBitmap(resolver, uri);
+ String[] proj = {MediaStore.Images.Media.DATA};
+ Cursor cursor = managedQuery(uri, proj, null, null, null);
+ cursor.moveToFirst();
+ onImageChanged(image);
+ } catch (IOException e) {
+ Log.e(TAG, e.toString());
+ }
+ break;
+ case TAKE_PHOTO_REQUEST_CODE:
+ Bundle extras = data.getExtras();
+ Bitmap image = (Bitmap) extras.get("data");
+ onImageChanged(image);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (predictor != null) {
+ predictor.releaseModel();
+ }
+ worker.quit();
+ super.onDestroy();
+ }
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java
new file mode 100644
index 0000000000000000000000000000000000000000..103d5d37aec3ddc026d48a202df17b140e3e4533
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OCRPredictorNative.java
@@ -0,0 +1,100 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.graphics.Bitmap;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class OCRPredictorNative {
+
+ private static final AtomicBoolean isSOLoaded = new AtomicBoolean();
+
+ public static void loadLibrary() throws RuntimeException {
+ if (!isSOLoaded.get() && isSOLoaded.compareAndSet(false, true)) {
+ try {
+ System.loadLibrary("Native");
+ } catch (Throwable e) {
+ RuntimeException exception = new RuntimeException(
+ "Load libNative.so failed, please check it exists in apk file.", e);
+ throw exception;
+ }
+ }
+ }
+
+ private Config config;
+
+ private long nativePointer = 0;
+
+ public OCRPredictorNative(Config config) {
+ this.config = config;
+ loadLibrary();
+ nativePointer = init(config.detModelFilename, config.recModelFilename,
+ config.cpuThreadNum, config.cpuPower);
+ Log.i("OCRPredictorNative", "load success " + nativePointer);
+
+ }
+
+ public void release(){
+ if (nativePointer != 0){
+ nativePointer = 0;
+ destory(nativePointer);
+ }
+ }
+
+ public ArrayList runImage(float[] inputData, int width, int height, int channels, Bitmap originalImage) {
+ Log.i("OCRPredictorNative", "begin to run image " + inputData.length + " " + width + " " + height);
+ float[] dims = new float[]{1, channels, height, width};
+ float[] rawResults = forward(nativePointer, inputData, dims, originalImage);
+ ArrayList results = postprocess(rawResults);
+ return results;
+ }
+
+ public static class Config {
+ public int cpuThreadNum;
+ public String cpuPower;
+ public String detModelFilename;
+ public String recModelFilename;
+
+ }
+
+ protected native long init(String detModelPath, String recModelPath, int threadNum, String cpuMode);
+
+ protected native float[] forward(long pointer, float[] buf, float[] ddims, Bitmap originalImage);
+
+ protected native void destory(long pointer);
+
+ private ArrayList postprocess(float[] raw) {
+ ArrayList results = new ArrayList();
+ int begin = 0;
+
+ while (begin < raw.length) {
+ int point_num = Math.round(raw[begin]);
+ int word_num = Math.round(raw[begin + 1]);
+ OcrResultModel model = parse(raw, begin + 2, point_num, word_num);
+ begin += 2 + 1 + point_num * 2 + word_num;
+ results.add(model);
+ }
+
+ return results;
+ }
+
+ private OcrResultModel parse(float[] raw, int begin, int pointNum, int wordNum) {
+ int current = begin;
+ OcrResultModel model = new OcrResultModel();
+ model.setConfidence(raw[current]);
+ current++;
+ for (int i = 0; i < pointNum; i++) {
+ model.addPoints(Math.round(raw[current + i * 2]), Math.round(raw[current + i * 2 + 1]));
+ }
+ current += (pointNum * 2);
+ for (int i = 0; i < wordNum; i++) {
+ int index = Math.round(raw[current + i]);
+ model.addWordIndex(index);
+ }
+ Log.i("OCRPredictorNative", "word finished " + wordNum);
+ return model;
+ }
+
+
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java
new file mode 100644
index 0000000000000000000000000000000000000000..9494574e0db2d0c5d11ce4ad1cb400710c71a623
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/OcrResultModel.java
@@ -0,0 +1,52 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.graphics.Point;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OcrResultModel {
+ private List points;
+ private List wordIndex;
+ private String label;
+ private float confidence;
+
+ public OcrResultModel() {
+ super();
+ points = new ArrayList<>();
+ wordIndex = new ArrayList<>();
+ }
+
+ public void addPoints(int x, int y) {
+ Point point = new Point(x, y);
+ points.add(point);
+ }
+
+ public void addWordIndex(int index) {
+ wordIndex.add(index);
+ }
+
+ public List getPoints() {
+ return points;
+ }
+
+ public List getWordIndex() {
+ return wordIndex;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public void setLabel(String label) {
+ this.label = label;
+ }
+
+ public float getConfidence() {
+ return confidence;
+ }
+
+ public void setConfidence(float confidence) {
+ this.confidence = confidence;
+ }
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java
new file mode 100644
index 0000000000000000000000000000000000000000..d491481e7b61bca8043c34a65ecb3bbf6a72487d
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Predictor.java
@@ -0,0 +1,351 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.util.Log;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Vector;
+
+import static android.graphics.Color.*;
+
+public class Predictor {
+ private static final String TAG = Predictor.class.getSimpleName();
+ public boolean isLoaded = false;
+ public int warmupIterNum = 1;
+ public int inferIterNum = 1;
+ public int cpuThreadNum = 4;
+ public String cpuPowerMode = "LITE_POWER_HIGH";
+ public String modelPath = "";
+ public String modelName = "";
+ protected OCRPredictorNative paddlePredictor = null;
+ protected float inferenceTime = 0;
+ // Only for object detection
+ protected Vector wordLabels = new Vector();
+ protected String inputColorFormat = "BGR";
+ protected long[] inputShape = new long[]{1, 3, 960};
+ protected float[] inputMean = new float[]{0.485f, 0.456f, 0.406f};
+ protected float[] inputStd = new float[]{1.0f / 0.229f, 1.0f / 0.224f, 1.0f / 0.225f};
+ protected float scoreThreshold = 0.1f;
+ protected Bitmap inputImage = null;
+ protected Bitmap outputImage = null;
+ protected String outputResult = "";
+ protected float preprocessTime = 0;
+ protected float postprocessTime = 0;
+
+
+ public Predictor() {
+ }
+
+ public boolean init(Context appCtx, String modelPath, String labelPath, int cpuThreadNum, String cpuPowerMode,
+ String inputColorFormat,
+ long[] inputShape, float[] inputMean,
+ float[] inputStd, float scoreThreshold) {
+ if (inputShape.length != 3) {
+ Log.e(TAG, "Size of input shape should be: 3");
+ return false;
+ }
+ if (inputMean.length != inputShape[1]) {
+ Log.e(TAG, "Size of input mean should be: " + Long.toString(inputShape[1]));
+ return false;
+ }
+ if (inputStd.length != inputShape[1]) {
+ Log.e(TAG, "Size of input std should be: " + Long.toString(inputShape[1]));
+ return false;
+ }
+ if (inputShape[0] != 1) {
+ Log.e(TAG, "Only one batch is supported in the image classification demo, you can use any batch size in " +
+ "your Apps!");
+ return false;
+ }
+ if (inputShape[1] != 1 && inputShape[1] != 3) {
+ Log.e(TAG, "Only one/three channels are supported in the image classification demo, you can use any " +
+ "channel size in your Apps!");
+ return false;
+ }
+ if (!inputColorFormat.equalsIgnoreCase("BGR")) {
+ Log.e(TAG, "Only BGR color format is supported.");
+ return false;
+ }
+ isLoaded = loadModel(appCtx, modelPath, cpuThreadNum, cpuPowerMode);
+ if (!isLoaded) {
+ return false;
+ }
+ isLoaded = loadLabel(appCtx, labelPath);
+ if (!isLoaded) {
+ return false;
+ }
+ this.inputColorFormat = inputColorFormat;
+ this.inputShape = inputShape;
+ this.inputMean = inputMean;
+ this.inputStd = inputStd;
+ this.scoreThreshold = scoreThreshold;
+ return true;
+ }
+
+ protected boolean loadModel(Context appCtx, String modelPath, int cpuThreadNum, String cpuPowerMode) {
+ // Release model if exists
+ releaseModel();
+
+ // Load model
+ if (modelPath.isEmpty()) {
+ return false;
+ }
+ String realPath = modelPath;
+ if (!modelPath.substring(0, 1).equals("/")) {
+ // Read model files from custom path if the first character of mode path is '/'
+ // otherwise copy model to cache from assets
+ realPath = appCtx.getCacheDir() + "/" + modelPath;
+ Utils.copyDirectoryFromAssets(appCtx, modelPath, realPath);
+ }
+ if (realPath.isEmpty()) {
+ return false;
+ }
+
+ OCRPredictorNative.Config config = new OCRPredictorNative.Config();
+ config.cpuThreadNum = cpuThreadNum;
+ config.detModelFilename = realPath + File.separator + "ch_det_mv3_db_opt.nb";
+ config.recModelFilename = realPath + File.separator + "ch_rec_mv3_crnn_opt.nb";
+ Log.e("Predictor", "model path" + config.detModelFilename + " ; " + config.recModelFilename);
+ config.cpuPower = cpuPowerMode;
+ paddlePredictor = new OCRPredictorNative(config);
+
+ this.cpuThreadNum = cpuThreadNum;
+ this.cpuPowerMode = cpuPowerMode;
+ this.modelPath = realPath;
+ this.modelName = realPath.substring(realPath.lastIndexOf("/") + 1);
+ return true;
+ }
+
+ public void releaseModel() {
+ if (paddlePredictor != null){
+ paddlePredictor.release();
+ paddlePredictor = null;
+ }
+ isLoaded = false;
+ cpuThreadNum = 4;
+ cpuPowerMode = "LITE_POWER_HIGH";
+ modelPath = "";
+ modelName = "";
+ }
+
+ protected boolean loadLabel(Context appCtx, String labelPath) {
+ wordLabels.clear();
+ // Load word labels from file
+ try {
+ InputStream assetsInputStream = appCtx.getAssets().open(labelPath);
+ int available = assetsInputStream.available();
+ byte[] lines = new byte[available];
+ assetsInputStream.read(lines);
+ assetsInputStream.close();
+ String words = new String(lines);
+ String[] contents = words.split("\n");
+ for (String content : contents) {
+ wordLabels.add(content);
+ }
+ Log.i(TAG, "Word label size: " + wordLabels.size());
+ } catch (Exception e) {
+ Log.e(TAG, e.getMessage());
+ return false;
+ }
+ return true;
+ }
+
+
+ public boolean runModel() {
+ if (inputImage == null || !isLoaded()) {
+ return false;
+ }
+
+ // Pre-process image, and feed input tensor with pre-processed data
+
+ Bitmap scaleImage = Utils.resizeWithStep(inputImage, Long.valueOf(inputShape[2]).intValue(), 32);
+
+ Date start = new Date();
+ int channels = (int) inputShape[1];
+ int width = scaleImage.getWidth();
+ int height = scaleImage.getHeight();
+ float[] inputData = new float[channels * width * height];
+ if (channels == 3) {
+ int[] channelIdx = null;
+ if (inputColorFormat.equalsIgnoreCase("RGB")) {
+ channelIdx = new int[]{0, 1, 2};
+ } else if (inputColorFormat.equalsIgnoreCase("BGR")) {
+ channelIdx = new int[]{2, 1, 0};
+ } else {
+ Log.i(TAG, "Unknown color format " + inputColorFormat + ", only RGB and BGR color format is " +
+ "supported!");
+ return false;
+ }
+ int[] channelStride = new int[]{width * height, width * height * 2};
+ int p = scaleImage.getPixel(scaleImage.getWidth() - 1, scaleImage.getHeight() - 1);
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int color = scaleImage.getPixel(x, y);
+ float[] rgb = new float[]{(float) red(color) / 255.0f, (float) green(color) / 255.0f,
+ (float) blue(color) / 255.0f};
+ inputData[y * width + x] = (rgb[channelIdx[0]] - inputMean[0]) / inputStd[0];
+ inputData[y * width + x + channelStride[0]] = (rgb[channelIdx[1]] - inputMean[1]) / inputStd[1];
+ inputData[y * width + x + channelStride[1]] = (rgb[channelIdx[2]] - inputMean[2]) / inputStd[2];
+
+ }
+ }
+ } else if (channels == 1) {
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int color = inputImage.getPixel(x, y);
+ float gray = (float) (red(color) + green(color) + blue(color)) / 3.0f / 255.0f;
+ inputData[y * width + x] = (gray - inputMean[0]) / inputStd[0];
+ }
+ }
+ } else {
+ Log.i(TAG, "Unsupported channel size " + Integer.toString(channels) + ", only channel 1 and 3 is " +
+ "supported!");
+ return false;
+ }
+ float[] pixels = inputData;
+ Log.i(TAG, "pixels " + pixels[0] + " " + pixels[1] + " " + pixels[2] + " " + pixels[3]
+ + " " + pixels[pixels.length / 2] + " " + pixels[pixels.length / 2 + 1] + " " + pixels[pixels.length - 2] + " " + pixels[pixels.length - 1]);
+ Date end = new Date();
+ preprocessTime = (float) (end.getTime() - start.getTime());
+
+ // Warm up
+ for (int i = 0; i < warmupIterNum; i++) {
+ paddlePredictor.runImage(inputData, width, height, channels, inputImage);
+ }
+ warmupIterNum = 0; // 之后不要再warm了
+ // Run inference
+ start = new Date();
+ ArrayList results = paddlePredictor.runImage(inputData, width, height, channels, inputImage);
+ end = new Date();
+ inferenceTime = (end.getTime() - start.getTime()) / (float) inferIterNum;
+
+ results = postprocess(results);
+ Log.i(TAG, "[stat] Preprocess Time: " + preprocessTime
+ + " ; Inference Time: " + inferenceTime + " ;Box Size " + results.size());
+ drawResults(results);
+
+ return true;
+ }
+
+
+ public boolean isLoaded() {
+ return paddlePredictor != null && isLoaded;
+ }
+
+ public String modelPath() {
+ return modelPath;
+ }
+
+ public String modelName() {
+ return modelName;
+ }
+
+ public int cpuThreadNum() {
+ return cpuThreadNum;
+ }
+
+ public String cpuPowerMode() {
+ return cpuPowerMode;
+ }
+
+ public float inferenceTime() {
+ return inferenceTime;
+ }
+
+ public Bitmap inputImage() {
+ return inputImage;
+ }
+
+ public Bitmap outputImage() {
+ return outputImage;
+ }
+
+ public String outputResult() {
+ return outputResult;
+ }
+
+ public float preprocessTime() {
+ return preprocessTime;
+ }
+
+ public float postprocessTime() {
+ return postprocessTime;
+ }
+
+
+ public void setInputImage(Bitmap image) {
+ if (image == null) {
+ return;
+ }
+ // Scale image to the size of input tensor
+ Bitmap rgbaImage = image.copy(Bitmap.Config.ARGB_8888, true);
+ this.inputImage = rgbaImage;
+ }
+
+ private ArrayList postprocess(ArrayList results) {
+ for (OcrResultModel r : results) {
+ StringBuffer word = new StringBuffer();
+ for (int index : r.getWordIndex()) {
+ if (index >= 0 && index < wordLabels.size()) {
+ word.append(wordLabels.get(index));
+ } else {
+ Log.e(TAG, "Word index is not in label list:" + index);
+ word.append("×");
+ }
+ }
+ r.setLabel(word.toString());
+ }
+ return results;
+ }
+
+ private void drawResults(ArrayList results) {
+ StringBuffer outputResultSb = new StringBuffer("");
+ for (int i=0;i points = result.getPoints();
+ path.moveTo(points.get(0).x, points.get(0).y);
+ for (int i = points.size() - 1; i >= 0; i--) {
+ Point p = points.get(i);
+ path.lineTo(p.x, p.y);
+ }
+ canvas.drawPath(path, paint);
+ canvas.drawPath(path, paintFillAlpha);
+ }
+ }
+
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..28727e60a3e5c124318e1d19e84fe7e4693a8eae
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/SettingsActivity.java
@@ -0,0 +1,200 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
+import android.preference.ListPreference;
+import android.support.v7.app.ActionBar;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class SettingsActivity extends AppCompatPreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
+ ListPreference lpChoosePreInstalledModel = null;
+ CheckBoxPreference cbEnableCustomSettings = null;
+ EditTextPreference etModelPath = null;
+ EditTextPreference etLabelPath = null;
+ EditTextPreference etImagePath = null;
+ ListPreference lpCPUThreadNum = null;
+ ListPreference lpCPUPowerMode = null;
+ ListPreference lpInputColorFormat = null;
+ EditTextPreference etInputShape = null;
+ EditTextPreference etInputMean = null;
+ EditTextPreference etInputStd = null;
+ EditTextPreference etScoreThreshold = null;
+
+ List preInstalledModelPaths = null;
+ List preInstalledLabelPaths = null;
+ List preInstalledImagePaths = null;
+ List preInstalledInputShapes = null;
+ List preInstalledCPUThreadNums = null;
+ List preInstalledCPUPowerModes = null;
+ List preInstalledInputColorFormats = null;
+ List preInstalledInputMeans = null;
+ List preInstalledInputStds = null;
+ List preInstalledScoreThresholds = null;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.settings);
+ ActionBar supportActionBar = getSupportActionBar();
+ if (supportActionBar != null) {
+ supportActionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ // Initialized pre-installed models
+ preInstalledModelPaths = new ArrayList();
+ preInstalledLabelPaths = new ArrayList();
+ preInstalledImagePaths = new ArrayList();
+ preInstalledInputShapes = new ArrayList();
+ preInstalledCPUThreadNums = new ArrayList();
+ preInstalledCPUPowerModes = new ArrayList();
+ preInstalledInputColorFormats = new ArrayList();
+ preInstalledInputMeans = new ArrayList();
+ preInstalledInputStds = new ArrayList();
+ preInstalledScoreThresholds = new ArrayList();
+ // Add ssd_mobilenet_v1_pascalvoc_for_cpu
+ preInstalledModelPaths.add(getString(R.string.MODEL_PATH_DEFAULT));
+ preInstalledLabelPaths.add(getString(R.string.LABEL_PATH_DEFAULT));
+ preInstalledImagePaths.add(getString(R.string.IMAGE_PATH_DEFAULT));
+ preInstalledCPUThreadNums.add(getString(R.string.CPU_THREAD_NUM_DEFAULT));
+ preInstalledCPUPowerModes.add(getString(R.string.CPU_POWER_MODE_DEFAULT));
+ preInstalledInputColorFormats.add(getString(R.string.INPUT_COLOR_FORMAT_DEFAULT));
+ preInstalledInputShapes.add(getString(R.string.INPUT_SHAPE_DEFAULT));
+ preInstalledInputMeans.add(getString(R.string.INPUT_MEAN_DEFAULT));
+ preInstalledInputStds.add(getString(R.string.INPUT_STD_DEFAULT));
+ preInstalledScoreThresholds.add(getString(R.string.SCORE_THRESHOLD_DEFAULT));
+
+ // Setup UI components
+ lpChoosePreInstalledModel =
+ (ListPreference) findPreference(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY));
+ String[] preInstalledModelNames = new String[preInstalledModelPaths.size()];
+ for (int i = 0; i < preInstalledModelPaths.size(); i++) {
+ preInstalledModelNames[i] =
+ preInstalledModelPaths.get(i).substring(preInstalledModelPaths.get(i).lastIndexOf("/") + 1);
+ }
+ lpChoosePreInstalledModel.setEntries(preInstalledModelNames);
+ lpChoosePreInstalledModel.setEntryValues(preInstalledModelPaths.toArray(new String[preInstalledModelPaths.size()]));
+ cbEnableCustomSettings =
+ (CheckBoxPreference) findPreference(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY));
+ etModelPath = (EditTextPreference) findPreference(getString(R.string.MODEL_PATH_KEY));
+ etModelPath.setTitle("Model Path (SDCard: " + Utils.getSDCardDirectory() + ")");
+ etLabelPath = (EditTextPreference) findPreference(getString(R.string.LABEL_PATH_KEY));
+ etImagePath = (EditTextPreference) findPreference(getString(R.string.IMAGE_PATH_KEY));
+ lpCPUThreadNum =
+ (ListPreference) findPreference(getString(R.string.CPU_THREAD_NUM_KEY));
+ lpCPUPowerMode =
+ (ListPreference) findPreference(getString(R.string.CPU_POWER_MODE_KEY));
+ lpInputColorFormat =
+ (ListPreference) findPreference(getString(R.string.INPUT_COLOR_FORMAT_KEY));
+ etInputShape = (EditTextPreference) findPreference(getString(R.string.INPUT_SHAPE_KEY));
+ etInputMean = (EditTextPreference) findPreference(getString(R.string.INPUT_MEAN_KEY));
+ etInputStd = (EditTextPreference) findPreference(getString(R.string.INPUT_STD_KEY));
+ etScoreThreshold = (EditTextPreference) findPreference(getString(R.string.SCORE_THRESHOLD_KEY));
+ }
+
+ private void reloadPreferenceAndUpdateUI() {
+ SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences();
+ boolean enableCustomSettings =
+ sharedPreferences.getBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false);
+ String modelPath = sharedPreferences.getString(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY),
+ getString(R.string.MODEL_PATH_DEFAULT));
+ int modelIdx = lpChoosePreInstalledModel.findIndexOfValue(modelPath);
+ if (modelIdx >= 0 && modelIdx < preInstalledModelPaths.size()) {
+ if (!enableCustomSettings) {
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putString(getString(R.string.MODEL_PATH_KEY), preInstalledModelPaths.get(modelIdx));
+ editor.putString(getString(R.string.LABEL_PATH_KEY), preInstalledLabelPaths.get(modelIdx));
+ editor.putString(getString(R.string.IMAGE_PATH_KEY), preInstalledImagePaths.get(modelIdx));
+ editor.putString(getString(R.string.CPU_THREAD_NUM_KEY), preInstalledCPUThreadNums.get(modelIdx));
+ editor.putString(getString(R.string.CPU_POWER_MODE_KEY), preInstalledCPUPowerModes.get(modelIdx));
+ editor.putString(getString(R.string.INPUT_COLOR_FORMAT_KEY),
+ preInstalledInputColorFormats.get(modelIdx));
+ editor.putString(getString(R.string.INPUT_SHAPE_KEY), preInstalledInputShapes.get(modelIdx));
+ editor.putString(getString(R.string.INPUT_MEAN_KEY), preInstalledInputMeans.get(modelIdx));
+ editor.putString(getString(R.string.INPUT_STD_KEY), preInstalledInputStds.get(modelIdx));
+ editor.putString(getString(R.string.SCORE_THRESHOLD_KEY),
+ preInstalledScoreThresholds.get(modelIdx));
+ editor.commit();
+ }
+ lpChoosePreInstalledModel.setSummary(modelPath);
+ }
+ cbEnableCustomSettings.setChecked(enableCustomSettings);
+ etModelPath.setEnabled(enableCustomSettings);
+ etLabelPath.setEnabled(enableCustomSettings);
+ etImagePath.setEnabled(enableCustomSettings);
+ lpCPUThreadNum.setEnabled(enableCustomSettings);
+ lpCPUPowerMode.setEnabled(enableCustomSettings);
+ lpInputColorFormat.setEnabled(enableCustomSettings);
+ etInputShape.setEnabled(enableCustomSettings);
+ etInputMean.setEnabled(enableCustomSettings);
+ etInputStd.setEnabled(enableCustomSettings);
+ etScoreThreshold.setEnabled(enableCustomSettings);
+ modelPath = sharedPreferences.getString(getString(R.string.MODEL_PATH_KEY),
+ getString(R.string.MODEL_PATH_DEFAULT));
+ String labelPath = sharedPreferences.getString(getString(R.string.LABEL_PATH_KEY),
+ getString(R.string.LABEL_PATH_DEFAULT));
+ String imagePath = sharedPreferences.getString(getString(R.string.IMAGE_PATH_KEY),
+ getString(R.string.IMAGE_PATH_DEFAULT));
+ String cpuThreadNum = sharedPreferences.getString(getString(R.string.CPU_THREAD_NUM_KEY),
+ getString(R.string.CPU_THREAD_NUM_DEFAULT));
+ String cpuPowerMode = sharedPreferences.getString(getString(R.string.CPU_POWER_MODE_KEY),
+ getString(R.string.CPU_POWER_MODE_DEFAULT));
+ String inputColorFormat = sharedPreferences.getString(getString(R.string.INPUT_COLOR_FORMAT_KEY),
+ getString(R.string.INPUT_COLOR_FORMAT_DEFAULT));
+ String inputShape = sharedPreferences.getString(getString(R.string.INPUT_SHAPE_KEY),
+ getString(R.string.INPUT_SHAPE_DEFAULT));
+ String inputMean = sharedPreferences.getString(getString(R.string.INPUT_MEAN_KEY),
+ getString(R.string.INPUT_MEAN_DEFAULT));
+ String inputStd = sharedPreferences.getString(getString(R.string.INPUT_STD_KEY),
+ getString(R.string.INPUT_STD_DEFAULT));
+ String scoreThreshold = sharedPreferences.getString(getString(R.string.SCORE_THRESHOLD_KEY),
+ getString(R.string.SCORE_THRESHOLD_DEFAULT));
+ etModelPath.setSummary(modelPath);
+ etModelPath.setText(modelPath);
+ etLabelPath.setSummary(labelPath);
+ etLabelPath.setText(labelPath);
+ etImagePath.setSummary(imagePath);
+ etImagePath.setText(imagePath);
+ lpCPUThreadNum.setValue(cpuThreadNum);
+ lpCPUThreadNum.setSummary(cpuThreadNum);
+ lpCPUPowerMode.setValue(cpuPowerMode);
+ lpCPUPowerMode.setSummary(cpuPowerMode);
+ lpInputColorFormat.setValue(inputColorFormat);
+ lpInputColorFormat.setSummary(inputColorFormat);
+ etInputShape.setSummary(inputShape);
+ etInputShape.setText(inputShape);
+ etInputMean.setSummary(inputMean);
+ etInputMean.setText(inputMean);
+ etInputStd.setSummary(inputStd);
+ etInputStd.setText(inputStd);
+ etScoreThreshold.setText(scoreThreshold);
+ etScoreThreshold.setSummary(scoreThreshold);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ reloadPreferenceAndUpdateUI();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals(getString(R.string.CHOOSE_PRE_INSTALLED_MODEL_KEY))) {
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putBoolean(getString(R.string.ENABLE_CUSTOM_SETTINGS_KEY), false);
+ editor.commit();
+ }
+ reloadPreferenceAndUpdateUI();
+ }
+}
diff --git a/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java
new file mode 100644
index 0000000000000000000000000000000000000000..90549433477f6a8cdb15b03203b2a5836f994d78
--- /dev/null
+++ b/deploy/android_demo/app/src/main/java/com/baidu/paddle/lite/demo/ocr/Utils.java
@@ -0,0 +1,113 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.Environment;
+
+import java.io.*;
+
+public class Utils {
+ private static final String TAG = Utils.class.getSimpleName();
+
+ public static void copyFileFromAssets(Context appCtx, String srcPath, String dstPath) {
+ if (srcPath.isEmpty() || dstPath.isEmpty()) {
+ return;
+ }
+ InputStream is = null;
+ OutputStream os = null;
+ try {
+ is = new BufferedInputStream(appCtx.getAssets().open(srcPath));
+ os = new BufferedOutputStream(new FileOutputStream(new File(dstPath)));
+ byte[] buffer = new byte[1024];
+ int length = 0;
+ while ((length = is.read(buffer)) != -1) {
+ os.write(buffer, 0, length);
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ os.close();
+ is.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public static void copyDirectoryFromAssets(Context appCtx, String srcDir, String dstDir) {
+ if (srcDir.isEmpty() || dstDir.isEmpty()) {
+ return;
+ }
+ try {
+ if (!new File(dstDir).exists()) {
+ new File(dstDir).mkdirs();
+ }
+ for (String fileName : appCtx.getAssets().list(srcDir)) {
+ String srcSubPath = srcDir + File.separator + fileName;
+ String dstSubPath = dstDir + File.separator + fileName;
+ if (new File(srcSubPath).isDirectory()) {
+ copyDirectoryFromAssets(appCtx, srcSubPath, dstSubPath);
+ } else {
+ copyFileFromAssets(appCtx, srcSubPath, dstSubPath);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static float[] parseFloatsFromString(String string, String delimiter) {
+ String[] pieces = string.trim().toLowerCase().split(delimiter);
+ float[] floats = new float[pieces.length];
+ for (int i = 0; i < pieces.length; i++) {
+ floats[i] = Float.parseFloat(pieces[i].trim());
+ }
+ return floats;
+ }
+
+ public static long[] parseLongsFromString(String string, String delimiter) {
+ String[] pieces = string.trim().toLowerCase().split(delimiter);
+ long[] longs = new long[pieces.length];
+ for (int i = 0; i < pieces.length; i++) {
+ longs[i] = Long.parseLong(pieces[i].trim());
+ }
+ return longs;
+ }
+
+ public static String getSDCardDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath();
+ }
+
+ public static boolean isSupportedNPU() {
+ return false;
+ // String hardware = android.os.Build.HARDWARE;
+ // return hardware.equalsIgnoreCase("kirin810") || hardware.equalsIgnoreCase("kirin990");
+ }
+
+ public static Bitmap resizeWithStep(Bitmap bitmap, int maxLength, int step) {
+ int width = bitmap.getWidth();
+ int height = bitmap.getHeight();
+ int maxWH = Math.max(width, height);
+ float ratio = 1;
+ int newWidth = width;
+ int newHeight = height;
+ if (maxWH > maxLength) {
+ ratio = maxLength * 1.0f / maxWH;
+ newWidth = (int) Math.floor(ratio * width);
+ newHeight = (int) Math.floor(ratio * height);
+ }
+
+ newWidth = newWidth - newWidth % step;
+ if (newWidth == 0) {
+ newWidth = step;
+ }
+ newHeight = newHeight - newHeight % step;
+ if (newHeight == 0) {
+ newHeight = step;
+ }
+ return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true);
+ }
+}
diff --git a/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1f6bb290603d7caa16c5fb6f61bbfdc750622f5c
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml b/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0d025f9bf6b67c63044a36a9ff44fbc69e5c5822
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/deploy/android_demo/app/src/main/res/layout/activity_main.xml b/deploy/android_demo/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000000000000000000000000000000000000..98b9bc1a875dc3a006496d9f87510f0f5d2f204c
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,99 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/deploy/android_demo/app/src/main/res/menu/menu_action_options.xml b/deploy/android_demo/app/src/main/res/menu/menu_action_options.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fe74758ae568ebfab41537ff72affb0992d753de
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/menu/menu_action_options.xml
@@ -0,0 +1,21 @@
+
diff --git a/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000000000000000000000000000000000..eca70cfe52eac1ba66ba280a68ca7be8fcf88a16
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000000000000000000000000000000000000..eca70cfe52eac1ba66ba280a68ca7be8fcf88a16
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher.png b/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..898f3ed59ac9f3248734a00e5902736c9367d455
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..dffca3601eba7bf5f409bdd520820e2eb5122c75
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher.png b/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..64ba76f75e9ce021aa3d95c213491f73bcacb597
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..dae5e082342fcdeee5db8a6e0b27028e2d2808f5
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..e5ed46597ea8447d91ab1786a34e30f1c26b18bd
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..14ed0af35023e4f1901cf03487b6c524257b8483
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..b0907cac3bfd8fbfdc46e1108247f0a1055387ec
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..d8ae03154975f397f8ed1b84f2d4bf9783ecfa26
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..2c18de9e66108411737e910f5c1972476f03ddbf
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000000000000000000000000000000000000..beed3cdd2c32af5114a7dc70b9ef5b698eb8797e
Binary files /dev/null and b/deploy/android_demo/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/deploy/android_demo/app/src/main/res/values/arrays.xml b/deploy/android_demo/app/src/main/res/values/arrays.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8e08ad57ddaca4bb0cff1a3d61ec84b0442b1b0e
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/values/arrays.xml
@@ -0,0 +1,39 @@
+
+
+
+ - 1 threads
+ - 2 threads
+ - 4 threads
+ - 8 threads
+
+
+ - 1
+ - 2
+ - 4
+ - 8
+
+
+ - HIGH(only big cores)
+ - LOW(only LITTLE cores)
+ - FULL(all cores)
+ - NO_BIND(depends on system)
+ - RAND_HIGH
+ - RAND_LOW
+
+
+ - LITE_POWER_HIGH
+ - LITE_POWER_LOW
+ - LITE_POWER_FULL
+ - LITE_POWER_NO_BIND
+ - LITE_POWER_RAND_HIGH
+ - LITE_POWER_RAND_LOW
+
+
+ - BGR color format
+ - RGB color format
+
+
+ - BGR
+ - RGB
+
+
\ No newline at end of file
diff --git a/deploy/android_demo/app/src/main/res/values/colors.xml b/deploy/android_demo/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000000000000000000000000000000000000..69b22338c6510250df3b43672635120dbce2fa49
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #008577
+ #00574B
+ #D81B60
+
diff --git a/deploy/android_demo/app/src/main/res/values/strings.xml b/deploy/android_demo/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f092ae4032321584b6dcd87de21fd250a1b4a035
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/values/strings.xml
@@ -0,0 +1,26 @@
+
+ OCR Chinese
+ CHOOSE_PRE_INSTALLED_MODEL_KEY
+ ENABLE_CUSTOM_SETTINGS_KEY
+ MODEL_PATH_KEY
+ LABEL_PATH_KEY
+ IMAGE_PATH_KEY
+ CPU_THREAD_NUM_KEY
+ CPU_POWER_MODE_KEY
+ INPUT_COLOR_FORMAT_KEY
+ INPUT_SHAPE_KEY
+ INPUT_MEAN_KEY
+ INPUT_STD_KEY
+ SCORE_THRESHOLD_KEY
+ models/ocr_v1_for_cpu
+ labels/ppocr_keys_v1.txt
+ images/5.jpg
+ 4
+ LITE_POWER_HIGH
+ BGR
+ 1,3,960
+ 0.485, 0.456, 0.406
+ 0.229,0.224,0.225
+ 0.1
+
+
diff --git a/deploy/android_demo/app/src/main/res/values/styles.xml b/deploy/android_demo/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000000000000000000000000000000000000..853262016a2ffab26185bf9f3dcd59e10605630a
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/values/styles.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/deploy/android_demo/app/src/main/res/xml/settings.xml b/deploy/android_demo/app/src/main/res/xml/settings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..440f3e4785569167904e65885c23dac91ef48c94
--- /dev/null
+++ b/deploy/android_demo/app/src/main/res/xml/settings.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/deploy/android_demo/app/src/test/java/com/baidu/paddle/lite/demo/ocr/ExampleUnitTest.java b/deploy/android_demo/app/src/test/java/com/baidu/paddle/lite/demo/ocr/ExampleUnitTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d523a9a7087434ffe9ce539a1eab76f26ea46b49
--- /dev/null
+++ b/deploy/android_demo/app/src/test/java/com/baidu/paddle/lite/demo/ocr/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.baidu.paddle.lite.demo.ocr;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/deploy/android_demo/build.gradle b/deploy/android_demo/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..fafc1b970be053f8a9ec61f55b94cb2e85b26a33
--- /dev/null
+++ b/deploy/android_demo/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ repositories {
+ google()
+ jcenter()
+
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.4.0'
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/deploy/android_demo/gradle.properties b/deploy/android_demo/gradle.properties
new file mode 100644
index 0000000000000000000000000000000000000000..82618cecb4d1cf137df18eca8dbe88e1b3b2c2b8
--- /dev/null
+++ b/deploy/android_demo/gradle.properties
@@ -0,0 +1,15 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+
+
diff --git a/deploy/android_demo/gradle/wrapper/gradle-wrapper.jar b/deploy/android_demo/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718
Binary files /dev/null and b/deploy/android_demo/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/deploy/android_demo/gradle/wrapper/gradle-wrapper.properties b/deploy/android_demo/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000000000000000000000000000000000..578b5482ad45045124272fa3e54d065a77c2eea2
--- /dev/null
+++ b/deploy/android_demo/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Aug 22 15:05:37 CST 2019
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
diff --git a/deploy/android_demo/gradlew b/deploy/android_demo/gradlew
new file mode 100644
index 0000000000000000000000000000000000000000..cccdd3d517fc5249beaefa600691cf150f2fa3e6
--- /dev/null
+++ b/deploy/android_demo/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/deploy/android_demo/gradlew.bat b/deploy/android_demo/gradlew.bat
new file mode 100644
index 0000000000000000000000000000000000000000..f9553162f122c71b34635112e717c3e733b5b212
--- /dev/null
+++ b/deploy/android_demo/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/deploy/android_demo/settings.gradle b/deploy/android_demo/settings.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..e7b4def49cb53d9aa04228dd3edb14c9e635e003
--- /dev/null
+++ b/deploy/android_demo/settings.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/deploy/cpp_infer/include/ocr_rec.h b/deploy/cpp_infer/include/ocr_rec.h
index 18b31b6b16dc7b45f7ea3622feffdcc33188bda9..471aeb58758d1de1d48b4da1067c8532457ddc92 100644
--- a/deploy/cpp_infer/include/ocr_rec.h
+++ b/deploy/cpp_infer/include/ocr_rec.h
@@ -46,6 +46,7 @@ public:
this->use_mkldnn_ = use_mkldnn;
this->label_list_ = Utility::ReadDict(label_path);
+ this->label_list_.push_back(" ");
LoadModel(model_dir);
}
@@ -83,4 +84,4 @@ private:
}; // class CrnnRecognizer
-} // namespace PaddleOCR
\ No newline at end of file
+} // namespace PaddleOCR
diff --git a/deploy/cpp_infer/readme.md b/deploy/cpp_infer/readme.md
index 47aa3a1133267a6c9597ea2da08aa0daa43c83d1..03db8c5051a9720de599e396a589f3e0c252e5e8 100644
--- a/deploy/cpp_infer/readme.md
+++ b/deploy/cpp_infer/readme.md
@@ -184,7 +184,7 @@ make -j
### 运行demo
-* 执行以下命令,完成对一幅图像的OCR识别与检测,最终输出
+* 执行以下命令,完成对一幅图像的OCR识别与检测。
```shell
sh tools/run.sh
diff --git a/deploy/cpp_infer/readme_en.md b/deploy/cpp_infer/readme_en.md
new file mode 100644
index 0000000000000000000000000000000000000000..ecb29f9b9673446c86b2b561440b57d29ea457f4
--- /dev/null
+++ b/deploy/cpp_infer/readme_en.md
@@ -0,0 +1,213 @@
+# Server-side C++ inference
+
+
+In this tutorial, we will introduce the detailed steps of deploying PaddleOCR ultra-lightweight Chinese detection and recognition models on the server side.
+
+
+## 1. Prepare the environment
+
+### Environment
+
+- Linux, docker is recommended.
+
+
+### 1.1 Compile opencv
+
+* First of all, you need to download the source code compiled package in the Linux environment from the opencv official website. Taking opencv3.4.7 as an example, the download command is as follows.
+
+```
+wget https://github.com/opencv/opencv/archive/3.4.7.tar.gz
+tar -xf 3.4.7.tar.gz
+```
+
+Finally, you can see the folder of `opencv-3.4.7/` in the current directory.
+
+* Compile opencv, the opencv source path (`root_path`) and installation path (`install_path`) should be set by yourself. Enter the opencv source code path and compile it in the following way.
+
+
+```shell
+root_path=your_opencv_root_path
+install_path=${root_path}/opencv3
+
+rm -rf build
+mkdir build
+cd build
+
+cmake .. \
+ -DCMAKE_INSTALL_PREFIX=${install_path} \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DBUILD_SHARED_LIBS=OFF \
+ -DWITH_IPP=OFF \
+ -DBUILD_IPP_IW=OFF \
+ -DWITH_LAPACK=OFF \
+ -DWITH_EIGEN=OFF \
+ -DCMAKE_INSTALL_LIBDIR=lib64 \
+ -DWITH_ZLIB=ON \
+ -DBUILD_ZLIB=ON \
+ -DWITH_JPEG=ON \
+ -DBUILD_JPEG=ON \
+ -DWITH_PNG=ON \
+ -DBUILD_PNG=ON \
+ -DWITH_TIFF=ON \
+ -DBUILD_TIFF=ON
+
+make -j
+make install
+```
+
+Among them, `root_path` is the downloaded opencv source code path, and `install_path` is the installation path of opencv. After `make install` is completed, the opencv header file and library file will be generated in this folder for later OCR source code compilation.
+
+
+
+The final file structure under the opencv installation path is as follows.
+
+```
+opencv3/
+|-- bin
+|-- include
+|-- lib
+|-- lib64
+|-- share
+```
+
+### 1.2 Compile or download or the Paddle inference library
+
+* There are 2 ways to obtain the Paddle inference library, described in detail below.
+
+
+#### 1.2.1 Compile from the source code
+* If you want to get the latest Paddle inference library features, you can download the latest code from Paddle github repository and compile the inference library from the source code.
+* You can refer to [Paddle inference library] (https://www.paddlepaddle.org.cn/documentation/docs/en/advanced_guide/inference_deployment/inference/build_and_install_lib_en.html) to get the Paddle source code from github, and then compile To generate the latest inference library. The method of using git to access the code is as follows.
+
+
+```shell
+git clone https://github.com/PaddlePaddle/Paddle.git
+```
+
+* After entering the Paddle directory, the compilation method is as follows.
+
+```shell
+rm -rf build
+mkdir build
+cd build
+
+cmake .. \
+ -DWITH_CONTRIB=OFF \
+ -DWITH_MKL=ON \
+ -DWITH_MKLDNN=ON \
+ -DWITH_TESTING=OFF \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DWITH_INFERENCE_API_TEST=OFF \
+ -DON_INFER=ON \
+ -DWITH_PYTHON=ON
+make -j
+make inference_lib_dist
+```
+
+For more compilation parameter options, please refer to the official website of the Paddle C++ inference library:[https://www.paddlepaddle.org.cn/documentation/docs/en/advanced_guide/inference_deployment/inference/build_and_install_lib_en.html](https://www.paddlepaddle.org.cn/documentation/docs/en/advanced_guide/inference_deployment/inference/build_and_install_lib_en.html).
+
+
+* After the compilation process, you can see the following files in the folder of `build/fluid_inference_install_dir/`.
+
+```
+build/fluid_inference_install_dir/
+|-- CMakeCache.txt
+|-- paddle
+|-- third_party
+|-- version.txt
+```
+
+Among them, `paddle` is the Paddle library required for C++ prediction later, and `version.txt` contains the version information of the current inference library.
+
+
+
+#### 1.2.2 Direct download and installation
+
+* Different cuda versions of the Linux inference library (based on GCC 4.8.2) are provided on the
+[Paddle inference library official website](https://www.paddlepaddle.org.cn/documentation/docs/en/advanced_guide/inference_deployment/inference/build_and_install_lib_en.html). You can view and select the appropriate version of the inference library on the official website.
+
+
+* After downloading, use the following method to uncompress.
+
+```
+tar -xf fluid_inference.tgz
+```
+
+Finally you can see the following files in the folder of `fluid_inference/`.
+
+
+## 2. Compile and run the demo
+
+### 2.1 Export the inference model
+
+* You can refer to [Model inference](../../doc/doc_ch/inference.md),export the inference model. After the model is exported, assuming it is placed in the `inference` directory, the directory structure is as follows.
+
+```
+inference/
+|-- det_db
+| |--model
+| |--params
+|-- rec_rcnn
+| |--model
+| |--params
+```
+
+
+### 2.2 Compile PaddleOCR C++ inference demo
+
+
+* The compilation commands are as follows. The addresses of Paddle C++ inference library, opencv and other Dependencies need to be replaced with the actual addresses on your own machines.
+
+```shell
+sh tools/build.sh
+```
+
+具体地,`tools/build.sh`中内容如下。
+
+```shell
+OPENCV_DIR=your_opencv_dir
+LIB_DIR=your_paddle_inference_dir
+CUDA_LIB_DIR=your_cuda_lib_dir
+CUDNN_LIB_DIR=your_cudnn_lib_dir
+
+BUILD_DIR=build
+rm -rf ${BUILD_DIR}
+mkdir ${BUILD_DIR}
+cd ${BUILD_DIR}
+cmake .. \
+ -DPADDLE_LIB=${LIB_DIR} \
+ -DWITH_MKL=ON \
+ -DDEMO_NAME=ocr_system \
+ -DWITH_GPU=OFF \
+ -DWITH_STATIC_LIB=OFF \
+ -DUSE_TENSORRT=OFF \
+ -DOPENCV_DIR=${OPENCV_DIR} \
+ -DCUDNN_LIB=${CUDNN_LIB_DIR} \
+ -DCUDA_LIB=${CUDA_LIB_DIR} \
+
+make -j
+```
+
+`OPENCV_DIR` is the opencv installation path; `LIB_DIR` is the download (`fluid_inference` folder) or the generated Paddle inference library path (`build/fluid_inference_install_dir` folder); `CUDA_LIB_DIR` is the cuda library file path, in docker; it is `/usr/local/cuda/lib64`; `CUDNN_LIB_DIR` is the cudnn library file path, in docker it is `/usr/lib/x86_64-linux-gnu/`.
+
+
+* After the compilation is completed, an executable file named `ocr_system` will be generated in the `build` folder.
+
+
+### Run the demo
+* Execute the following command to complete the OCR recognition and detection of an image.
+
+```shell
+sh tools/run.sh
+```
+
+The detection results will be shown on the screen, which is as follows.
+
+
+

+
+
+
+### 2.3 Note
+
+* `MKLDNN` is disabled by default for C++ inference (`use_mkldnn` in `tools/config.txt` is set to 0), if you need to use MKLDNN for inference acceleration, you need to modify `use_mkldnn` to 1, and use the latest version of the Paddle source code to compile the inference library. When using MKLDNN for CPU prediction, if multiple images are predicted at the same time, there will be a memory leak problem (the problem is not present if MKLDNN is disabled). The problem is currently being fixed, and the temporary solution is: when predicting multiple pictures, Re-initialize the recognition (`CRNNRecognizer`) and detection class (`DBDetector`) every 30 pictures or so.
diff --git a/deploy/cpp_infer/src/ocr_det.cpp b/deploy/cpp_infer/src/ocr_det.cpp
index cddb0a8d84f4c2b11ecad1b6dc946334c26c46d9..c87b653ceab011ef0593e7fb87358325deaf882b 100644
--- a/deploy/cpp_infer/src/ocr_det.cpp
+++ b/deploy/cpp_infer/src/ocr_det.cpp
@@ -38,6 +38,7 @@ void DBDetector::LoadModel(const std::string &model_dir) {
config.SwitchIrOptim(true);
config.EnableMemoryOptim();
+ config.DisableGlogInfo();
this->predictor_ = CreatePaddlePredictor(config);
}
@@ -106,4 +107,4 @@ void DBDetector::Run(cv::Mat &img,
}
}
-} // namespace PaddleOCR
\ No newline at end of file
+} // namespace PaddleOCR
diff --git a/deploy/cpp_infer/src/ocr_rec.cpp b/deploy/cpp_infer/src/ocr_rec.cpp
index 446f9bc16baabe11fbaa972e3174e036874d7e74..bbd7b9b2269ca776c2433d502893986ebda809c3 100644
--- a/deploy/cpp_infer/src/ocr_rec.cpp
+++ b/deploy/cpp_infer/src/ocr_rec.cpp
@@ -127,6 +127,7 @@ void CRNNRecognizer::LoadModel(const std::string &model_dir) {
config.SwitchIrOptim(true);
config.EnableMemoryOptim();
+ config.DisableGlogInfo();
this->predictor_ = CreatePaddlePredictor(config);
}
@@ -186,4 +187,4 @@ cv::Mat CRNNRecognizer::GetRotateCropImage(const cv::Mat &srcimage,
}
}
-} // namespace PaddleOCR
\ No newline at end of file
+} // namespace PaddleOCR
diff --git a/deploy/cpp_infer/src/postprocess_op.cpp b/deploy/cpp_infer/src/postprocess_op.cpp
index 1df51a8481c5eac487081c8efd4f4afc09f7cf54..59115dc4b2217d5d9099fa5b564540c7179e9b5a 100644
--- a/deploy/cpp_infer/src/postprocess_op.cpp
+++ b/deploy/cpp_infer/src/postprocess_op.cpp
@@ -57,8 +57,12 @@ cv::RotatedRect PostProcessor::UnClip(std::vector> box,
points.emplace_back(soln[j][i].X, soln[j][i].Y);
}
}
- cv::RotatedRect res = cv::minAreaRect(points);
-
+ cv::RotatedRect res;
+ if (points.size() <= 0) {
+ res = cv::RotatedRect(cv::Point2f(0, 0), cv::Size2f(1, 1), 0);
+ } else {
+ res = cv::minAreaRect(points);
+ }
return res;
}
@@ -215,6 +219,9 @@ PostProcessor::BoxesFromBitmap(const cv::Mat pred, const cv::Mat bitmap,
std::vector>> boxes;
for (int _i = 0; _i < num_contours; _i++) {
+ if (contours[_i].size() <= 2) {
+ continue;
+ }
float ssid;
cv::RotatedRect box = cv::minAreaRect(contours[_i]);
auto array = GetMiniBoxes(box, ssid);
@@ -233,6 +240,9 @@ PostProcessor::BoxesFromBitmap(const cv::Mat pred, const cv::Mat bitmap,
// start for unclip
cv::RotatedRect points = UnClip(box_for_unclip, det_db_unclip_ratio);
+ if (points.size.height < 1.001 && points.size.width < 1.001) {
+ continue;
+ }
// end for unclip
cv::RotatedRect clipbox = points;
diff --git a/deploy/hubserving/ocr_det/module.py b/deploy/hubserving/ocr_det/module.py
index e5fac23faff29ff1b407fa5629b39231cce52724..be74306dacf4a3648e3227f11227e6399e6ed2eb 100644
--- a/deploy/hubserving/ocr_det/module.py
+++ b/deploy/hubserving/ocr_det/module.py
@@ -31,7 +31,7 @@ from tools.infer.predict_det import TextDetector
author_email="paddle-dev@baidu.com",
type="cv/text_recognition")
class OCRDet(hub.Module):
- def _initialize(self, use_gpu=False):
+ def _initialize(self, use_gpu=False, enable_mkldnn=False):
"""
initialize with the necessary elements
"""
@@ -51,6 +51,7 @@ class OCRDet(hub.Module):
"Environment Variable CUDA_VISIBLE_DEVICES is not set correctly. If you wanna use gpu, please set CUDA_VISIBLE_DEVICES via export CUDA_VISIBLE_DEVICES=cuda_device_id."
)
cfg.ir_optim = True
+ cfg.enable_mkldnn = enable_mkldnn
self.text_detector = TextDetector(cfg)
diff --git a/deploy/hubserving/ocr_rec/module.py b/deploy/hubserving/ocr_rec/module.py
index cf612dd7376078f1badb41fe8655dccda9d3f05d..846f5437fe3b0a5136bff6c902481f888558d594 100644
--- a/deploy/hubserving/ocr_rec/module.py
+++ b/deploy/hubserving/ocr_rec/module.py
@@ -31,7 +31,7 @@ from tools.infer.predict_rec import TextRecognizer
author_email="paddle-dev@baidu.com",
type="cv/text_recognition")
class OCRRec(hub.Module):
- def _initialize(self, use_gpu=False):
+ def _initialize(self, use_gpu=False, enable_mkldnn=False):
"""
initialize with the necessary elements
"""
@@ -51,6 +51,7 @@ class OCRRec(hub.Module):
"Environment Variable CUDA_VISIBLE_DEVICES is not set correctly. If you wanna use gpu, please set CUDA_VISIBLE_DEVICES via export CUDA_VISIBLE_DEVICES=cuda_device_id."
)
cfg.ir_optim = True
+ cfg.enable_mkldnn = enable_mkldnn
self.text_recognizer = TextRecognizer(cfg)
diff --git a/deploy/hubserving/ocr_system/module.py b/deploy/hubserving/ocr_system/module.py
index bed0c4e6fff372275aa415e60d878fe4dcd7acc4..cb526e1185d8eb623af84ba2451ff38523bbf642 100644
--- a/deploy/hubserving/ocr_system/module.py
+++ b/deploy/hubserving/ocr_system/module.py
@@ -31,7 +31,7 @@ from tools.infer.predict_system import TextSystem
author_email="paddle-dev@baidu.com",
type="cv/text_recognition")
class OCRSystem(hub.Module):
- def _initialize(self, use_gpu=False):
+ def _initialize(self, use_gpu=False, enable_mkldnn=False):
"""
initialize with the necessary elements
"""
@@ -51,7 +51,8 @@ class OCRSystem(hub.Module):
"Environment Variable CUDA_VISIBLE_DEVICES is not set correctly. If you wanna use gpu, please set CUDA_VISIBLE_DEVICES via export CUDA_VISIBLE_DEVICES=cuda_device_id."
)
cfg.ir_optim = True
-
+ cfg.enable_mkldnn = enable_mkldnn
+
self.text_sys = TextSystem(cfg)
def read_images(self, paths=[]):
diff --git a/deploy/ios_demo/download_dependencies.sh b/deploy/ios_demo/download_dependencies.sh
new file mode 100755
index 0000000000000000000000000000000000000000..40a8b3d259c83614c9bd210815fbe2f3bf49a607
--- /dev/null
+++ b/deploy/ios_demo/download_dependencies.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+set -e
+
+OCR_MODEL_URL="https://paddleocr.bj.bcebos.com/deploy/lite/ocr_v1_for_cpu.tar.gz"
+PADDLE_LITE_LIB_URL="https://paddlelite-demo.bj.bcebos.com/libs/ios/paddle_lite_libs_v2_6_0.tar.gz"
+OPENCV3_FRAMEWORK_URL="https://paddlelite-demo.bj.bcebos.com/libs/ios/opencv3.framework.tar.gz"
+
+download_and_extract() {
+ local url="$1"
+ local dst_dir="$2"
+ local tempdir=$(mktemp -d)
+
+ echo "Downloading ${url} ..."
+ curl -L ${url} > ${tempdir}/temp.tar.gz
+ echo "Download ${url} done "
+
+ if [ ! -d ${dst_dir} ];then
+ mkdir -p ${dst_dir}
+ fi
+
+ echo "Extracting ..."
+ tar -zxvf ${tempdir}/temp.tar.gz -C ${dst_dir}
+ echo "Extract done "
+
+ rm -rf ${tempdir}
+}
+
+echo -e "[Download ios ocr demo denpendancy]\n"
+download_and_extract "${OCR_MODEL_URL}" "./ios-demo/ocr_demo/models"
+download_and_extract "${PADDLE_LITE_LIB_URL}" "./ios-demo/ocr_demo"
+download_and_extract "${OPENCV3_FRAMEWORK_URL}" "./ios-demo/ocr_demo"
+echo -e "[done]\n"
diff --git a/deploy/ios_demo/ocr_demo.xcodeproj/project.pbxproj b/deploy/ios_demo/ocr_demo.xcodeproj/project.pbxproj
new file mode 100644
index 0000000000000000000000000000000000000000..4a5fc93e1dd05efff31d01df5cb57cd124bb98fc
--- /dev/null
+++ b/deploy/ios_demo/ocr_demo.xcodeproj/project.pbxproj
@@ -0,0 +1,462 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ A98EA8B624CD8E85B9EFADA1 /* OcrData.m in Sources */ = {isa = PBXBuildFile; fileRef = A98EAD74A71FE136D084F392 /* OcrData.m */; };
+ E0A53559219A832A005A6056 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = E0A53558219A832A005A6056 /* AppDelegate.m */; };
+ E0A5355C219A832A005A6056 /* ViewController.mm in Sources */ = {isa = PBXBuildFile; fileRef = E0A5355B219A832A005A6056 /* ViewController.mm */; };
+ E0A5355F219A832A005A6056 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E0A5355D219A832A005A6056 /* Main.storyboard */; };
+ E0A53561219A832E005A6056 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E0A53560219A832E005A6056 /* Assets.xcassets */; };
+ E0A53564219A832E005A6056 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = E0A53562219A832E005A6056 /* LaunchScreen.storyboard */; };
+ E0A53567219A832E005A6056 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = E0A53566219A832E005A6056 /* main.m */; };
+ E0A53576219A89FF005A6056 /* lib in Resources */ = {isa = PBXBuildFile; fileRef = E0A53574219A89FF005A6056 /* lib */; };
+ E0A5357B219AA3D1005A6056 /* AVFoundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0A5357A219AA3D1005A6056 /* AVFoundation.framework */; };
+ E0A5357D219AA3DF005A6056 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0A5357C219AA3DE005A6056 /* AssetsLibrary.framework */; };
+ E0A5357F219AA3E7005A6056 /* CoreMedia.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E0A5357E219AA3E7005A6056 /* CoreMedia.framework */; };
+ ED37528F24B88737008DEBDA /* opencv2.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ED37528E24B88737008DEBDA /* opencv2.framework */; };
+ ED3752A424B9AD5C008DEBDA /* ocr_clipper.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED37529E24B9AD5C008DEBDA /* ocr_clipper.cpp */; };
+ ED3752A524B9AD5C008DEBDA /* ocr_db_post_process.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED3752A024B9AD5C008DEBDA /* ocr_db_post_process.cpp */; };
+ ED3752A624B9AD5C008DEBDA /* ocr_crnn_process.cpp in Sources */ = {isa = PBXBuildFile; fileRef = ED3752A124B9AD5C008DEBDA /* ocr_crnn_process.cpp */; };
+ ED3752A924B9ADB4008DEBDA /* BoxLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = ED3752A824B9ADB4008DEBDA /* BoxLayer.m */; };
+ ED3752AC24B9B118008DEBDA /* Helpers.m in Sources */ = {isa = PBXBuildFile; fileRef = ED3752AA24B9B118008DEBDA /* Helpers.m */; };
+ ED3752B324B9D4C6008DEBDA /* ch_det_mv3_db_opt.nb in Resources */ = {isa = PBXBuildFile; fileRef = ED3752B124B9D4C6008DEBDA /* ch_det_mv3_db_opt.nb */; };
+ ED3752B424B9D4C6008DEBDA /* ch_rec_mv3_crnn_opt.nb in Resources */ = {isa = PBXBuildFile; fileRef = ED3752B224B9D4C6008DEBDA /* ch_rec_mv3_crnn_opt.nb */; };
+ ED3752BC24B9DAD7008DEBDA /* ocr.png in Resources */ = {isa = PBXBuildFile; fileRef = ED3752BA24B9DAD6008DEBDA /* ocr.png */; };
+ ED3752BD24B9DAD7008DEBDA /* label_list.txt in Resources */ = {isa = PBXBuildFile; fileRef = ED3752BB24B9DAD7008DEBDA /* label_list.txt */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXFileReference section */
+ A98EAD74A71FE136D084F392 /* OcrData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OcrData.m; sourceTree = ""; };
+ A98EAD76E0669B2BFB628B48 /* OcrData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OcrData.h; sourceTree = ""; };
+ E0846DF2230BC93900031405 /* timer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = timer.h; sourceTree = ""; };
+ E0A18EDD219C03000015DC15 /* face.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = face.jpg; sourceTree = ""; };
+ E0A53554219A8329005A6056 /* paddle-lite-ocr.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "paddle-lite-ocr.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+ E0A53557219A832A005A6056 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; };
+ E0A53558219A832A005A6056 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; };
+ E0A5355A219A832A005A6056 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; };
+ E0A5355B219A832A005A6056 /* ViewController.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ViewController.mm; sourceTree = ""; };
+ E0A5355E219A832A005A6056 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ E0A53560219A832E005A6056 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ E0A53563219A832E005A6056 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ E0A53565219A832E005A6056 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ E0A53566219A832E005A6056 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; };
+ E0A53574219A89FF005A6056 /* lib */ = {isa = PBXFileReference; lastKnownFileType = folder; path = lib; sourceTree = ""; };
+ E0A5357A219AA3D1005A6056 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; };
+ E0A5357C219AA3DE005A6056 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; };
+ E0A5357E219AA3E7005A6056 /* CoreMedia.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreMedia.framework; path = System/Library/Frameworks/CoreMedia.framework; sourceTree = SDKROOT; };
+ ED37528E24B88737008DEBDA /* opencv2.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = opencv2.framework; path = ocr_demo/opencv2.framework; sourceTree = ""; };
+ ED37529E24B9AD5C008DEBDA /* ocr_clipper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ocr_clipper.cpp; sourceTree = ""; };
+ ED37529F24B9AD5C008DEBDA /* ocr_db_post_process.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ocr_db_post_process.h; sourceTree = ""; };
+ ED3752A024B9AD5C008DEBDA /* ocr_db_post_process.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ocr_db_post_process.cpp; sourceTree = ""; };
+ ED3752A124B9AD5C008DEBDA /* ocr_crnn_process.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ocr_crnn_process.cpp; sourceTree = ""; };
+ ED3752A224B9AD5C008DEBDA /* ocr_clipper.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ocr_clipper.hpp; sourceTree = ""; };
+ ED3752A324B9AD5C008DEBDA /* ocr_crnn_process.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ocr_crnn_process.h; sourceTree = ""; };
+ ED3752A724B9ADB4008DEBDA /* BoxLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BoxLayer.h; sourceTree = ""; };
+ ED3752A824B9ADB4008DEBDA /* BoxLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BoxLayer.m; sourceTree = ""; };
+ ED3752AA24B9B118008DEBDA /* Helpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Helpers.m; sourceTree = ""; };
+ ED3752AB24B9B118008DEBDA /* Helpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Helpers.h; sourceTree = ""; };
+ ED3752B124B9D4C6008DEBDA /* ch_det_mv3_db_opt.nb */ = {isa = PBXFileReference; lastKnownFileType = file; path = ch_det_mv3_db_opt.nb; sourceTree = "