基于PaddleMobile的Android图像识别应用开发¶
项目概述¶
本文档详细介绍了如何使用百度PaddleMobile框架在Android平台上实现图像识别功能。通过将训练好的深度学习模型集成到移动应用中,我们可以在本地设备上完成图像分类任务,无需依赖云端服务,从而提升识别速度和用户隐私安全性。
环境准备¶
安装必要工具¶
- Android Studio (推荐3.0以上版本)
- Java Development Kit (JDK 8)
- Python 3.x (用于模型转换和量化)
下载PaddleMobile源码¶
git clone https://github.com/PaddlePaddle/paddle-mobile.git
编译PaddleMobile库¶
使用Docker编译¶
- 安装Docker
apt-get install docker.io
- 构建Docker镜像
cd paddle-mobile
docker build -t paddle-mobile:dev - < Dockerfile
- 运行容器并编译
docker run -it -v $PWD:/paddle-mobile paddle-mobile:dev
cd paddle-mobile
cmake -DCMAKE_TOOLCHAIN_FILE=tools/toolchains/arm-android-neon.cmake
make
exit
- 获取编译产物
ls build/libpaddle-mobile.so
使用Ubuntu交叉编译¶
- 安装NDK
wget https://dl.google.com/android/repository/android-ndk-r17b-linux-x86_64.zip
unzip android-ndk-r17b-linux-x86_64.zip
export NDK_ROOT="/path/to/android-ndk-r17b"
- 编译PaddleMobile
cd paddle-mobile/tools
sh build.sh android
创建Android项目¶
项目结构设置¶
-
创建新项目
- 包名:com.example.paddlemobile1
- 最低SDK版本: API 16+ -
添加权限
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- 创建目录结构
- 在main目录下创建assets/paddle_models文件夹,存放模型文件
- 添加jniLibs/armeabi-v7a文件夹,放入编译好的libpaddle-mobile.so
核心Java代码实现¶
1. ImageRecognition.java (JNI接口)¶
package com.example.paddlemobile1;
public class ImageRecognition {
// 设置线程数
public static native void setThread(int threadCount);
// 加载模型
public static native boolean load(String modelDir);
// 预测图像
public static native float[] predictImage(float[] buf, int[] dims);
// 加载优化模型
public static native boolean loadQualified(String modelDir);
// 加载组合模型
public static native boolean loadCombined(String modelPath, String paramPath);
static {
System.loadLibrary("paddle-mobile");
}
}
2. PhotoUtil.java (图像处理工具)¶
package com.example.paddlemobile1;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.util.Log;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class PhotoUtil {
// 启动相机
public static Uri startCamera(Activity activity, int requestCode) {
// 实现相机启动逻辑
}
// 打开相册
public static void usePhoto(Activity activity, int requestCode) {
// 实现相册打开逻辑
}
// 获取图片路径
public static String getPathFromUri(Context context, Uri uri) {
// 实现Uri转路径逻辑
}
// 图片压缩与预处理
public static float[] getScaledMatrix(Bitmap bitmap, int desWidth, int desHeight) {
// 实现图片缩放和数据转换
float[] data = new float[3 * desWidth * desHeight];
// ... 数据处理逻辑 ...
return data;
}
}
3. MainActivity.java (主界面逻辑)¶
package com.example.paddlemobile1;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
public class MainActivity extends AppCompatActivity {
private static final int USE_PHOTO = 1001;
private static final int START_CAMERA = 1002;
private ImageView showImage;
private TextView resultText;
private String assetsPath = "paddle_models";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化视图
Button usePhotoBtn = findViewById(R.id.use_photo);
Button startCameraBtn = findViewById(R.id.start_camera);
showImage = findViewById(R.id.show_image);
resultText = findViewById(R.id.result_text);
// 请求权限
requestPermissions();
// 复制模型文件到SD卡
copyModelFromAssets();
// 加载模型
loadModel();
// 按钮点击事件
usePhotoBtn.setOnClickListener(v -> PhotoUtil.usePhoto(this, USE_PHOTO));
startCameraBtn.setOnClickListener(v -> {
Uri uri = PhotoUtil.startCamera(this, START_CAMERA);
Glide.with(this).load(uri).into(showImage);
});
}
// 从assets复制模型文件到SD卡
private void copyModelFromAssets() {
// 实现模型文件复制逻辑
}
// 加载PaddleMobile模型
private void loadModel() {
String modelPath = Environment.getExternalStorageDirectory() +
"/paddle_models/googlenet";
boolean loadSuccess = ImageRecognition.load(modelPath);
if (loadSuccess) {
Log.d("PaddleMobile", "模型加载成功");
} else {
Log.e("PaddleMobile", "模型加载失败");
}
}
// 处理图片选择结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == USE_PHOTO) {
Uri uri = data.getData();
String imagePath = PhotoUtil.getPathFromUri(this, uri);
predictImage(imagePath);
} else if (requestCode == START_CAMERA) {
Uri uri = PhotoUtil.startCamera(this, START_CAMERA);
String imagePath = PhotoUtil.getPathFromUri(this, uri);
predictImage(imagePath);
}
}
}
// 预测图像
private void predictImage(String imagePath) {
Bitmap bitmap = PhotoUtil.getScaledBitmap(imagePath);
float[] inputData = PhotoUtil.getScaledMatrix(bitmap, 224, 224);
int[] dims = {1, 3, 224, 224};
long start = System.currentTimeMillis();
float[] result = ImageRecognition.predictImage(inputData, dims);
long end = System.currentTimeMillis();
// 处理预测结果
int maxIndex = getMaxIndex(result);
String resultText = "预测结果: " + maxIndex + "\n概率: " + result[maxIndex] +
"\n耗时: " + (end - start) + "ms";
this.resultText.setText(resultText);
}
// 获取概率最大的结果索引
private int getMaxIndex(float[] result) {
int maxIndex = 0;
float maxValue = result[0];
for (int i = 1; i < result.length; i++) {
if (result[i] > maxValue) {
maxValue = result[i];
maxIndex = i;
}
}
return maxIndex;
}
// 请求权限
private void requestPermissions() {
// 实现权限请求逻辑
}
}
布局文件 (activity_main.xml)¶
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/show_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/btn_ll" />
<TextView
android:id="@+id/result_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@id/btn_ll"
android:layout_margin="16dp"
android:text="预测结果将显示在这里" />
<LinearLayout
android:id="@+id/btn_ll"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<Button
android:id="@+id/use_photo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="相册" />
<Button
android:id="@+id/start_camera"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="拍照" />
</LinearLayout>
</RelativeLayout>
模型准备与优化¶
模型转换¶
使用PaddleMobile提供的工具将训练好的模型转换为移动端格式:
python tools/convert.py --model ./models --output ./paddle_models
模型量化 (可选)¶
cd paddle-mobile/tools/quantification
python quantify.py --model ./googlenet --output ./googlenet_quantized
编译与运行¶
- 添加依赖
implementation 'com.github.bumptech.glide:glide:4.3.1'
-
配置Gradle
- 添加NDK支持
- 设置CPU架构为armeabi-v7a -
运行应用
- 选择”相册”或”拍照”功能
- 应用会自动加载模型并显示预测结果
性能优化建议¶
-
模型优化
- 使用模型量化减小模型体积
- 选择轻量级网络结构(如MobileNet)
- 移除不必要的层和连接 -
图像处理优化
- 采用适当的图像压缩策略
- 预加载常用模型到内存
- 使用多线程加速预测 -
代码优化
- 异步处理预测任务
- 实现模型缓存机制
- 合理释放资源
参考资料¶
注意事项¶
- 确保模型文件路径正确
- 注意权限请求和动态权限管理
- 处理不同设备上的硬件差异
- 优化预测速度和内存使用
通过以上步骤,你可以构建一个高效的Android图像识别应用,实现本地深度学习模型的部署与推理。