赞
踩
推荐看这篇之前把下面这篇先看一下,因为这篇中一些运用的前提在下面PC篇,如转换模型。
因为我本来本科大二的时候玩过android, 所以接触这个项目移植这部分我主要负责,早想把自己弄得清晰思路分享大家了。
进入下面这个网站,找到Android Studio板块之后点进去,自动就会显示你当前系统版本和配置的下载包,下载即可,傻瓜操作,不赘述了
https://developer.android.google.cn
解压到/usr/local文件中
sudo unzip android-studio-ide-181.5056338-linux.zip -d /usr/local
/usr/local/android-studio/bin/中有个studio.sh 启动即可 之后点安卓图标右键锁定到启动器,以后直接点击桌面左面图标即可
第一次打开会有一些配置选项,问题不大,最主要是会有一个自动的android sdk的安装最好网络好一些,要不会很卡(自动下载还是很方便的,像以前我用eclipse开发android的时候,还需要自己配置sdk,很麻烦)下载之后也会发现自己的home里多了一个Android文件夹
下载完成,你可以随便开始一个project。
这边移植的话主要按下面步骤来
NDKbuild 当时做项目的时候研究了几天发现代码没错,就是编译不成功,又挺了几天果断换成Cmake进行编译
首先新建一个空的工程
这里是demo,之后一直next即可
点击File->Settings->Appearance&Behavior->System Settings->Android SDK下载一些必要的包如图所示
如果要是之前自动下载了NDK那目前的步骤只需要下载LLDB,NDK,CMAKE,因为其他的都自动下载完了。
这里不多讲了我之前的博客也有说过或者百度bashrc 和vim使用即可
vim .bashrc
export NDK_HOME=/home/che/Android/Sdk/ndk-bundle
PATH=$NDK_HOME:$PATH
source ~/.bashrc
进行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文件夹里面有两个文件
命名为MobileNetSSD_demo
file/new/newproject
因为这里我们采用cmake编译.so文件的方法,所以相当于用java接口去调用c++,使用NDK技术,所以这边需要选择include c++ support之后next 选择Phone和Tablet之后选择空project之后next 到了下面这个界面需要选择c++11
finish
发现文件里多个cpp文件,下面也多了一个CMakeLists.txt
生成转换文件请看 MobileNetSSD通过Ncnn前向推理框架在PC端的使用(目标检测 objection detection)
background
aeroplane
bicycle
bird
boat
bottle
bus
car
cat
chair
cow
diningtable
dog
horse
motorbike
person
pottedplant
sheep
sofa
train
tvmonitor
下面代码自己仿照ncnn的examples里的squeezencnn的安卓项目工程里面的代码所修改,这个项目工程是利用NDK-build不是我所讲的Cmake方式
具体代码如下(有详细注释)
#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文件夹下
<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"/>
# 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})
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'
}
命名为和.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");//最后
}
}
<?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>
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;
}
}
}
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,之后点击Make Project,等一会,不出意外应该是成功了
查看自己的这个文件夹的对应的编译硬件架构的文件夹下成功生成.so文件,名字为libMobileNetssd.so
我喜欢用真机测试,插上真机打开开发者模式等等,可百度一下,很多,不赘述了
先导入一些测试图片,之后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
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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。