【手把手AI项目】八、MobileNetSSD通过Ncnn前向推理框架在Android端的使用--Cmake编译(目标检测 objection detection)

版权声明:欢迎关注公众号:AI蜗牛车 || 本文为博主原创文章,未经博主允许不得转载, 若转载请与我联系。 https://blog.csdn.net/qq_33431368/article/details/85009758

一、前言

推荐看这篇之前把下面这篇先看一下,因为这篇中一些运用的前提在下面PC篇,如转换模型。

因为我本来本科大二的时候玩过android, 所以接触这个项目移植这部分我主要负责,早想把自己弄得清晰思路分享大家了。

二、安装Android Studio (AS)

1.下载AS安装包

进入下面这个网站,找到Android Studio板块之后点进去,自动就会显示你当前系统版本和配置的下载包,下载即可,傻瓜操作,不赘述了
https://developer.android.google.cn

2.解压

解压到/usr/local文件中

sudo unzip android-studio-ide-181.5056338-linux.zip -d /usr/local

3.打开使用

/usr/local/android-studio/bin/中有个studio.sh 启动即可 之后点安卓图标右键锁定到启动器,以后直接点击桌面左面图标即可
第一次打开会有一些配置选项,问题不大,最主要是会有一个自动的android sdk的安装最好网络好一些,要不会很卡(自动下载还是很方便的,像以前我用eclipse开发android的时候,还需要自己配置sdk,很麻烦)下载之后也会发现自己的home里多了一个Android文件夹
在这里插入图片描述下载完成,你可以随便开始一个project。
这边移植的话主要按下面步骤来

三、android前期准备

NDKbuild 当时做项目的时候研究了几天发现代码没错,就是编译不成功,又挺了几天果断换成Cmake进行编译

1.下载需要的包

首先新建一个空的工程
在这里插入图片描述这里是demo,之后一直next即可
点击File->Settings->Appearance&Behavior->System Settings->Android SDK下载一些必要的包如图所示
在这里插入图片描述
如果要是之前自动下载了NDK那目前的步骤只需要下载LLDB,NDK,CMAKE,因为其他的都自动下载完了。
在这里插入图片描述

2.添加环境变量

这里不多讲了我之前的博客也有说过或者百度bashrc 和vim使用即可

vim .bashrc

export NDK_HOME=/home/che/Android/Sdk/ndk-bundle
PATH=$NDK_HOME:$PATH

source ~/.bashrc

在这里插入图片描述

四、ncnn for android 前期准备

进行buld ncnn(android)

ANDROID_ABI 是移动端硬件架构名字,"armeabi-v7a" 支持绝大部分手机硬件,项目中运用RK3399是v8硬件架构,这边用手机测试所以用v7a
ANDROID_ARM_NEON 是否使用 NEON 指令集,设为 ON 支持绝大部分手机硬件 
ANDROID_PLATFORM 指定最低系统版本,"android-14" 就是 android-4.0(这个玩过android的应该都清楚)

cd ncnn
mkdir -p build-android
cd build-android
cmake -DCMAKE_TOOLCHAIN_FILE=/home/che/Android/Sdk/ndk-bundle/build/cmake/android.toolchain.cmake     -DANDROID_ABI="armeabi-v7a" -DANDROID_ARM_NEON=ON     -DANDROID_PLATFORM=android-14 ..
make -j4
make install

一般都会出现错误,这里需要把cmake升级下(要求3.6.0及以上)
在这里插入图片描述首先在http://www.cmake.org/files找到自己想下的版本,我在这里下载3.6.2

sudo apt-get install build-essential 
wget https://cmake.org/files/v3.6/cmake-3.6.2.tar.gz
tar xf cmake-3.6.2.tar.gz 
cd cmake-3.6.2 
./configure  
make 
sudo make install

安装完可以用以下命令查看Cmake版本

cmake --version

在这里插入图片描述
搞定
重新进行进行buld ncnn(android)那步骤
依然可能出现问题
CMake Error at examples/CMakeLists.txt:4 (find_package):
By not providing “FindOpenCV.cmake” in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by “OpenCV”, but
CMake did not find one.

Could not find a package configuration file provided by “OpenCV” with any
of the following names:

OpenCVConfig.cmake
opencv-config.cmake

Add the installation prefix of “OpenCV” to CMAKE_PREFIX_PATH or set
“OpenCV_DIR” to a directory containing one of the above files. If “OpenCV”
provides a separate development package or SDK, be sure it has been
installed.
在这里插入图片描述解决方案把ncnn/CMakeLists.txt里的
add_subdirectory(examples)注释掉,本来默认就是注释的,但是看过我的第一篇ncnn文章 MobileNetSSD通过Ncnn前向推理框架在PC端的使用(目标检测 objection detection)的朋友可能这里修改了一下,需要改回来。
最后make install成功后
会在ncnn/build-android下产生一个install文件夹里面有两个文件

  • include文件夹 里面包含各种常用的.h文件
  • lib/libncnn.a文件  以我理解这个文件就是相当于把ncnn打包成android可导入的形式
    在这里插入图片描述在这里插入图片描述

五、创建android工程

命名为MobileNetSSD_demo
file/new/newproject
在这里插入图片描述因为这里我们采用cmake编译.so文件的方法,所以相当于用java接口去调用c++,使用NDK技术,所以这边需要选择include c++ support之后next 选择Phone和Tablet之后选择空project之后next 到了下面这个界面需要选择c++11
在这里插入图片描述finish
在这里插入图片描述
发现文件里多个cpp文件,下面也多了一个CMakeLists.txt

六、复制文件到android项目中(加密model和网络文件以及build-android文件)

生成转换文件请看 MobileNetSSD通过Ncnn前向推理框架在PC端的使用(目标检测 objection detection)

  • 在main目录下创建assets目录,将MobileNetSSD_deploy.param.bin (转换后网络模型参数)MobileNetSSD_deploy.bin (转换后的网络权重)复制到文件中去,自己创建一个words.txt也复制到其中
    words.txt内容(如果是自己的dataset就按照自己的label创建即可)如下:
background
aeroplane
bicycle
bird
boat
bottle
bus
car
cat
chair
cow
diningtable
dog
horse
motorbike
person
pottedplant
sheep
sofa
train
tvmonitor
  • 复制上述build-android得到的install文件中的include文件夹以及ncnn加密得到的编译
    把mobilenet_v2.id.h复制到cpp目录下。(一会主要c++文件也放在其中这个一会讲到)
  • 在main目录下创建jniLibs/armeabi-v7a/目录,并把复制上述build-android得到的install文件中lib下的libncnn.a文件复制到该目录。(jniLibs下的这个文件夹名称和上面build-andorid时考虑的兼容硬件架构一致,如果这边使用RK3399则为v8)
  • 还有一点你会发现新建的android工程中有cpp下native-lib.cpp文件里面是NDK代码的一个demo,你会在MainActivity中发现这个cpp的java接口代码,我当时研究NDK的时候也是根据这个demo自己去琢磨的,你也可以多看看如果没有学过NDK的话,这边就不赘述了直接删掉 最后的文件部署如下图所示
    在这里插入图片描述

七、核心代码和配置

下面代码自己仿照ncnn的examples里的squeezencnn的安卓项目工程里面的代码所修改,这个项目工程是利用NDK-build不是我所讲的Cmake方式

1.MobileNetssd.cpp(创建或者直接从example里更改)

具体代码如下(有详细注释)

#include <android/bitmap.h>
#include <android/log.h>
#include <jni.h>
#include <string>
#include <vector>

// ncnn
#include "include/opencv.h"
#include "MobileNetSSD_deploy.id.h"   //这里看成自己的id.h
#include <sys/time.h>
#include <unistd.h>
#include "include/net.h"

static ncnn::UnlockedPoolAllocator g_blob_pool_allocator;
static ncnn::PoolAllocator g_workspace_pool_allocator;

static ncnn::Mat ncnn_param;
static ncnn::Mat ncnn_bin;
static ncnn::Net ncnn_net;

extern "C" {

// public native boolean Init(byte[] words,byte[] param, byte[] bin);  原函数形式(c++) 以下形式为ndk的c++形式
JNIEXPORT jboolean JNICALL
Java_com_example_che_mobilenetssd_demo_MobileNetssd_Init(JNIEnv *env, jobject obj, jbyteArray param, jbyteArray bin){
    __android_log_print(ANDROID_LOG_DEBUG, "MobileNetssd", "enter the jni func");
    // init param
    {
        int len = env->GetArrayLength(param);
        ncnn_param.create(len, (size_t) 1u);
        env->GetByteArrayRegion(param, 0, len, (jbyte *) ncnn_param);
        int ret = ncnn_net.load_param((const unsigned char *) ncnn_param);
        __android_log_print(ANDROID_LOG_DEBUG, "MobileNetssd", "load_param %d %d", ret, len);
    }

    // init bin
    {
        int len = env->GetArrayLength(bin);
        ncnn_bin.create(len, (size_t) 1u);
        env->GetByteArrayRegion(bin, 0, len, (jbyte *) ncnn_bin);
        int ret = ncnn_net.load_model((const unsigned char *) ncnn_bin);
        __android_log_print(ANDROID_LOG_DEBUG, "MobileNetssd", "load_model %d %d", ret, len);
    }

    ncnn::Option opt;
    opt.lightmode = true;
    opt.num_threads = 4;   //线程 这里可以修改
    opt.blob_allocator = &g_blob_pool_allocator;
    opt.workspace_allocator = &g_workspace_pool_allocator;

    ncnn::set_default_option(opt);

    return JNI_TRUE;
}

// public native String Detect(Bitmap bitmap);
JNIEXPORT jfloatArray JNICALL Java_com_example_che_mobilenetssd_demo_MobileNetssd_Detect(JNIEnv* env, jobject thiz, jobject bitmap)
{
    // ncnn from bitmap
    ncnn::Mat in;
    {
        AndroidBitmapInfo info;
        AndroidBitmap_getInfo(env, bitmap, &info);
//        int origin_w = info.width;
//        int origin_h = info.height;
//        int width = 300;
//        int height = 300;
        int width = info.width;
        int height = info.height;
        if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
            return NULL;

        void* indata;
        AndroidBitmap_lockPixels(env, bitmap, &indata);
        // 把像素转换成data,并指定通道顺序
        // 因为图像预处理每个网络层输入的数据格式不一样一般为300*300 128*128等等所以这类需要一个resize的操作可以在cpp中写,也可以是java读入图片时有个resize操作
//      in = ncnn::Mat::from_pixels_resize((const unsigned char*)indata, ncnn::Mat::PIXEL_RGBA2RGB, origin_w, origin_h, width, height);

        in = ncnn::Mat::from_pixels((const unsigned char*)indata, ncnn::Mat::PIXEL_RGBA2RGB, width, height);

        // 下面一行为debug代码
        //__android_log_print(ANDROID_LOG_DEBUG, "MobilenetssdJniIn", "Mobilenetssd_predict_has_input1, in.w: %d; in.h: %d", in.w, in.h);
        AndroidBitmap_unlockPixels(env, bitmap);
    }

    // ncnn_net
    std::vector<float> cls_scores;
    {
        // 减去均值和乘上比例(这个数据和前面的归一化图片预处理形式一一对应)
        const float mean_vals[3] = {127.5f, 127.5f, 127.5f};
        const float scale[3] = {0.007843f, 0.007843f, 0.007843f};

        in.substract_mean_normalize(mean_vals, scale);// 归一化

        ncnn::Extractor ex = ncnn_net.create_extractor();//前向传播

        // 如果不加密是使用ex.input("data", in);
        // BLOB_data在id.h文件中可见,相当于datainput网络层的id
        ex.input(MobileNetSSD_deploy_param_id::BLOB_data, in);
        //ex.set_num_threads(4); 和上面一样一个对象

        ncnn::Mat out;
        // 如果时不加密是使用ex.extract("prob", out);
        //BLOB_detection_out.h文件中可见,相当于dataout网络层的id,输出检测的结果数据
        ex.extract(MobileNetSSD_deploy_param_id::BLOB_detection_out, out);

        int output_wsize = out.w;
        int output_hsize = out.h;

        //输出整理
        jfloat *output[output_wsize * output_hsize];
        for(int i = 0; i< out.h; i++) {
            for (int j = 0; j < out.w; j++) {
                output[i*output_wsize + j] = &out.row(i)[j];
            }
        }
        jfloatArray jOutputData = env->NewFloatArray(output_wsize);
        if (jOutputData == nullptr) return nullptr;
        env->SetFloatArrayRegion(jOutputData, 0,  output_wsize * output_hsize,
                                 reinterpret_cast<const jfloat *>(*output));

        return jOutputData;
    }
}
}

将.cpp文件放到cpp文件夹下
在这里插入图片描述

2.AndroidManifest.xml添加权限

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.CAMERA"/>

3.CMakeLists.txt修改

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

##需要添加 相当于添加ncnn for android 的包 need to add
set(ncnn_lib ${CMAKE_SOURCE_DIR}/src/main/jniLibs/armeabi-v7a/libncnn.a)
add_library (ncnn_lib STATIC IMPORTED)
set_target_properties(ncnn_lib PROPERTIES IMPORTED_LOCATION ${ncnn_lib})

add_library( # Sets the name of the library.
        MobileNetssd ## 为生成.so的文字最好直接和.cpp名字一样,需要更改 need to add

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        src/main/cpp/MobileNetssd.cpp )##cpp文件的name

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        ##以下三个都要添加 need to add
        MobileNetssd   #和上面一样
        ncnn_lib       #这个ncnn的lib的add
        jnigraphics    #这个jni也需要add

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

4.build.gradle修改

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.che.mobilenetssd_demo"
        minSdkVersion 15
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -fopenmp"//c++,多线程 需要添加 need to add
                abiFilters "armeabi-v7a" // 手机的硬件架构,基本所有的硬件都适配 need to add
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }

    // 需要添加 把 .a文件导入, .a为 ncnn make intall生成的里面的.a文件 need to add
    sourceSets {
        main {
            jniLibs.srcDirs = ["src/main/jniLibs"]
            jni.srcDirs = ['src/cpp']
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    implementation 'com.github.bumptech.glide:glide:4.3.1'   // 需要添加,增加图片类 bumptech,build自动红线消失 need to add
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

5.自己编写java接口

命名为和.cpp文件一样的名称
这里就是MobileNetssd.java

package com.example.che.mobilenetssd_demo;

import android.graphics.Bitmap;

/**
 *  MobileNetssd的java接口,与本地c++代码相呼应 native为本地 此文件与 MobileNetssd.cpp相呼应
 */
public class MobileNetssd {

    public native boolean Init(byte[] param, byte[] bin); // 初始化函数 
    public native float[] Detect(Bitmap bitmap); // 检测函数
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("MobileNetssd");//最后
    }
}

  • public native boolean Init(byte[] param, byte[] bin); // 初始化函数 对应于NDK编写的.cpp文件中的JNIEXPORT jboolean JNICALL
    Java_com_example_che_mobilenetssd_demo_MobileNetssd_Init(JNIEnv *env, jobject obj, jbyteArray param, jbyteArray bin)
  • public native float[] Detect(Bitmap bitmap); // 检测函数 对应于NDK编写的.cpp文件中的JNIEXPORT jfloatArray JNICALL Java_com_example_che_mobilenetssd_demo_MobileNetssd_Detect(JNIEnv* env, jobject thiz, jobject bitmap)
    其实有规律可寻以第二个为例子
    函数前三个都是JNIEXPORT +函数返回类型(NDK形式)+JNICALL
    Java_com_example_che_mobilenetssd_demo_MobileNetssd_Detect(JNIEnv* env, jobject thiz, jobject bitmap)
    对于这个命名先看下面这个图你可能就懂了
    在这里插入图片描述
    相当于绝对路径那种感觉了显示java文件夹,再是com.example.che.mobilenetssd_demo这个包再是MobileNetssd.java文件最后是 java接口文件中的public native float[] Detect(Bitmap bitmap); 函数的文件名,即Java_com_example_che_mobilenetssd_demo_MobileNetssd_Detect,后面参数则为
    (JNIEnv* env, jobject thiz,+原函数的函数参数(NDK类型格式))
    其实正常来讲NDK这种不应该是先写cpp再写java接口而是应该先写java接口再利用IDE本身带有的NDK的开发环境可以直接双点写的java接口的函数(函数格式如上需要加一个native)之后快捷键按ALT+ENTER即可直接在cpp文件中添加成功,具体请看NDK技术即可,这里不赘余了。
    接下来就是我的其他.java文件以及UI的XML文件了

6.activity_main.xml(res/layout/activity_main.xml)

<?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"
    tools:context=".MainActivity">
    <LinearLayout
        android:id="@+id/btn_ll"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/use_photo"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:text="相册"/>
    </LinearLayout>
    <TextView
        android:layout_above="@id/btn_ll"
        android:id="@+id/result_text"
        android:textSize="16sp"
        android:layout_width="match_parent"
        android:hint="预测结果会在这里显示"
        android:layout_height="100dp"/>
    <ImageView
        android:layout_alignParentTop="true"
        android:layout_above="@id/result_text"
        android:id="@+id/show_image"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</RelativeLayout>

7.MainActivity.java修改成如下

package com.example.che.mobilenetssd_demo;

import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
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 java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


import com.bumptech.glide.Glide;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.RequestOptions;


public class MainActivity extends AppCompatActivity {

    private static final String TAG = MainActivity.class.getName();
    private static final int USE_PHOTO = 1001;
    private String camera_image_path;
    private ImageView show_image;
    private TextView result_text;
    private boolean load_result = false;
    private int[] ddims = {1, 3, 300, 300}; //这里的维度的值要和train model的input 一一对应
    private int model_index = 1;
    private List<String> resultLabel = new ArrayList<>();
    private MobileNetssd mobileNetssd = new MobileNetssd(); //java接口实例化 下面直接利用java函数调用NDK c++函数

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        try
        {
            initMobileNetSSD();
        } catch (IOException e) {
            Log.e("MainActivity", "initMobileNetSSD error");
        }
        init_view();
        readCacheLabelFromLocalFile();
}

    /**
     *
     * MobileNetssd初始化,也就是把model文件进行加载
     */
    private void initMobileNetSSD() throws IOException {
        byte[] param = null;
        byte[] bin = null;
        {
            //用io流读取二进制文件,最后存入到byte[]数组中
            InputStream assetsInputStream = getAssets().open("MobileNetSSD_deploy.param.bin");// param:  网络结构文件
            int available = assetsInputStream.available();
            param = new byte[available];
            int byteCode = assetsInputStream.read(param);
            assetsInputStream.close();
        }
        {
            //用io流读取二进制文件,最后存入到byte上,转换为int型
            InputStream assetsInputStream = getAssets().open("MobileNetSSD_deploy.bin");//bin:   model文件
            int available = assetsInputStream.available();
            bin = new byte[available];
            int byteCode = assetsInputStream.read(bin);
            assetsInputStream.close();
        }

        load_result = mobileNetssd.Init(param, bin);// 再将文件传入java的NDK接口(c++ 代码中的init接口 )
        Log.d("load model", "MobileNetSSD_load_model_result:" + load_result);
    }


    // initialize view
    private void init_view() {
        request_permissions();
        show_image = (ImageView) findViewById(R.id.show_image);
        result_text = (TextView) findViewById(R.id.result_text);
        result_text.setMovementMethod(ScrollingMovementMethod.getInstance());
        Button use_photo = (Button) findViewById(R.id.use_photo);
        // use photo click
        use_photo.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!load_result) {
                    Toast.makeText(MainActivity.this, "never load model", Toast.LENGTH_SHORT).show();
                    return;
                }
                PhotoUtil.use_photo(MainActivity.this, USE_PHOTO);
            }
        });
    }

    // load label's name
    private void readCacheLabelFromLocalFile() {
        try {
            AssetManager assetManager = getApplicationContext().getAssets();
            BufferedReader reader = new BufferedReader(new InputStreamReader(assetManager.open("words.txt")));//这里是label的文件
            String readLine = null;
            while ((readLine = reader.readLine()) != null) {
                resultLabel.add(readLine);
            }
            reader.close();
        } catch (Exception e) {
            Log.e("labelCache", "error " + e);
        }
    }


    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        String image_path;
        RequestOptions options = new RequestOptions().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE);
        if (resultCode == Activity.RESULT_OK) {
            switch (requestCode) {
                case USE_PHOTO:
                    if (data == null) {
                        Log.w(TAG, "user photo data is null");
                        return;
                    }
                    Uri image_uri = data.getData();

                    //Glide.with(MainActivity.this).load(image_uri).apply(options).into(show_image);

                    // get image path from uri
                    image_path = PhotoUtil.get_path_from_URI(MainActivity.this, image_uri);
                    // predict image
                    predict_image(image_path);
                    break;
            }
        }
    }

    //  predict image
    private void predict_image(String image_path) {
        // picture to float array
        Bitmap bmp = PhotoUtil.getScaleBitmap(image_path);
        Bitmap rgba = bmp.copy(Bitmap.Config.ARGB_8888, true);
        // resize to 300*300
        Bitmap input_bmp = Bitmap.createScaledBitmap(rgba, ddims[2], ddims[3], false);
        try {
            // Data format conversion takes too long
            // Log.d("inputData", Arrays.toString(inputData));
            long start = System.currentTimeMillis();
            // get predict result
            float[] result = mobileNetssd.Detect(input_bmp);
            // time end
            long end = System.currentTimeMillis();
            Log.d(TAG, "origin predict result:" + Arrays.toString(result));
            long time = end - start;
            Log.d("result length", "length of result: " + String.valueOf(result.length));
            // show predict result and time
            float[] r = get_max_result(result);

            String show_text = "result:" + Arrays.toString(r) + "\nname:" + resultLabel.get((int) r[0]) + "\nprobability:" + r[1] + "\ntime:" + time + "ms" ;
            result_text.setText(show_text);
            Canvas canvas = new Canvas(rgba);
            //图像上画矩形
            Paint paint = new Paint();
            paint.setColor(Color.RED);
            paint.setStyle(Paint.Style.STROKE);//不填充
            paint.setStrokeWidth(10); //线的宽度
            canvas.drawRect(r[2]*rgba.getWidth(), r[3]*rgba.getHeight(), r[4]*rgba.getWidth(), r[5]*rgba.getHeight(), paint);
            show_image.setImageBitmap(rgba);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // get max probability label
    private float[] get_max_result(float[] result) {
        int num_rs = result.length / 6;
        float maxProp = result[1];
        int maxI = 0;
        for(int i = 1; i<num_rs;i++){
            if(maxProp<result[i*6+1]){
                maxProp = result[i*6+1];
                maxI = i;
            }
        }
        float[] ret = {0,0,0,0,0,0};
        for(int j=0;j<6;j++){
            ret[j] = result[maxI*6 + j];
        }
        return ret;
    }

    // request permissions(add)
    private void request_permissions() {
        List<String> permissionList = new ArrayList<>();
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(Manifest.permission.CAMERA);
        }
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
        }
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissionList.add(Manifest.permission.READ_EXTERNAL_STORAGE);
        }
        // if list is not empty will request permissions
        if (!permissionList.isEmpty()) {
            ActivityCompat.requestPermissions(this, permissionList.toArray(new String[permissionList.size()]), 1);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0) {
                    for (int i = 0; i < grantResults.length; i++) {
                        int grantResult = grantResults[i];
                        if (grantResult == PackageManager.PERMISSION_DENIED) {
                            String s = permissions[i];
                            Toast.makeText(this, s + "permission was denied", Toast.LENGTH_SHORT).show();
                        }
                    }
                }
                break;
        }
    }



}


8.PhotoUtil.java(照片工具类)

package com.example.che.mobilenetssd_demo;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore;

public class PhotoUtil {
    // get picture in photo
    public static void use_photo(Activity activity, int requestCode) {
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType("image/*");
        activity.startActivityForResult(intent, requestCode);
    }

    // get photo from Uri
    public static String get_path_from_URI(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;
    }

    // compress picture
    public static Bitmap getScaleBitmap(String filePath) {
        BitmapFactory.Options opt = new BitmapFactory.Options();
        opt.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, opt);

        int bmpWidth = opt.outWidth;
        int bmpHeight = opt.outHeight;

        int maxSize = 500;

        // compress picture with inSampleSize
        opt.inSampleSize = 1;
        while (true) {
            if (bmpWidth / opt.inSampleSize < maxSize || bmpHeight / opt.inSampleSize < maxSize) {
                break;
            }
            opt.inSampleSize *= 2;
        }
        opt.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(filePath, opt);
    }
}


全部完毕

八、build project /真机测试

1.build

在这里插入图片描述点击Build,之后点击Make Project,等一会,不出意外应该是成功了
在这里插入图片描述查看自己的这个文件夹的对应的编译硬件架构的文件夹下成功生成.so文件,名字为libMobileNetssd.so

2.真机测试

我喜欢用真机测试,插上真机打开开发者模式等等,可百度一下,很多,不赘述了
先导入一些测试图片,之后run 就好了,这个地方太基本不想多说了,没说过android的朋友稍微探索一下怎么成功导入app即可,这过程中可能因为手机版本等更换SDK版本等等问题,常见的就是修改sdk版本即可,在导入的过程中需要一些手机真机的钥匙等确认。
安装完毕打开app有几个权限需要手机确认以下即可,真机照片(这里会出现一个无法编译成功的错误,看总结里写到解决方式,主要和NDK命名规则有关)
在这里插入图片描述
在这里插入图片描述
前面提到一个 线程的问题,这里可以自行更改到速度最快即可,一般为4最快,这个也和你输入的图片

九、总结

这边的代码目前只支持单目标检测,现在一张图片中有多个目标则无法成功检测出最后的框图版本,下一篇博客把这个实现写上准备,这里还有一点就是.cpp文件中的NDK函数的名称需要修改才能最后app装在手机上成功,我项目的android名称为一连串的,而这回起名为MobileNetSSD_demo形式(感觉自己也是没事找事= =),中多个下划线,这里直接关系到NDK的函数命名格式

Java_com_example_che_mobilenetssd_demo_MobileNetssd_Init(原来的)
Java_com_example_che_mobilenetssd_1demo_MobileNetssd_Init(更改的)

在mobilenetssd_后多个1再加上demo才能关联成功,java接口的这个函数直接go to Declaration就可以直接关联到.cpp中这个函数了,Detect函数以此列推,如果你自己再走一遍流程起名是一连串的(比如MobileNetssddemo)就不会出现这样的问题了,但是这个问题不会影响之前的所有讲解,没有问题,改完之后需要clean project 之后重新make project 即可成功 成功结果如上图所示

十、代码链接

已在我的github上,希望大家给个star follow.因为准备和下一篇博文多检测的分开重命名为single
https://github.com/chehongshu/ncnnforandroid_objectiondetection_Mobilenetssd/tree/master/MobileNetSSD_demo_single

PS: 如果觉得本篇本章对您有所帮助,欢迎关注、评论、点赞!Github给个Star就更完美了_!

十一、补充

如果想打包成android APK请参照我另一篇博文
Android studio打包APK Android Studio

Reference

https://blog.csdn.net/freezingxu/article/details/73917933

https://blog.csdn.net/qq_36982160/article/details/79931741

https://blog.csdn.net/qq_33200967/article/details/82421089#_85

https://blog.csdn.net/learning_tortosie/article/details/80593956

猜你喜欢

转载自blog.csdn.net/qq_33431368/article/details/85009758