zoukankan      html  css  js  c++  java
  • MACE(3)-----工程化

    作者:十岁的小男孩

    QQ:929994365

    能下者,上。

    前言

      本文是MACE的第三步即MACE环境编译出来的库在Android工程中的使用。在第一篇博文中通过mace官方提供的安卓工程进行调试,本文将其精简,只关心其数据流的逻辑过程。该工程功能是mace的demo物体识别,即传入一张图片,模型识别预测将结果在桌面显示。本人萌新一枚,学习安卓有一月多了,工程漏洞百出,望相互学习。下一篇博文会对mace做一个全面的总结。本部分的学习需要掌握JNI/NDK技术,若有问题浏览前面文章。

      MACE(1)-----环境搭建:https://www.cnblogs.com/missidiot/p/9480033.html

      MACE(2)-----模型编译:https://www.cnblogs.com/missidiot/p/9509831.html

      JNI/NDK:https://www.cnblogs.com/missidiot/p/9716902.html

    经过mace环境编译出文件如下:

    此处应该有图!!! 

    给Android studio配置NDK版本,其版本与编译版本应一致,本文是r-16b。

    新建工程,勾选c++支持选项框。

    其c++标准库选择c++11。

     以上工程构建完毕。

    Android studio2.2版本以上采用的是CMake编译,加载库在CMakeLists.txt文件中配置。在MainActivity中加载库,如下:

    库的名称自己更改:比如本文改为"mace_jni",相对应的在CMakeLists.txt文件中修改名称和创建对应的cpp文件,即mace_jni.cpp,如下图:

    在MainActivity中加载库;

    static {
            System.loadLibrary("mace_jni");
        }

    在CMakeLists.txt文件中修改;(刚开始文件如下)

    cmake_minimum_required(VERSION 3.4.1)
    
    add_library(
                 mace_jni
    
                 SHARED
    
                 src/main/cpp/mace_jni.cpp )
    
    find_library(
                  log-lib
    
                  log )
    
    target_link_libraries(
                           mace_jni
    
                           ${log-lib} )

    CMakeLists最终文件如下:(加载编译出的a库)

    cmake_minimum_required(VERSION 3.4.1)
    
    #set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/../app/libs/${ANDROID_ABI})
    
    # 这块路径有点问题
    include_directories(${CMAKE_SOURCE_DIR}/)
    include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/public)
    
    # libmace.a是mace编译生成的,这块以后要改的在这块
    set(mace_lib ${CMAKE_SOURCE_DIR}/src/main/cpp/lib/arm64-v8a/libmace.a)
    # set(mace_lib ${CMAKE_SOURCE_DIR}/src/main/cpp/lib/armeabi-v7a/libmace.a)
    
    # mobilneet.a也是mace环境编译生成的,后期如果生成的话要改在这里
    set(mobilenet_lib ${CMAKE_SOURCE_DIR}/src/main/cpp/model/arm64-v8a/mobilenet.a)
    # set(mobilenet_lib ${CMAKE_SOURCE_DIR}/src/main/cpp/model/armeabi-v7a/mobilenet.a)
    
    add_library (mace_lib STATIC IMPORTED)
    set_target_properties(mace_lib PROPERTIES IMPORTED_LOCATION ${mace_lib})
    
    add_library (mobilenet_lib STATIC IMPORTED)
    set_target_properties(mobilenet_lib PROPERTIES IMPORTED_LOCATION ${mobilenet_lib})
    
    
    add_library( # Sets the name of the library.
                 mace_jni
    
                 SHARED
    
                 src/main/cpp/mace_jni.cpp
    
    
                 )
    find_library(
                  log-lib
                  log )
    
    target_link_libraries( # Specifies the target library.
                            mace_jni
                            mace_lib
                            mobilenet_lib
                           ${log-lib} )

    创建mace_jni.cpp文件,实现native方法。

    在java包下新建JniUtils.java,将native方法的声明放在其中,当然也将MainActivity中的加载库代码剪切放在JniUtils文件中。并声明三个方法,会自动在mace_jni.cpp下自动被声明。新建方法会显示红色报错,在AS中快捷键是alt+enter创建方法在mace_jni.cpp文件中。

    package com.tcl.weilong.mace;
    
    public class JniUtils {
        static {
            System.loadLibrary("mace_jni");
        }
    
        /**
         * 给模型设置参数
         * @param ompNumThreads
         * @param cpuAffinityPolicy
         * @param gpuPerfHint
         * @param gpuPriorityHint
         * @param kernelPath
         * @return
         */
    
        public   native int maceMobilenetSetAttrs(int ompNumThreads, int cpuAffinityPolicy, int gpuPerfHint, int gpuPriorityHint, String kernelPath);
        /**
         * 给模型创建运行环境
         * @param model
         * @param device
         * @return
         */
        public  native int maceMobilenetCreateEngine(String model, String device);
    
        /**
         * 模型具体核心功能,识别图片
         * @param input
         * @return
         */
        public  native float[] maceMobilenetClassify(float[] input);
    }

     在mace_jni.cpp文件中自动生成对声明方法的实现声明:

    在build.gradle文件中更改为如下代码:

    externalNativeBuild {
                cmake {
                    cppFlags "-std=c++11 -fopenmp"
                    abiFilters "arm64-v8a"
                }
            }

    将mace附带的example工程中的lib,model,public三个文件拷贝到cpp文件夹下面并在加载时候修改路径,按如下目录结构。

    最为该工程核心的是在java声明的native方法在底层c/c++中具体实现,以下代码是以上三个方法在mace_jni.cpp文件中的具体实现:

    #include <jni.h>
    #include <algorithm>
    #include <functional>
    #include <map>
    #include <memory>
    #include <string>
    #include <vector>
    #include <numeric>
    
    #include "public/mace.h"
    #include "public/mace_runtime.h"
    #include "public/mace_engine_factory.h"
    
    namespace {
    
        struct ModelInfo {
            std::string input_name;
            std::string output_name;
            std::vector<int64_t> input_shape;
            std::vector<int64_t> output_shape;
        };
    //有的地方叫.cc也可以叫.cpp其实一个意思,实质为区别c文件的
        struct MaceContext {
            std::shared_ptr<mace::MaceEngine> engine;
            std::shared_ptr<mace::KVStorageFactory> storage_factory;
            std::string model_name;
            mace::DeviceType device_type = mace::DeviceType::CPU;
            //模型的输入输出在这里改
            std::map<std::string, ModelInfo> model_infos = {
                    {"mobilenet_v1", {"input", "MobilenetV1/Predictions/Reshape_1",
                                             {1, 224, 224, 3}, {1, 1001}}},
                    {"mobilenet_v2", {"input", "MobilenetV2/Predictions/Reshape_1",
                                             {1, 224, 224, 3}, {1, 1001}}}
            };
        };
    
        mace::DeviceType ParseDeviceType(const std::string &device) {
            if (device.compare("CPU") == 0) {
                return mace::DeviceType::CPU;
            } else if (device.compare("GPU") == 0) {
                return mace::DeviceType::GPU;
            } else if (device.compare("HEXAGON") == 0) {
                return mace::DeviceType::HEXAGON;
            } else {
                return mace::DeviceType::CPU;   //默认是返回CPU
            }
        }
    
        MaceContext& GetMaceContext() {
            static auto *mace_context = new MaceContext;
    
            return *mace_context;
        }
    
    }   //namcspace
    /**
     * java+包名+类名+函数名
     * Java+com.tcl.weilong.mace+JniUtils+maceMobilenetSetAttrs
     */
    extern "C"
    jint Java_com_tcl_weilong_mace_JniUtils_maceMobilenetSetAttrs(JNIEnv *env, jobject instance,
                                                             jint ompNumThreads, jint cpuAffinityPolicy,
                                                             jint gpuPerfHint, jint gpuPriorityHint,
                                                             jstring kernelPath_) {
        MaceContext &mace_context = GetMaceContext();
        mace::MaceStatus status;
        // openmp ??
        status = mace::SetOpenMPThreadPolicy(
                ompNumThreads,
                static_cast<mace::CPUAffinityPolicy>(cpuAffinityPolicy));
        //  gpu
        mace::SetGPUHints(
                static_cast<mace::GPUPerfHint>(gpuPerfHint),
                static_cast<mace::GPUPriorityHint>(gpuPriorityHint));
        //  opencl cache
        const char *kernel_path_ptr = env->GetStringUTFChars(kernelPath_, nullptr);
        if (kernel_path_ptr == nullptr) return JNI_ERR;
        const std::string kernel_file_path(kernel_path_ptr);
        mace_context.storage_factory.reset(
                new mace::FileStorageFactory(kernel_file_path));
        mace::SetKVStorageFactory(mace_context.storage_factory);
        env->ReleaseStringUTFChars(kernelPath_, kernel_path_ptr);
        return JNI_OK;
    }
    extern "C"
    jint Java_com_tcl_weilong_mace_JniUtils_maceMobilenetCreateEngine(JNIEnv *env, jobject instance,
                                                                 jstring model_, jstring device_) {
        MaceContext &mace_context = GetMaceContext();
        //  parse model name
        const char *model_name_ptr = env->GetStringUTFChars(model_, nullptr);
        if (model_name_ptr == nullptr) return JNI_ERR;
        mace_context.model_name.assign(model_name_ptr);
        env->ReleaseStringUTFChars(model_, model_name_ptr);
    
        //  load model input and output name
    //    auto model_info_iter = mace_context.model_infos.find(mace_context.model_name);
        auto model_info_iter = mace_context.model_infos.find(mace_context.model_name);
        if (model_info_iter == mace_context.model_infos.end()) {
    
            return JNI_ERR;
        }
        std::vector<std::string> input_names = {model_info_iter->second.input_name};
    
        std::vector<std::string> output_names = {model_info_iter->second.output_name};
    
        // get device
        const char *device_ptr = env->GetStringUTFChars(device_, nullptr);
        if (device_ptr == nullptr) return JNI_ERR;
        mace_context.device_type = ParseDeviceType(device_ptr);
        env->ReleaseStringUTFChars(device_, device_ptr);
    
        mace::MaceStatus create_engine_status =
                CreateMaceEngineFromCode(mace_context.model_name,
                                         std::string(),
                                         input_names,
                                         output_names,
                                         mace_context.device_type,
                                         &mace_context.engine);
    
    
        return create_engine_status == mace::MaceStatus::MACE_SUCCESS ?
               JNI_OK : JNI_ERR;
    }extern "C"
    jfloatArray Java_com_tcl_weilong_mace_JniUtils_maceMobilenetClassify(JNIEnv *env, jobject instance,
                                                             jfloatArray input_) {
        MaceContext &mace_context = GetMaceContext();
        //  prepare input and output
        auto model_info_iter =
                mace_context.model_infos.find(mace_context.model_name);
        if (model_info_iter == mace_context.model_infos.end()) {
    
            return nullptr;
        }
        const ModelInfo &model_info = model_info_iter->second;
        const std::string &input_name = model_info.input_name;
        const std::string &output_name = model_info.output_name;
        const std::vector<int64_t> &input_shape = model_info.input_shape;
        const std::vector<int64_t> &output_shape = model_info.output_shape;
        const int64_t input_size =
                std::accumulate(input_shape.begin(), input_shape.end(), 1,
                                std::multiplies<int64_t>());
        const int64_t output_size =
                std::accumulate(output_shape.begin(), output_shape.end(), 1,
                                std::multiplies<int64_t>());
    
        //  load input
        jfloat *input_data_ptr = env->GetFloatArrayElements(input_, nullptr);
        if (input_data_ptr == nullptr) return nullptr;
        jsize length = env->GetArrayLength(input_);
        if (length != input_size) return nullptr;
    
        std::map<std::string, mace::MaceTensor> inputs;
        std::map<std::string, mace::MaceTensor> outputs;
        // construct input
        auto buffer_in = std::shared_ptr<float>(new float[input_size],
                                                std::default_delete<float[]>());
        std::copy_n(input_data_ptr, input_size, buffer_in.get());
        env->ReleaseFloatArrayElements(input_, input_data_ptr, 0);
        inputs[input_name] = mace::MaceTensor(input_shape, buffer_in);
    
        // construct output
        auto buffer_out = std::shared_ptr<float>(new float[output_size],
                                                 std::default_delete<float[]>());
        outputs[output_name] = mace::MaceTensor(output_shape, buffer_out);
    
        // run model
        mace_context.engine->Run(inputs, &outputs);
    
        // transform output
        jfloatArray jOutputData = env->NewFloatArray(output_size);  // allocate
        if (jOutputData == nullptr) return nullptr;
        env->SetFloatArrayRegion(jOutputData, 0, output_size,
                                 outputs[output_name].data().get());  // copy
    
        return jOutputData;
    }
    

      以上步骤将模型底层运行环境准备好了,接下来继续封装为模型准备数据及其数据结果展示表层工作。

    建立AppModel.java为模型准备输入数据和接受模型的预测结果数据作为MainActivity和底层native方法的桥梁。

    说明:这个文件有四个方法,首先是对模型的设置参数,第二个是对模型创建一个运行环境实在cpu还是在gpu下运行,第三个是对识别功能准备数据将其转换为rgb,第四个是核心功能即物体识别。

    package com.tcl.weilong.mace;
    
    import android.graphics.Bitmap;
    import android.os.Environment;
    import android.util.Log;
    import java.io.File;
    import java.nio.FloatBuffer;
    
    public class AppModel {
        JniUtils jniUtils = new JniUtils();
        //这些参数的值具体代表什么尚未搞清楚
        int ompNumThreads = 2;  //线程个数
        int cpuAffinityPolicy = 0;  //
        int gpuPerfHint = 3;    //
        int gpuPriorityHint = 3;    //
        String kernelPath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "mace";  //内核路径
    
        public static final String[] MODELS = new String[]{"mobilenet_v1", "mobilenet_v2"}; //模型名称,这块不要de
        public static final String[] DEVICES = new String[]{"CPU", "GPU"};  //设备
    
        String model = MODELS[1]; //mobilenet_v2
        String device = DEVICES[0]; //CPU
    
        private int[] colorValues; //存储RGB颜色数据
        private FloatBuffer floatBuffer; //输入缓存
    
        /**
         * 给模型设置属性
         */
        public void maceMobilenetSetAttrs(){
            int attrs;
            attrs = jniUtils.maceMobilenetSetAttrs(ompNumThreads,cpuAffinityPolicy,gpuPerfHint,gpuPriorityHint,kernelPath);
            Log.d("idiot","attrs="+attrs);
        }
    
        /**
         * 给模型创建运行引擎,cpu或者gpu
         */
        public void maceMobilenetCreateEngine(){
            int engine;
            engine = jniUtils.maceMobilenetCreateEngine(model,device);
            Log.d("idiot","engin="+engine);
        }
    
        /**
         * 输入数据处理,将照片转换成数组 float[],bitmap是224*224的大小
         * @param bitmap 要处理的原始图片
         * @return 将图片处理后转换成像素点进行返回
         */
        public FloatBuffer dealPic(Bitmap bitmap){
            //这块要创建一个224*224的图片是针对输入原始图片创建的,每一行有224个像素点,共有224行
            colorValues = new int[224 * 224];   //存储像素
            float[] floatValues = new float[224 * 224 * 3]; //RGB像素*3
            floatBuffer = FloatBuffer.wrap(floatValues, 0, 224 * 224 * 3);
            //这个函数要深究,从(0,0)点开始平移,平移尺度是单元尺寸的一个宽度。
            bitmap.getPixels(colorValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());
            floatBuffer.rewind();   //??
            //最核心的图像像素点的变化,根本没懂
                for (int i = 0; i < colorValues.length; i++) {
                    int value = colorValues[i];
                    floatBuffer.put((((value >> 16) & 0xFF) - 128f) / 128f);
                    floatBuffer.put((((value >> 8) & 0xFF) - 128f) / 128f);
                    floatBuffer.put(((value & 0xFF) - 128f) / 128f);
                }
                return floatBuffer;
        }
        /**
         * 模型的分类函数
         * @param input 缓存的像素点
         * @return  识别结果数据,在类标中匹配识别
         */
        public float[] maceMobilenetClassify(FloatBuffer input){
            float[] result;
            result = jniUtils.maceMobilenetClassify(input.array());
            return result;
        }
    }
    

      以上文件即可实现对模型输入图片的功能,下面的文件是对识别结果进行匹配。创建LableCache.java文件实现:

    package com.tcl.weilong.mace;
    
    import android.content.Context;
    import android.content.res.AssetManager;
    import android.util.Log;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;
    
    public class LabelCache {
    
        private List<Float> floatList = new ArrayList<>();  //每张识别结果的概率
        private List<String> resultLabel = new ArrayList<>();   //识别结果缓存列表
        private ResultData mResultData; //
    
        /**
         * 加载类标的资源文件
         * @param context 上下文
         */
        public void readCacheLabelFromLocalFile(Context context) {
            try {
                AssetManager assetManager = context.getAssets();    //获取资源管理器
                //从资源管理器中加载标签文件,所有的结果在文本文件中,识别的结果在label中去匹配
                BufferedReader reader = new BufferedReader(new InputStreamReader(assetManager.open("cacheLabel.txt")));
                String readLine = null;
                while ((readLine = reader.readLine()) != null) {
                    Log.d("labelCache", "readLine = " + readLine);
                    resultLabel.add(readLine);  //原文是移动识别,这样一张大的图片会识别很多个,将所有识别的都放在该列表里,该最简工程只识别了最小识别单元
                }
                reader.close();
            } catch (Exception e) {
                Log.e("labelCache", "error " + e);
            }
        }
    
        /**
         *  获取所有识别结果中概率最高的一个值进行返回
         * @param floats 识别结果值: result = appModel.maceMobilenetClassify(input);
         * @return 将识别的结果值存储到data中返回
         */
        public ResultData getResultFirst(float[] floats) {
            floatList.clear();
            for (float f : floats) {
                floatList.add(f);
            }
            float maxResult = Collections.max(floatList);   //在所有识别结果中获取最大的概率即为最终识别结果
    
            int indexResult = floatList.indexOf(maxResult);
            if (indexResult < resultLabel.size()) {
                String result = resultLabel.get(indexResult);
                if (result != null) {
                    if (mResultData == null) {
                        mResultData = new ResultData(result, maxResult);
                    } else {
                        mResultData.updateData(result, maxResult);
                    }
                    return mResultData;
                }
            }
            return null;
        }
    }

      所需的资源文件为cacheLabel.txt文件在assets资源文件夹下面。

      

    以上工作将识别的结果已经拿到,由于结果包括识别名称,识别的时间和相对应的概率,我们需要data包来封装这些数据,创建ResultData.java文件封装。

    package com.tcl.weilong.mace;
    
    public class ResultData {
        public String name; //识别结果内容
        public float probability;   //识别的可能性,概率值
        public long costTime;  // 运行时间
    
        /**
         * 构造函数,只有名称和识别概率值
         * @param name
         * @param probability
         */
        public ResultData(String name, float probability) {
            this.name = name;
            this.probability = probability;
        }
    
        /**
         * 构造函数
         * @param name
         */
        public ResultData(String name) {
            this.name = name;
        }
    
        /**
         * 更新名称和概率值,这个函数原工程中使用用于不断的识别,更新名称显示
         * @param name
         * @param probability
         */
        public void updateData(String name, float probability) {
            this.name = name;
            this.probability = probability;
        }
    
    }
    

      接下来对拿到的数据进行展示,展示比较简单。其activity.xml文件如下:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_height="match_parent"
        android:layout_width="match_parent">
        <ImageView
            android:id="@+id/iv"
            android:layout_width="match_parent"
            android:layout_height="400dp"
            android:layout_gravity="center_horizontal" />
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/tv"/>
    </LinearLayout>
    

      最后MainActivity.java文件进行调度:

    package com.tcl.weilong.mace;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    import java.nio.FloatBuffer;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            ImageView iv = findViewById(R.id.iv);
            TextView tv =  findViewById(R.id.tv);
            float[] result;
            //获取图片,图片的大小是224 * 224,由于模型的识别输入是该大小,如果是一张大图片就会顺次平移类似于CNN一样
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inScaled = false;
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.mouse,options);
            Log.d("idiot",""+bitmap.getWidth()+";height:"+bitmap.getHeight());
            iv.setImageBitmap(bitmap);  //只显示了图片,这块没有意义,原意该处为图片的获取及其展示
    
            LabelCache labelCache = new LabelCache();       //模型结果label
            Context context = getApplicationContext();
            labelCache.readCacheLabelFromLocalFile(context);    //加载label资源文件
    
            AppModel appModel = new AppModel();
            appModel.maceMobilenetSetAttrs();      //为模型设置环境
            appModel.maceMobilenetCreateEngine();
    
            FloatBuffer input = appModel.dealPic(bitmap);   //加载输入识别照片,返回缓存的像素点单元
            long start = System.currentTimeMillis();
            result = appModel.maceMobilenetClassify(input); //该工程的核心功能块,识别图片
            long end = System.currentTimeMillis();
            long costTime = end - start;    //识别一张图片的时间
            Toast.makeText(this,"CostTime: "+costTime+" ms",Toast.LENGTH_LONG).show();
    
            ResultData data;
            data = labelCache.getResultFirst(result);   //将返回的结果对照label处理
            data.costTime = costTime;
            String resultt = data.name  + "
    " + data.probability + "
    cost time(ms): " + data.costTime;
            tv.setText(resultt);    //展示结果
        }
    
    }
    

    注意:

    1.传入的图片是224*224大小,这个问题以后会更改。

    2.运行的cpu架构是armv-8的,这个问题没有解决。

    模拟器运行结果如下:采用的模拟器时间很慢。

    真机测试运行时间和内存消耗结果:

    CPU:

    GPU:

    MACE采用GPU加速,时间和硬件消耗缩减一半左右。

  • 相关阅读:
    新框架的选择
    ‘’火星文‘’的解析
    http.request请求及在node中post请求参数解析
    http.request的请求
    ReactNative环境配置的坑
    return false与return true的区别
    什么是DOM,DOM level 123 的区别是什么
    页面重绘和回流以及优化
    时代人物之任正非
    Adriod与HTML+JS的交互
  • 原文地址:https://www.cnblogs.com/missidiot/p/9717633.html
Copyright © 2011-2022 走看看