diff --git a/demo/android/PaddleMobile_Android/.gitignore b/demo/android/PaddleMobile_Android/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..39fb081a42a86ccf8f9cf99dbccc8bdf7c828bce --- /dev/null +++ b/demo/android/PaddleMobile_Android/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/demo/android/PaddleMobile_Android/app/.gitignore b/demo/android/PaddleMobile_Android/app/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc --- /dev/null +++ b/demo/android/PaddleMobile_Android/app/.gitignore @@ -0,0 +1 @@ + diff --git a/demo/android/PaddleMobile_Android/app/build.gradle b/demo/android/PaddleMobile_Android/app/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..3d61efb83a083909a0038886469e1c7fce218e8c --- /dev/null +++ b/demo/android/PaddleMobile_Android/app/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 21 + defaultConfig { + applicationId "com.baidu.paddle" + minSdkVersion 15 + targetSdkVersion 21 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.android.support:appcompat-v7:21.0.3' +} diff --git a/demo/android/PaddleMobile_Android/app/proguard-rules.pro b/demo/android/PaddleMobile_Android/app/proguard-rules.pro new file mode 100644 index 0000000000000000000000000000000000000000..f1b424510da51fd82143bc74a0a801ae5a1e2fcd --- /dev/null +++ b/demo/android/PaddleMobile_Android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/demo/android/PaddleMobile_Android/app/src/main/AndroidManifest.xml b/demo/android/PaddleMobile_Android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..b9bf81dcb9fffb855d067cc7df1633ebcf586432 --- /dev/null +++ b/demo/android/PaddleMobile_Android/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/demo/android/PaddleMobile_Android/app/src/main/java/com/baidu/paddle/MainActivity.java b/demo/android/PaddleMobile_Android/app/src/main/java/com/baidu/paddle/MainActivity.java new file mode 100755 index 0000000000000000000000000000000000000000..febe816681d3845a61c5a8b40630e82ac9b4ea95 --- /dev/null +++ b/demo/android/PaddleMobile_Android/app/src/main/java/com/baidu/paddle/MainActivity.java @@ -0,0 +1,408 @@ +/* + * Copyright (c) 2016 Baidu, Inc. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and + * to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of + * the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO + * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +package com.baidu.paddle; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Bundle; +import android.os.Environment; + +import android.provider.MediaStore; + +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; + +import static android.graphics.Color.blue; +import static android.graphics.Color.green; +import static android.graphics.Color.red; +import static com.baidu.paddle.MainActivity.TYPE.googlenet; + + +public class MainActivity extends Activity { + public static final int TAKE_PHOTO_REQUEST_CODE = 1001; + + private Context mContext = null; + + private int inputSize = 224; + + enum TYPE { + googlenet + } + + private TYPE type = googlenet; + private ImageView imageView; + private TextView tvSpeed; + private Button button; + private Bitmap bmp; + + static { + try { + System.loadLibrary("paddle-mobile"); + + } catch (SecurityException e) { + e.printStackTrace(); + + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + + } catch (NullPointerException e) { + e.printStackTrace(); + + } + + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mContext = this; + setContentView(R.layout.main_activity); + init(); + } + + private void init() { + imageView = (ImageView) findViewById(R.id.imageView); + tvSpeed = (TextView) findViewById(R.id.tv_speed); + button = (Button) findViewById(R.id.button); + button.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (!isHasSdCard()) { + Toast.makeText(mContext, R.string.sdcard_not_available, + Toast.LENGTH_LONG).show(); + return; + } + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + // save pic in sdcard + Uri imageUri = Uri.fromFile(getTempImage()); + intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); + startActivityForResult(intent, TAKE_PHOTO_REQUEST_CODE); + + } + }); + Button bt_load = (Button) findViewById(R.id.bt_load); + bt_load.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + String assetPath = "pml_demo"; + String sdcardPath = Environment.getExternalStorageDirectory() + + File.separator + assetPath + File.separator + type; + PML.load(sdcardPath); + + } + }); + Button bt_clear = (Button) findViewById(R.id.bt_clear); + bt_clear.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + + PML.clear(); + + + } + }); + String assetPath = "pml_demo"; + String sdcardPath = Environment.getExternalStorageDirectory() + + File.separator + assetPath; + copyFilesFromAssets(this, assetPath, sdcardPath); + + + } + + public void copyFilesFromAssets(Context context, String oldPath, String newPath) { + try { + String[] fileNames = context.getAssets().list(oldPath); + if (fileNames.length > 0) { + // directory + File file = new File(newPath); + file.mkdirs(); + // copy recursivelyC + for (String fileName : fileNames) { + copyFilesFromAssets(context, oldPath + "/" + fileName, + newPath + "/" + fileName); + } + } else { + // file + InputStream is = context.getAssets().open(oldPath); + FileOutputStream fos = new FileOutputStream(new File(newPath)); + byte[] buffer = new byte[1024]; + int byteCount; + while ((byteCount = is.read(buffer)) != -1) { + fos.write(buffer, 0, byteCount); + } + fos.flush(); + is.close(); + fos.close(); + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public File getTempImage() { + if (Environment.getExternalStorageState().equals( + Environment.MEDIA_MOUNTED)) { + File tempFile = new File(Environment.getExternalStorageDirectory(), "temp.jpg"); + try { + tempFile.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + + return tempFile; + } + return null; + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch (requestCode) { + case TAKE_PHOTO_REQUEST_CODE: + if (resultCode == RESULT_OK) { + DetectionTask detectionTask = new DetectionTask(); + detectionTask.execute(getTempImage().getPath()); + } + break; + default: + break; + } + } + + /** + * draw rect on imageView + * + * @param bitmap + * @param predicted + * @param viewWidth + * @param viewHeight + */ + private void drawRect(Bitmap bitmap, float[] predicted, int viewWidth, int viewHeight) { + + Canvas canvas = new Canvas(bitmap); + canvas.drawBitmap(bitmap, 0, 0, null); + if (type == googlenet) { + Paint paint = new Paint(); + paint.setColor(Color.RED); + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(3.0f); + float x1 = 0; + float x2 = 0; + float y1 = 0; + float y2 = 0; + + // the googlenet result sequence is (left top right top bottom) + x1 = (predicted[0] * viewWidth / 224); + y1 = (predicted[1] * viewHeight / 224); + x2 = (predicted[2] * viewWidth / 224); + y2 = (predicted[3] * viewHeight / 224); + + + canvas.drawRect(x1, y1, x2, y2, paint); + } + + + imageView.setImageBitmap(bitmap); + + } + + float getMaxIndex(float[] predicted) { + float max = 0; + int index = 0; + for (int i = 0; i < predicted.length; i++) { + if (predicted[i] > max) { + max = predicted[i]; + index = i; + } + } + return index; + } + + public float[] getScaledMatrix(Bitmap bitmap, int desWidth, + int desHeight) { + float[] dataBuf = new float[3 * desWidth * desHeight]; + int rIndex; + int gIndex; + int bIndex; + int[] pixels = new int[desWidth * desHeight]; + Bitmap bm = Bitmap.createScaledBitmap(bitmap, desWidth, desHeight, false); + bm.getPixels(pixels, 0, desWidth, 0, 0, desWidth, desHeight); + int j = 0; + int k = 0; + for (int i = 0; i < pixels.length; i ++) { + int clr = pixels[i]; + j = i / desHeight; + k = i % desWidth; + rIndex = j * desWidth + k; + gIndex = rIndex + desHeight * desWidth; + bIndex = gIndex + desHeight * desWidth; + dataBuf[rIndex] = (float)((clr & 0x00ff0000)>> 16) -148; + dataBuf[gIndex] = (float)((clr & 0x0000ff00)>> 8) - 148; + dataBuf[bIndex] = (float)((clr & 0x000000ff)) -148; + + } + if (bm.isRecycled()) { + bm.recycle(); + } + return dataBuf; + + + } + /** + * check whether sdcard is mounted + * + * @return + */ + public boolean isHasSdCard() { + if (Environment.getExternalStorageState().equals( + Environment.MEDIA_MOUNTED)) { + return true; + } else { + return false; + } + } + + public void dumpData(float[] results, String filename) { + try { + File writename = new File(filename); + writename.createNewFile(); + BufferedWriter out = new BufferedWriter(new FileWriter(writename)); + for (float result : results) { + out.write(result + " "); + } + out.flush(); + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + + /** + * scale bitmap in case of OOM + * + * @param ctx + * @param filePath + * @return + */ + public Bitmap getScaleBitmap(Context ctx, String filePath) { + BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inJustDecodeBounds = true; + BitmapFactory.decodeFile(filePath, opt); + + int bmpWidth = opt.outWidth; + int bmpHeight = opt.outHeight; + + int maxSize = 500; + + opt.inSampleSize = 1; + while (true) { + if (bmpWidth / opt.inSampleSize < maxSize || bmpHeight / opt.inSampleSize < maxSize) { + break; + } + opt.inSampleSize *= 2; + } + opt.inJustDecodeBounds = false; + Bitmap bmp = BitmapFactory.decodeFile(filePath, opt); + return bmp; + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + Log.d("mdl", "mdl clear"); + // clear mdl + PML.clear(); + + } + + class DetectionTask extends AsyncTask { + private long time; + + public DetectionTask() { + super(); + } + + @Override + protected void onPreExecute() { + super.onPreExecute(); + if (type == googlenet) { + inputSize = 224; + } + } + + @Override + protected void onPostExecute(float[] result) { + super.onPostExecute(result); + try { + Bitmap src = Bitmap.createScaledBitmap(bmp, imageView.getWidth(), + imageView.getHeight(), false); + drawRect(src, result, imageView.getWidth(), imageView.getHeight()); + tvSpeed.setText("detection cost:" + time + "ms"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + protected void onProgressUpdate(Void... values) { + super.onProgressUpdate(values); + } + + @Override + protected void onCancelled() { + super.onCancelled(); + } + + @Override + protected float[] doInBackground(String... strings) { + bmp = getScaleBitmap(mContext, strings[0]); + float[] inputData = getScaledMatrix(bmp, inputSize, inputSize); + float[] result = null; + try { + long start = System.currentTimeMillis(); + result = PML.predict(inputData); + long end = System.currentTimeMillis(); + time = end - start; + + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + } +} diff --git a/demo/android/PaddleMobile_Android/app/src/main/java/com/baidu/paddle/PML.java b/demo/android/PaddleMobile_Android/app/src/main/java/com/baidu/paddle/PML.java new file mode 100644 index 0000000000000000000000000000000000000000..7649d4c081223bace01b806d1eb7dca57129ed7c --- /dev/null +++ b/demo/android/PaddleMobile_Android/app/src/main/java/com/baidu/paddle/PML.java @@ -0,0 +1,23 @@ +package com.baidu.paddle; + +public class PML { + /** + * Load + * @param modelPath + * @return + */ + public static native boolean load(String modelPath); + + + /** + * object detection + * + * @param buf + * @return + */ + public static native float[] predict(float[] buf); + + + public static native void clear(); + +} diff --git a/demo/android/PaddleMobile_Android/app/src/main/res/drawable/ic_launcher_background.xml b/demo/android/PaddleMobile_Android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..d5fccc538c179838bfdce779c26eebb4fa0b5ce9 --- /dev/null +++ b/demo/android/PaddleMobile_Android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/android/PaddleMobile_Android/app/src/main/res/layout/main_activity.xml b/demo/android/PaddleMobile_Android/app/src/main/res/layout/main_activity.xml new file mode 100644 index 0000000000000000000000000000000000000000..6653b182adbfc6d82d50d313db8c88789c0b027b --- /dev/null +++ b/demo/android/PaddleMobile_Android/app/src/main/res/layout/main_activity.xml @@ -0,0 +1,55 @@ + + + + +