PyTorch模型安卓部署流程(NCNN)全流程实战(2)代码详细解析

代码来源PyTorch模型安卓部署流程(NCNN)全流程实战(1)

至于为什么要备注,因为我基础不好,就得一点一点来
适合和我一样的慢羊羊学习

项目整体结构
如图

1.布局文件 不解析了比较简单最简单的线性布局main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">

    <Button
        android:id="@+id/buttonImage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="选图" />
    <Button
        android:id="@+id/buttonDetect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="识别-cpu" />
    <Button
        android:id="@+id/buttonDetectGPU"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="识别-gpu" />
    </LinearLayout>

    <TextView
        android:id="@+id/infoResult"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="" />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1" />

</LinearLayout>

2.资源文件string.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">squeezencnn</string>
</resources>

在Android开发中,资源文件(通常以.xml结尾)用于定义静态内容,如字符串、颜色、尺寸等。res/values/strings.xml 文件通常包含应用程序使用的所有字符串资源。
存了个APP名字的字符串

3.asset中存放的是训练的模型.bin和.charm文件
在这里插入图片描述
4.SqueezeNcnn.java 代码解析

// Tencent is pleased to support the open source community by making ncnn available.
 腾讯很高兴通过开放源代码的方式支持开发者社区,使得 ncnn 可用。
// Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
/// 版权所有 © 2017 THL A29 Limited,腾讯公司。保留所有权利。
// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
 根据 BSD 3-Clause 许可证(“许可证”),除非符合许可证的条款,否则您不得使用本文件
// https://opensource.org/licenses/BSD-3-Clause
 您可以在以下链接获取许可证的副本:
//
 https://opensource.org/licenses/BSD-3-Clause
//
 除非适用法律要求或书面同意,根据许可证分发的软件以“原样”分发,不附带任何形式的担保或条件。
//这个块提供了 squeezencnn 库的许可信息,说明该库依据BSD 3-Clause许可发布。它还包括了完整许可文本的链接。
 详细的许可证条款请参阅许可证文档。
// 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.
//看起来你提供了一份ImageNet类标签清单,每个标签对应不同类型的动物或鸟类。
// 这些标签通常用于图像识别任务,训练模型将图像分类到这些类别中。以下是你列出的一些标签的详细解释
//每个标签对应特定的鱼类、鸟类或动物物种。这些标签用于在机器学习和计算机视觉应用中识别和分类这些生物的图像。
package com.tencent.squeezencnn;

import android.content.res.AssetManager;
import android.graphics.Bitmap;

public class SqueezeNcnn
    //定义了一个名为 SqueezeNcnn 的公共类。
{
    public native boolean Init(AssetManager mgr);
//native 方法声明表示这些方法由本地代码(通常是C或C++)提供实现。这些方法是Java代码中的占位符,将调用本地代码。
    public native String Detect(Bitmap bitmap, boolean use_gpu);
//Init:使用 AssetManager 对象(mgr)初始化某些内容。
//Detect:使用 Bitmap 对象(bitmap)和布尔标志(use_gpu)执行某种检测或分析。
    //Bitmap(位图)是一种图像文件格式,它由像素阵列组成,每个像素都有特定的位置和颜色信息。
// 位图图像由像素阵列组成,每个像素都有自己的颜色信息。
    static {
        System.loadLibrary("squeezencnn");
        //这个静态代码块确保在类初始化时加载名为 squeezencnn 的本地库。
        // 这个库包含了在 SqueezeNcnn 类中声明的 native 方法的实现。
    }
}

总结来说,SqueezeNcnn 是一个Java类,通过 Init 和 Detect 方法与本地代码(C或C++)进行交互。它与Android的 AssetManager 和 Bitmap 类紧密集成,用于执行神经网络推理或其他类似任务,这些任务由 squeezencnn 本地库支持。

5.squeezencnn_jni.cpp的作用 这里都是C++我不懂

// Tencent is pleased to support the open source community by making ncnn available.
//
// Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// https://opensource.org/licenses/BSD-3-Clause
//
// 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.

#include <android/asset_manager_jni.h>
#include <android/bitmap.h>
#include <android/log.h>

#include <jni.h>

#include <string>
#include <vector>

// ncnn
#include "net.h"
#include "benchmark.h"

#include "squeezenet_v1.1.id.h"

static std::vector<std::string> squeezenet_words;
static ncnn::Net squeezenet;
static ncnn::Net squeezenet_gpu;

static std::vector<std::string> split_string(const std::string& str, const std::string& delimiter)
{
    std::vector<std::string> strings;

    std::string::size_type pos = 0;
    std::string::size_type prev = 0;
    while ((pos = str.find(delimiter, prev)) != std::string::npos)
    {
        strings.push_back(str.substr(prev, pos - prev));
        prev = pos + 1;
    }

    // To get the last substring (or only, if delimiter is not found)
    strings.push_back(str.substr(prev));

    return strings;
}

extern "C" {

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "JNI_OnLoad");

    ncnn::create_gpu_instance();

    return JNI_VERSION_1_4;
}

JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
{
    __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "JNI_OnUnload");

    ncnn::destroy_gpu_instance();
}

// public native boolean Init(AssetManager mgr);
JNIEXPORT jboolean JNICALL Java_com_tencent_squeezencnn_SqueezeNcnn_Init(JNIEnv* env, jobject thiz, jobject assetManager)
{
    AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);

    // init param
    {
        int ret = squeezenet.load_param_bin(mgr, "squeezenet_v1.1.param.bin");
        if (ret != 0)
        {
            __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "load_param_bin failed");
            return JNI_FALSE;
        }
    }

    // init bin
    {
        int ret = squeezenet.load_model(mgr, "squeezenet_v1.1.bin");
        if (ret != 0)
        {
            __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "load_model failed");
            return JNI_FALSE;
        }
    }

    // use vulkan compute
    if (ncnn::get_gpu_count() != 0)
    {
        squeezenet_gpu.opt.use_vulkan_compute = true;

        {
            int ret = squeezenet_gpu.load_param_bin(mgr, "squeezenet_v1.1.param.bin");
            if (ret != 0)
            {
                __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "load_param_bin failed");
                return JNI_FALSE;
            }
        }
        {
            int ret = squeezenet_gpu.load_model(mgr, "squeezenet_v1.1.bin");
            if (ret != 0)
            {
                __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "load_model failed");
                return JNI_FALSE;
            }
        }
    }

    // init words
    {
        AAsset* asset = AAssetManager_open(mgr, "synset_words.txt", AASSET_MODE_BUFFER);
        if (!asset)
        {
            __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "open synset_words.txt failed");
            return JNI_FALSE;
        }

        int len = AAsset_getLength(asset);

        std::string words_buffer;
        words_buffer.resize(len);
        int ret = AAsset_read(asset, (void*)words_buffer.data(), len);

        AAsset_close(asset);

        if (ret != len)
        {
            __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "read synset_words.txt failed");
            return JNI_FALSE;
        }

        squeezenet_words = split_string(words_buffer, "\n");
    }

    return JNI_TRUE;
}

// public native String Detect(Bitmap bitmap, boolean use_gpu);
JNIEXPORT jstring JNICALL Java_com_tencent_squeezencnn_SqueezeNcnn_Detect(JNIEnv* env, jobject thiz, jobject bitmap, jboolean use_gpu)
{
    if (use_gpu == JNI_TRUE && ncnn::get_gpu_count() == 0)
    {
        return env->NewStringUTF("no vulkan capable gpu");
    }

    double start_time = ncnn::get_current_time();

    AndroidBitmapInfo info;
    AndroidBitmap_getInfo(env, bitmap, &info);
    int width = info.width;
    int height = info.height;
    if (width != 227 || height != 227)
        return NULL;
    if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
        return NULL;

    // ncnn from bitmap
    ncnn::Mat in = ncnn::Mat::from_android_bitmap(env, bitmap, ncnn::Mat::PIXEL_BGR);

    // squeezenet
    std::vector<float> cls_scores;
    {
        const float mean_vals[3] = {104.f, 117.f, 123.f};
        in.substract_mean_normalize(mean_vals, 0);

        ncnn::Extractor ex = use_gpu ? squeezenet_gpu.create_extractor() : squeezenet.create_extractor();

        ex.input(squeezenet_v1_1_param_id::BLOB_data, in);

        ncnn::Mat out;
        ex.extract(squeezenet_v1_1_param_id::BLOB_prob, out);

        cls_scores.resize(out.w);
        for (int j=0; j<out.w; j++)
        {
            cls_scores[j] = out[j];
        }
    }

    // return top class
    int top_class = 0;
    float max_score = 0.f;
    for (size_t i=0; i<cls_scores.size(); i++)
    {
        float s = cls_scores[i];
//         __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "%d %f", i, s);
        if (s > max_score)
        {
            top_class = i;
            max_score = s;
        }
    }

    const std::string& word = squeezenet_words[top_class];
    char tmp[32];
    sprintf(tmp, "%.3f", max_score);
    std::string result_str = std::string(word.c_str() + 10) + " = " + tmp;

    // +10 to skip leading n03179701
    jstring result = env->NewStringUTF(result_str.c_str());

    double elasped = ncnn::get_current_time() - start_time;
    __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "%.2fms   detect", elasped);

    return result;
}

}

这段代码的主要作用是在Android平台上初始化并加载SqueezeNet模型,为后续的推理任务做准备。它使用了JNI来与Java层交互,从应用的Assets中加载模型文件。同时,它还支持在GPU上运行NCNN库,提高推理速度和效率。这段代码通过 JNI 接口实现了一个基于 SqueezeNet 模型的图像分类器,支持在 Android 设备上使用 GPU 进行加速。它包括了模型初始化、输入图像处理、推理过程、结果处理和性能记录等关键步骤,是一个典型的深度学习模型在移动设备上部署的实现示例。

5.MainActivity.java

// Tencent is pleased to support the open source community by making ncnn available.
//
// Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved.
//
// Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// https://opensource.org/licenses/BSD-3-Clause
//
// 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.tencent.squeezencnn;

import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import java.io.FileNotFoundException;

public class MainActivity extends Activity
{
    private static final int SELECT_IMAGE = 1;

    private TextView infoResult;
    private ImageView imageView;
    private Bitmap yourSelectedImage = null;

    private SqueezeNcnn squeezencnn = new SqueezeNcnn();

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        boolean ret_init = squeezencnn.Init(getAssets());
        //在 onCreate 方法中,首先调用 squeezencnn.Init(getAssets()) 来初始化 SqueezeNcnn 对象,该对象用于进行图像检测。
        if (!ret_init)
        {
            Log.e("MainActivity", "squeezencnn Init failed");
        }

        infoResult = (TextView) findViewById(R.id.infoResult);
        imageView = (ImageView) findViewById(R.id.imageView);
        //然后通过 findViewById 方法找到布局文件中定义的 TextView 和 ImageView 控件,
        // 并将它们分别赋值给 infoResult 和 imageView 成员变量。


        Button buttonImage = (Button) findViewById(R.id.buttonImage);
        buttonImage.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                Intent i = new Intent(Intent.ACTION_PICK);
                i.setType("image/*");
                startActivityForResult(i, SELECT_IMAGE);
                //当 "buttonImage" 被点击时,会启动一个图片选择的意图,并通过 startActivityForResult 方法来等待选择结果。
            }
        });

                //通过 findViewById 方法找到 "buttonImage" 和 "buttonDetect" 两个按钮,并为它们设置点击事件监听器。

        Button buttonDetect = (Button) findViewById(R.id.buttonDetect);
        buttonDetect.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                if (yourSelectedImage == null)
                    return;

                String result = squeezencnn.Detect(yourSelectedImage, false);
                //当 "buttonDetect" 被点击时,会调用 squeezencnn.Detect(yourSelectedImage, false) 方法来对选择的图片进行检测,
                // 并将结果显示在 infoResult 中。
                if (result == null)
                {
                    infoResult.setText("detect failed");
                }
                else
                {
                    infoResult.setText(result);
                }
            }
        });

        Button buttonDetectGPU = (Button) findViewById(R.id.buttonDetectGPU);
        //找到布局文件中的 buttonDetectGPU 按钮。
        //为按钮设置点击事件监听器,当按钮被点击时:
        buttonDetectGPU.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                if (yourSelectedImage == null)
                    return;
                //如果没有选择图片 (yourSelectedImage 为空),则直接返回。
                String result = squeezencnn.Detect(yourSelectedImage, true);
                //调用 squeezencnn.Detect(yourSelectedImage, true) 方法进行 GPU 加速的图像检测。
                if (result == null)
                {
                    infoResult.setText("detect failed");
                    //如果检测结果为空,显示 "detect failed";否则显示检测结果。
                }
                else
                {
                    infoResult.setText(result);
                }
            }
        });
    }







    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        super.onActivityResult(requestCode, resultCode, data);
        //当用户选择图片并返回时,onActivityResult 方法会被调用。
        if (resultCode == RESULT_OK && null != data) {
            //检查结果码是否为 RESULT_OK 且数据不为空。
            Uri selectedImage = data.getData();
            //获取选定图片的 URI 并尝试解码图片:
            try
            {
                if (requestCode == SELECT_IMAGE) {
                    //如果请求码为 SELECT_IMAGE,调用 decodeUri 方法进行解码。
                    Bitmap bitmap = decodeUri(selectedImage);

                    Bitmap rgba = bitmap.copy(Bitmap.Config.ARGB_8888, true);

                    // resize to 227x227
                    yourSelectedImage = Bitmap.createScaledBitmap(rgba, 227, 227, false);
                    //将解码后的图片复制为 Bitmap.Config.ARGB_8888 格式,并将其缩放至 227x227 像素。
                    rgba.recycle();
                    //将原始的 rgba 图像资源释放(回收)。
                    imageView.setImageBitmap(bitmap);//在 imageView 中显示原始解码后的图片。
                }
            }
            catch (FileNotFoundException e)
            {
                Log.e("MainActivity", "FileNotFoundException");
                return;
            }
        }
    }







    private Bitmap decodeUri(Uri selectedImage) throws FileNotFoundException
    {
        // Decode image size
        //定义 decodeUri 方法,用于通过 URI 解码图片。
        //首先解码图片尺寸:
        
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(getContentResolver().openInputStream(selectedImage), null, o);
        //设置 BitmapFactory.Options 的 inJustDecodeBounds 为 true 以获取图片尺寸而不加载图片。
        
        
        // The new size we want to scale to
        final int REQUIRED_SIZE = 400;
        //确定目标缩放大小 REQUIRED_SIZE 为 400。
        
        
        // Find the correct scale value. It should be the power of 2.
        //计算缩放比例,使宽度和高度都不超过 REQUIRED_SIZE,且缩放值为 2 的幂次。
        int width_tmp = o.outWidth, height_tmp = o.outHeight;
        int scale = 1;
        while (true) {
            if (width_tmp / 2 < REQUIRED_SIZE
               || height_tmp / 2 < REQUIRED_SIZE) {
                break;
            }
            width_tmp /= 2;
            height_tmp /= 2;
            scale *= 2;
        }
        //使用计算出的缩放比例解码图片,返回解码后的位图。
        // Decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize = scale;
        return BitmapFactory.decodeStream(getContentResolver().openInputStream(selectedImage), null, o2);
    }


    //这段代码主要实现了从设备中选择图片、对图片进行解码和缩放,
    // 并通过 SqueezeNcnn 对象进行图像检测的功能。同时提供了 CPU 和 GPU 两种检测方式供用户选择。
}

我觉得更好的代码理解能更好的添加自己的模型
希望有帮助吧

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值