基于Paddle Lite在Android手机上实现图像分类
原文博客:Doi技術(shù)團(tuán)隊(duì)
鏈接地址:https://blog.doiduoyi.com/authors/1584446358138
初心:記錄優(yōu)秀的Doi技術(shù)團(tuán)隊(duì)學(xué)習(xí)經(jīng)歷
本文鏈接:基于Paddle Lite在Android手機(jī)上實(shí)現(xiàn)圖像分類(lèi)
前言
Paddle Lite是飛槳基于Paddle Mobile全新升級(jí)推出的端側(cè)推理引擎,在多硬件、多平臺(tái)以及硬件混合調(diào)度的支持上更加完備,為包括手機(jī)在內(nèi)的端側(cè)場(chǎng)景的AI應(yīng)用提供高效輕量的推理能力,有效解決手機(jī)算力和內(nèi)存限制等問(wèn)題,致力于推動(dòng)AI應(yīng)用更廣泛的落地。
本教程源碼地址:https://github.com/yeyupiaoling/ClassificationForAndroid/tree/master/PaddleLiteClassification
模型轉(zhuǎn)換
Paddle Lite使用的是PaddlePaddle保存的預(yù)測(cè)模型,如果不了解PaddlePaddle的模型保存,可以參考《模型的保存與使用》這篇文章。下面簡(jiǎn)單介紹一下保存模型的方式。通過(guò)使用fluid.io.save_inference_model()接口可以保存預(yù)測(cè)模型,預(yù)測(cè)模型值保存推所需的網(wǎng)絡(luò),不會(huì)保存損失函數(shù)等。當(dāng)使用model_filename和params_filename指定參數(shù)之后,保存的預(yù)測(cè)模型只有兩個(gè)文件,這種稱(chēng)為合并模型,否則會(huì)以網(wǎng)絡(luò)結(jié)構(gòu)命名將大量的參數(shù)文件保存在dirname指定的路徑下,這種叫做非合并模型。例如通過(guò)以下的代碼片段保存的預(yù)測(cè)模型為model和params,這兩個(gè)模型將會(huì)用于下一步的模型轉(zhuǎn)換。
import paddle.fluid as fluid# 定義網(wǎng)絡(luò) image = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32') label = fluid.layers.data(name='label', shape=[1], dtype='int64') feeder = fluid.DataFeeder(feed_list=[image, label], place=fluid.CPUPlace()) predict = fluid.layers.fc(input=image, size=10, act='softmax')loss = fluid.layers.cross_entropy(input=predict, label=label) avg_loss = fluid.layers.mean(loss)exe = fluid.Executor(fluid.CPUPlace()) exe.run(fluid.default_startup_program())# 數(shù)據(jù)輸入及訓(xùn)練過(guò)程# 保存預(yù)測(cè)模型 fluid.io.save_inference_model(dirname="mobilenet_v2/",feeded_var_names=[image.name],target_vars=[predict],executor=exe,model_filename="model", params_filename="params")opt轉(zhuǎn)換
使用fluid.io.save_inference_model()接口可以保存預(yù)測(cè)模型并不能直接使用,還需要通過(guò)opt工具轉(zhuǎn)換,這個(gè)工具可以下載Paddle Lite預(yù)編譯的,或者通過(guò)源碼編譯,opt下載地址:https://paddle-lite.readthedocs.io/zh/latest/user_guides/release_lib.html#opt,關(guān)于如何編譯opt請(qǐng)看下一部分。
通過(guò)以下命令即即可把預(yù)測(cè)模型轉(zhuǎn)變成Paddle Lite使用的模型,其中輸出的mobilenet_v2.nb就是所需的模型文件,因?yàn)檗D(zhuǎn)換之后,模型可以在valid_targets指定的環(huán)境上加速預(yù)測(cè),所以變得非常牛B,因此后綴名為nb(開(kāi)個(gè)玩笑)。
./opt \--model_file=mobilenet_v2/model \--param_file=mobilenet_v2/params \--optimize_out_type=naive_buffer \--optimize_out=mobilenet_v2 \--valid_targets=arm opencl \--record_tailoring_info=false上面參數(shù)的說(shuō)明如下表所示,其中需要關(guān)注的是valid_targets參數(shù),要看模型用著上面設(shè)備上,通過(guò)指定backend可以使用更好的加速方式。有些讀取可能會(huì)出現(xiàn)這樣的疑問(wèn),上面使用的是合并的模型,沒(méi)合并的模型怎樣用呢,其實(shí)很簡(jiǎn)單,只有設(shè)置--model_dir,忽略--model_file和--param_file就可以了。
| –model_dir | 待優(yōu)化的PaddlePaddle模型(非combined形式)的路徑 |
| –model_file | 待優(yōu)化的PaddlePaddle模型(combined形式)的網(wǎng)絡(luò)結(jié)構(gòu)文件路徑。 |
| –param_file | 待優(yōu)化的PaddlePaddle模型(combined形式)的權(quán)重文件路徑。 |
| –optimize_out_type | 輸出模型類(lèi)型,目前支持兩種類(lèi)型:protobuf和naive_buffer,其中naive_buffer是一種更輕量級(jí)的序列化/反序列化實(shí)現(xiàn)。若您需要在mobile端執(zhí)行模型預(yù)測(cè),請(qǐng)將此選項(xiàng)設(shè)置為naive_buffer。默認(rèn)為protobuf。 |
| –optimize_out | 優(yōu)化模型的輸出路徑。 |
| –valid_targets | 指定模型可執(zhí)行的backend,默認(rèn)為arm。目前可支持x86、arm、opencl、npu、xpu,可以同時(shí)指定多個(gè)backend(以空格分隔),Model Optimize Tool將會(huì)自動(dòng)選擇最佳方式。如果需要支持華為NPU(Kirin 810/990 Soc搭載的達(dá)芬奇架構(gòu)NPU),應(yīng)當(dāng)設(shè)置為npu, arm。 |
| –record_tailoring_info | 當(dāng)使用 根據(jù)模型裁剪庫(kù)文件 功能時(shí),則設(shè)置該選項(xiàng)為true,以記錄優(yōu)化后模型含有的kernel和OP信息,默認(rèn)為false。 |
源碼編譯opt
上面所使用的opt工具是通過(guò)下載得到的,如果讀者喜歡折騰,可以嘗試自行源碼編譯編譯,首先是環(huán)境搭建,環(huán)境搭建有兩種方式,第一種是使用Docker,第二種是本地搭建環(huán)境。
在以上的環(huán)境中編譯opt工具,執(zhí)行以下命令即可完成編譯,編譯完成之后,在build.opt/lite/api/下的可執(zhí)行文件opt。
cd Paddle-Lite && ./lite/tools/build.sh build_optimize_toolPaddle Lite的Android預(yù)測(cè)庫(kù)
Paddle Lite的Android預(yù)測(cè)庫(kù)也可以通過(guò)下載預(yù)編譯的,或者通過(guò)源碼編譯。下載地址為:,注意本教程使用的是靜態(tài)庫(kù)的方式,而且使用的是圖像識(shí)別的,所以需要選擇的下載庫(kù)為with_extra=ON,arm_stl=c++_static,with_cv=ON的armv7和armv8庫(kù)。下載解壓之后得到的目錄結(jié)構(gòu)如下,其中我們所需的在java的jar和so動(dòng)態(tài)庫(kù),注意32位的so動(dòng)態(tài)庫(kù)放在Android的armeabi-v7a目錄,64位的so動(dòng)態(tài)庫(kù)放在Android的arm64-v8a目錄,jar包只取一個(gè)就好。
inference_lite_lib.android.armv8/ |-- cxx C++ 預(yù)測(cè)庫(kù)和頭文件 | |-- include C++ 頭文件 | | |-- paddle_api.h | | |-- paddle_image_preprocess.h | | |-- paddle_lite_factory_helper.h | | |-- paddle_place.h | | |-- paddle_use_kernels.h | | |-- paddle_use_ops.h | | `-- paddle_use_passes.h | `-- lib C++預(yù)測(cè)庫(kù) | |-- libpaddle_api_light_bundled.a C++靜態(tài)庫(kù) | `-- libpaddle_light_api_shared.so C++動(dòng)態(tài)庫(kù) |-- java Java預(yù)測(cè)庫(kù) | |-- jar | | `-- PaddlePredictor.jar | |-- so | | `-- libpaddle_lite_jni.so | `-- src |-- demo C++和Java示例代碼 | |-- cxx C++ 預(yù)測(cè)庫(kù)demo | `-- java Java 預(yù)測(cè)庫(kù)demo同樣如果讀者喜歡折騰,可以嘗試自行源碼編譯編譯,在上面編譯opt工具時(shí)搭建的環(huán)境上編譯Paddle Lite的Android預(yù)測(cè)庫(kù)。在Paddle Lite源碼的根目錄下執(zhí)行以下兩條命令編譯Paddle Lite的Android預(yù)測(cè)庫(kù)。
./lite/tools/build_android.sh --arch=armv7 --with_extra=ON ./lite/tools/build_android.sh --arch=armv8 --with_extra=ON完成編譯之后,會(huì)在Paddle-Lite/build.lite.android.armv7.gcc/inference_lite_lib.android.armv7和Paddle-Lite/build.lite.android.armv8.gcc/inference_lite_lib.android.armv8目錄生成所以的jar和動(dòng)態(tài)庫(kù),所在位置和使用查看上面的下載Android預(yù)測(cè)庫(kù)的介紹。
開(kāi)發(fā)Android項(xiàng)目
創(chuàng)建一個(gè)Android項(xiàng)目,在app/libs目錄下存放上一步編譯得到的PaddlePredictor.jar,并添加到app庫(kù)中,添加方式可以是選擇這個(gè)jar包,右鍵選擇add as Librarys,或者在app/build.gradle添加以下代碼結(jié)果都是一樣的。
implementation files('libs\\PaddlePredictor.jar')然后在app/src/main/jniLibs下存放下載或者編譯得到的動(dòng)態(tài)庫(kù),最好把32位和64為的動(dòng)態(tài)庫(kù)libpaddle_lite_jni.so都添加進(jìn)去,分別是armeabi-v7a目錄和arm64-v8a目錄。
復(fù)制轉(zhuǎn)換的預(yù)測(cè)模型到app/src/main/assets目錄下,還有類(lèi)別的標(biāo)簽,每一行對(duì)應(yīng)一個(gè)標(biāo)簽名稱(chēng)。
Paddle Lite工具
編寫(xiě)一個(gè)PaddleLiteClassification工具類(lèi),關(guān)于Paddle Lite的操作都在這里完成,如加載模型、預(yù)測(cè)。在構(gòu)造方法中,通過(guò)參數(shù)傳遞的模型路徑加載模型,在加載模型的時(shí)候配置預(yù)測(cè)信息,如預(yù)測(cè)時(shí)使用的線(xiàn)程數(shù)量,使用計(jì)算資源的模式,要注意的是圖像預(yù)處理的縮放比例scale,均值inputMean和標(biāo)準(zhǔn)差inputStd,因?yàn)樵谟?xùn)練的時(shí)候圖像預(yù)處理可能不一樣的,有些讀者出現(xiàn)在電腦上準(zhǔn)確率很高,但在手機(jī)上準(zhǔn)確率很低,多數(shù)情況下就是這個(gè)圖像預(yù)處理做得不對(duì)。
public class PaddleLiteClassification {private static final String TAG = PaddleLiteClassification.class.getName();private PaddlePredictor paddlePredictor;private Tensor inputTensor;private long[] inputShape = new long[]{1, 3, 224, 224};private static float[] scale = new float[]{1.0f / 255.0f, 1.0f / 255.0f, 1.0f / 255.0f};private static float[] inputMean = new float[]{0.485f, 0.456f, 0.406f};private static float[] inputStd = new float[]{0.229f, 0.224f, 0.225f};private static final int NUM_THREADS = 4;/*** @param modelPath model path*/public PaddleLiteClassification(String modelPath) throws Exception {File file = new File(modelPath);if (!file.exists()) {throw new Exception("model file is not exists!");}try {MobileConfig config = new MobileConfig();config.setModelFromFile(modelPath);config.setThreads(NUM_THREADS);config.setPowerMode(PowerMode.LITE_POWER_HIGH);paddlePredictor = PaddlePredictor.createPaddlePredictor(config);inputTensor = paddlePredictor.getInput(0);inputTensor.resize(inputShape);} catch (Exception e) {e.printStackTrace();throw new Exception("load model fail!");}}為了兼容圖片路徑和Bitmap格式的圖片預(yù)測(cè),這里創(chuàng)建了兩個(gè)重載方法,它們都是通過(guò)調(diào)用predict()
public float[] predictImage(String image_path) throws Exception {if (!new File(image_path).exists()) {throw new Exception("image file is not exists!");}FileInputStream fis = new FileInputStream(image_path);Bitmap bitmap = BitmapFactory.decodeStream(fis);float[] result = predictImage(bitmap);if (bitmap.isRecycled()) {bitmap.recycle();}return result;}public float[] predictImage(Bitmap bitmap) throws Exception {return predict(bitmap);}這里創(chuàng)建一個(gè)獲取最大概率值,并把下標(biāo)返回的方法,其實(shí)就是獲取概率最大的預(yù)測(cè)標(biāo)簽。
public static int getMaxResult(float[] result) {float probability = 0;int r = 0;for (int i = 0; i < result.length; i++) {if (probability < result[i]) {probability = result[i];r = i;}}return r;}在數(shù)據(jù)輸入之前,需要對(duì)數(shù)據(jù)進(jìn)行預(yù)處理,輸入的數(shù)據(jù)是一個(gè)浮點(diǎn)數(shù)組,但是目前輸入的是一個(gè)Bitmap的圖片,所以需要把Bitmap轉(zhuǎn)換為浮點(diǎn)數(shù)組,在轉(zhuǎn)換過(guò)程中需要對(duì)圖像做相應(yīng)的預(yù)處理,如乘比例,減均值,除以方差。為了避免輸入的圖像過(guò)大,圖像預(yù)處理變慢,通常在元數(shù)據(jù)預(yù)處理之前,需要對(duì)圖像進(jìn)行壓縮,使用getScaleBitmap()方法可以壓縮等比例壓縮圖像。
private static 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;// 轉(zhuǎn)成RGB通道順序dataBuf[rIndex] = (((clr & 0x00ff0000) >> 16) * scale[0] - inputMean[0]) / inputStd[0];dataBuf[gIndex] = (((clr & 0x0000ff00) >> 8) * scale[1] - inputMean[1]) / inputStd[1];dataBuf[bIndex] = (((clr & 0x000000ff)) * scale[2] - inputMean[2]) / inputStd[2];}if (bm.isRecycled()) {bm.recycle();}return dataBuf;}private Bitmap getScaleBitmap(Bitmap bitmap) {int bmpWidth = bitmap.getWidth();int bmpHeight = bitmap.getHeight();int size = (int) inputShape[2];float scaleWidth = (float) size / bitmap.getWidth();float scaleHeight = (float) size / bitmap.getHeight();Matrix matrix = new Matrix();matrix.postScale(scaleWidth, scaleHeight);return Bitmap.createBitmap(bitmap, 0, 0, bmpWidth, bmpHeight, matrix, true);}這個(gè)方法就是Paddle Lite執(zhí)行預(yù)測(cè)的最后一步,使用inputTensor.setData(inputData)輸入預(yù)測(cè)圖像數(shù)據(jù),通過(guò)執(zhí)行paddlePredictor.run()對(duì)輸入的數(shù)據(jù)進(jìn)行預(yù)測(cè)并得到預(yù)測(cè)結(jié)果,預(yù)測(cè)結(jié)果通過(guò)paddlePredictor.getOutput(0)提前出來(lái),最后通過(guò)解析獲取到最大的概率的預(yù)測(cè)標(biāo)簽。到這里Paddle Lite的工具就完成了。
private float[] predict(Bitmap bmp) throws Exception {Bitmap b = getScaleBitmap(bmp);float[] inputData = getScaledMatrix(b, (int) inputShape[2], (int) inputShape[3]);b.recycle();bmp.recycle();inputTensor.setData(inputData);try {paddlePredictor.run();} catch (Exception e) {throw new Exception("predict image fail! log:" + e);}Tensor outputTensor = paddlePredictor.getOutput(0);float[] result = outputTensor.getFloatData();Log.d(TAG, Arrays.toString(result));int l = getMaxResult(result);return new float[]{l, result[l]};}選擇圖片預(yù)測(cè)
本教程會(huì)有兩個(gè)頁(yè)面,一個(gè)是選擇圖片進(jìn)行預(yù)測(cè)的頁(yè)面,另一個(gè)是使用相機(jī)實(shí)時(shí)預(yù)測(cè)并顯示預(yù)測(cè)結(jié)果。以下為activity_main.xml的代碼,通過(guò)按鈕選擇圖片,并在該頁(yè)面顯示圖片和預(yù)測(cè)結(jié)果。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"><ImageViewandroid:id="@+id/image_view"android:layout_width="match_parent"android:layout_height="400dp" /><TextViewandroid:id="@+id/result_text"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@id/image_view"android:text="識(shí)別結(jié)果"android:textSize="16sp" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_alignParentBottom="true"android:orientation="horizontal"><Buttonandroid:id="@+id/select_img_btn"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="選擇照片" /><Buttonandroid:id="@+id/open_camera"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:text="實(shí)時(shí)預(yù)測(cè)" /></LinearLayout></RelativeLayout>在MainActivity.java中,進(jìn)入到頁(yè)面我們就要先加載模型,我們是把模型放在Android項(xiàng)目的assets目錄的,但是Tensorflow Lite并不建議直接在assets讀取模型,所以我們需要把模型復(fù)制到一個(gè)緩存目錄,然后再?gòu)木彺婺夸浖虞d模型,同時(shí)還有讀取標(biāo)簽名,標(biāo)簽名稱(chēng)按照訓(xùn)練的label順序存放在assets的label_list.txt,以下為實(shí)現(xiàn)代碼。
classNames = Utils.ReadListFromFile(getAssets(), "label_list.txt"); String classificationModelPath = getCacheDir().getAbsolutePath() + File.separator + "mobilenet_v2.nb"; Utils.copyFileFromAsset(MainActivity.this, "mobilenet_v2.nb", classificationModelPath); try {paddleLiteClassification = new PaddleLiteClassification(classificationModelPath);Toast.makeText(MainActivity.this, "模型加載成功!", Toast.LENGTH_SHORT).show(); } catch (Exception e) {Toast.makeText(MainActivity.this, "模型加載失敗!", Toast.LENGTH_SHORT).show();e.printStackTrace();finish(); }添加兩個(gè)按鈕點(diǎn)擊事件,可以選擇打開(kāi)相冊(cè)讀取圖片進(jìn)行預(yù)測(cè),或者打開(kāi)另一個(gè)Activity進(jìn)行調(diào)用攝像頭實(shí)時(shí)識(shí)別。
Button selectImgBtn = findViewById(R.id.select_img_btn); Button openCamera = findViewById(R.id.open_camera); imageView = findViewById(R.id.image_view); textView = findViewById(R.id.result_text); selectImgBtn.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 打開(kāi)相冊(cè)Intent intent = new Intent(Intent.ACTION_PICK);intent.setType("image/*");startActivityForResult(intent, 1);} }); openCamera.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {// 打開(kāi)實(shí)時(shí)拍攝識(shí)別頁(yè)面Intent intent = new Intent(MainActivity.this, CameraActivity.class);startActivity(intent);} });當(dāng)打開(kāi)相冊(cè)選擇照片之后,回到原來(lái)的頁(yè)面,在下面這個(gè)回調(diào)方法中獲取選擇圖片的Uri,通過(guò)Uri可以獲取到圖片的絕對(duì)路徑。如果Android8以上的設(shè)備獲取不到圖片,需要在AndroidManifest.xml配置文件中的application添加android:requestLegacyExternalStorage="true"。拿到圖片路徑之后,調(diào)用PaddleLiteClassification類(lèi)中的predictImage()方法預(yù)測(cè)并獲取預(yù)測(cè)值,在頁(yè)面上顯示預(yù)測(cè)的標(biāo)簽、對(duì)應(yīng)標(biāo)簽的名稱(chēng)、概率值和預(yù)測(cè)時(shí)間。
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {super.onActivityResult(requestCode, resultCode, data);String image_path;if (resultCode == Activity.RESULT_OK) {if (requestCode == 1) {if (data == null) {Log.w("onActivityResult", "user photo data is null");return;}Uri image_uri = data.getData();image_path = getPathFromURI(MainActivity.this, image_uri);try {// 預(yù)測(cè)圖像FileInputStream fis = new FileInputStream(image_path);imageView.setImageBitmap(BitmapFactory.decodeStream(fis));long start = System.currentTimeMillis();float[] result = paddleLiteClassification.predictImage(image_path);long end = System.currentTimeMillis();String show_text = "預(yù)測(cè)結(jié)果標(biāo)簽:" + (int) result[0] +"\n名稱(chēng):" + classNames.get((int) result[0]) +"\n概率:" + result[1] +"\n時(shí)間:" + (end - start) + "ms";textView.setText(show_text);} catch (Exception e) {e.printStackTrace();}}} }上面獲取的Uri可以通過(guò)下面這個(gè)方法把Url轉(zhuǎn)換成絕對(duì)路徑。
// get photo from Uri public static String getPathFromURI(Context context, Uri uri) {String result;Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);if (cursor == null) {result = uri.getPath();} else {cursor.moveToFirst();int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);result = cursor.getString(idx);cursor.close();}return result; }攝像頭實(shí)時(shí)預(yù)測(cè)
在調(diào)用相機(jī)實(shí)時(shí)預(yù)測(cè)我就不再介紹了,原理都差不多,具體可以查看https://github.com/yeyupiaoling/ClassificationForAndroid/tree/master/PaddleLiteClassification中的源代碼。核心代碼如下,創(chuàng)建一個(gè)子線(xiàn)程,子線(xiàn)程中不斷從攝像頭預(yù)覽的AutoFitTextureView上獲取圖像,并執(zhí)行預(yù)測(cè),并在頁(yè)面上顯示預(yù)測(cè)的標(biāo)簽、對(duì)應(yīng)標(biāo)簽的名稱(chēng)、概率值和預(yù)測(cè)時(shí)間。每一次預(yù)測(cè)完成之后都立即獲取圖片繼續(xù)預(yù)測(cè),只要預(yù)測(cè)速度夠快,就可以看成實(shí)時(shí)預(yù)測(cè)。
private Runnable periodicClassify =new Runnable() {@Overridepublic void run() {synchronized (lock) {if (runClassifier) {// 開(kāi)始預(yù)測(cè)前要判斷相機(jī)是否已經(jīng)準(zhǔn)備好if (getApplicationContext() != null && mCameraDevice != null && tfLiteClassificationUtil != null) {predict();}}}if (mInferThread != null && mInferHandler != null && mCaptureHandler != null && mCaptureThread != null) {mInferHandler.post(periodicClassify);}}};// 預(yù)測(cè)相機(jī)捕獲的圖像 private void predict() {// 獲取相機(jī)捕獲的圖像Bitmap bitmap = mTextureView.getBitmap();try {// 預(yù)測(cè)圖像long start = System.currentTimeMillis();float[] result = paddleLiteClassification.predictImage(bitmap);long end = System.currentTimeMillis();String show_text = "預(yù)測(cè)結(jié)果標(biāo)簽:" + (int) result[0] +"\n名稱(chēng):" + classNames.get((int) result[0]) +"\n概率:" + result[1] +"\n時(shí)間:" + (end - start) + "ms";textView.setText(show_text);} catch (Exception e) {e.printStackTrace();} }本項(xiàng)目中使用的了讀取圖片的權(quán)限和打開(kāi)相機(jī)的權(quán)限,所以不要忘記在AndroidManifest.xml添加以下權(quán)限申請(qǐng)。
<uses-permission android:name="android.permission.CAMERA"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>如果是Android 6 以上的設(shè)備還要?jiǎng)討B(tài)申請(qǐng)權(quán)限。
// check had permissionprivate boolean hasPermission() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {return checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED &&checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;} else {return true;}}// request permissionprivate void requestPermission() {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);}}選擇圖片識(shí)別效果圖:
相機(jī)實(shí)時(shí)識(shí)別效果圖:
總結(jié)
以上是生活随笔為你收集整理的基于Paddle Lite在Android手机上实现图像分类的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 手机串号IMEI的国际查询网站及说明
- 下一篇: 骗子广告联盟_骗子把我的脸变成了Goog