【Android Studio】将 ResNet50(pt格式->未量化) 模型部署在APP上

一、Android Studio 简单介绍

什么是Android Studio

Android Studio是谷歌官方推出的一款用于开发Android应用程序的集成开发环境(IDE)。它基于IntelliJ IDEA开发,集成了强大的代码编辑器、模拟器、调试器等功能,为开发者提供了一站式的开发工具,帮助快速高效地开发Android应用程序。

Android Studio的特点

  1. 集成模拟器:Android Studio内置了模拟器,可以在计算机上模拟运行Android设备,方便开发者进行应用程序的测试和调试。

  2. 强大的代码编辑器:Android Studio集成了功能强大的代码编辑器,支持语法高亮、自动补全、代码提示等功能,提高了代码编写效率。

  3. 调试工具:Android Studio内置了调试器,可以帮助开发者在开发过程中进行调试,快速找到并解决程序中的错误。

  4. 版本控制:Android Studio支持常见的版本控制系统(如Git),方便开发者进行代码版本控制和团队协作。

  5. 插件支持:Android Studio支持各种插件,可以根据开发者的需求扩展其功能。

  6. 多平台支持:Android Studio不仅支持Windows、Mac和Linux等多个操作系统,还支持多种屏幕分辨率和语言环境。

如何安装和启动Android Studio

  1. 下载Android Studio安装包(可以从官网下载)。

  2. 运行安装包,按照提示完成安装过程。

  3. 启动Android Studio,选择语言和版本,进入欢迎界面。

  4. 选择新建项目或打开已有项目,开始开发之旅。

如何使用Android Studio进行应用程序开发

  1. 创建新项目:在Android Studio中,选择“新建项目”,填写项目名称、包名等信息,选择应用程序类型和最小SDK版本等参数。

  2. 设计界面:在布局编辑器中,拖拽控件、设置属性,完成界面设计。

  3. 编写代码:使用Java或Kotlin语言编写代码,实现应用程序的功能。可以使用自动补全、语法高亮等功能提高编码效率。

  4. 运行调试:通过模拟器或连接真实设备进行应用程序的测试和调试。可以在Android Studio中实时查看日志、调试代码,提高开发效率。

  5. 打包发布:完成应用程序的开发后,可以使用Android Studio的打包工具将应用程序打包成APK文件,发布到应用市场或其他渠道。

二、Android Studio 创建项目

Gradle 版本

SDK & JDK 版本

三、Android Studio 界面设置

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/photoButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="Photo"
        android:textAllCaps="false"
        app:layout_constraintEnd_toStartOf="@+id/detectButton"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/detectButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="Detect"
        android:textAllCaps="false"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/photoButton"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/resultText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="120dp"
        android:textSize="18sp"
        android:background="#80000000"
        android:textColor="@android:color/white"
        app:layout_constraintTop_toTopOf="parent"
        tools:ignore="MissingConstraints"
        android:gravity="center"/>

</androidx.constraintlayout.widget.ConstraintLayout>

<ImageView
    android:id="@+id/imageView"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:layout_marginTop="180dp"
    android:background="#FFFFFF"
    app:layout_constraintDimensionRatio="1:1"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="1.0"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<ProgressBar
    android:id="@+id/progressBar"
    android:visibility="invisible"
    android:layout_marginTop="20dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="0.498"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/imageView"/>

四、模型转换

# -*- coding:utf-8 -*-
# @author: 牧锦程

import argparse
from model.model import ResNetModel
import torch


def get_parser():
    parser = argparse.ArgumentParser()

    # model
    parser.add_argument("--pretrained", type=bool, default=None, help="use pretrained model")
    parser.add_argument("--selectLayer", type=int, default=50, choices=[18, 34, 50, 101, 152], help="resnet model layer number")
    parser.add_argument("--n_classes", type=int, default=131, help="data category number")

    # save
    parser.add_argument("--save_pt", type=str, default="./weights/model.pt", help="save label dir")
 
    # infer model
    parser.add_argument("--infer_path", type=str, default="./weights/bset.ckpt", help='val dataset train and save best model')

    return parser


def load_model(opts):
    model = ResNetModel(weights_flag=opts.pretrained, selectLayer=opts.selectLayer, classNum=opts.n_classes)
    model.load_state_dict(torch.load(opts.infer_path, map_location='cpu')['state_dict'])
    model.eval()
    return model
    


if __name__ == "__main__":
    opts = get_parser().parse_args()
    model = load_model(opts)
    
    # 将模型保存为.pt文件
    torch.jit.save(torch.jit.script(model), opts.save_pt)
    

注意:在这里我使用的 torch 的版本为 1.10.0。这里的转换的版本要与Android studio 中依赖的 pytorch_android 和 pytorch_android_torchvision 版本一致。如果不一致就会导致加载失败。

五、Android Studio 关键代码

模型加载

module = Module.load(assetFilePath(modelName));

类别文件读取

HashMap<Integer, String> labelMap = new HashMap<>();

try (BufferedReader br = new BufferedReader(new FileReader(labelPath))) {
    String line;
    while ((line = br.readLine()) != null) {
        line = line.trim();
        line = line.substring(1, line.length() - 1); // 去掉大括号

        if (line.startsWith("{") || line.endsWith("}")) {
            continue;
        }
        String[] pairs = line.split(", ");
        for (String pair : pairs) {
            String[] keyValue = pair.split(": ");
            int key = Integer.parseInt(keyValue[0]);
            String value = keyValue[1].replaceAll("'", "").trim();
            labelMap.put(key, value);
        }
    }
} catch (IOException e) {
    e.printStackTrace();
}

处理

// 修改为固定尺寸
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, 100, 100, false);

// 将图片转换为tensor
final Tensor inputTensor = TensorImageUtils.bitmapToFloat32Tensor(resizedBitmap,
                                TensorImageUtils.TORCHVISION_NORM_MEAN_RGB, 
                                TensorImageUtils.TORCHVISION_NORM_STD_RGB, 
                                MemoryFormat.CHANNELS_LAST);

模型推理

// 进行推理
final Tensor outputTensor = module.forward(IValue.from(inputTensor)).toTensor();

推理结果

// 将结果处理成列表
final float[] scores = outputTensor.getDataAsFloatArray();

// 选取列表中最大的概率的一个值
float maxScore = -Float.MAX_VALUE;
int maxScoreIdx = -1;
for (int i = 0; i < scores.length; i++) {
    if (scores[i] > maxScore) {
        maxScore = scores[i];
        maxScoreIdx = i;
    }
}

// 获取到实际的类名
// Assuming labelData is a Map<String, String>
String className = labelData.get(maxScoreIdx);

六、Android Studio 真机测试

Logo

聚焦RDK与具身智能,汇集机器人开发教程、开源案例和技术交流

更多推荐