代码链接:
https://github.com/watersink/SqueezeClassify-as
本代码可以在模拟器下进行跑。
环境:
Android studio 3.6
Sdk:android10 api 29
Ndk:r15c
Ncnn:20200226
Android下开发:
(1)增加模型文件,(src/main/assets)
在src/main/cpp下面增加模型的.id.h文件,
(2)增加ncnn库文件(src/main/jniLibs)
增加ncnn的头文件,(src/main/cpp/include)
(3)修改布局文件,src/main/res/layout
<?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/buttonImage"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="选图"/>
<Button
android:id="@+id/buttonDetect"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="检测"/>
</LinearLayout>
<TextView
android:id="@+id/infoResult"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_above="@id/btn_ll"
android:hint="预测结果会在这里显示"
android:textSize="16sp" />
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/infoResult"
android:layout_alignParentTop="true"
android:layout_marginTop="1dp"
android:layout_marginBottom="-1dp" />
</RelativeLayout>
(4)修改java层代码src/main/java
SqueezeNcnn层代码,
public class SqueezeNcnn
{
public native boolean Init(byte[] param, byte[] bin, byte[] words);
public native String Detect(Bitmap bitmap);
static {
System.loadLibrary("squeeze");
}
}
MainActivity层代码,
public class MainActivity extends AppCompatActivity {
private static final int SELECT_IMAGE = 1;
private TextView infoResult;
private ImageView imageView;
private Bitmap yourSelectedImage = null;
private SqueezeNcnn squeezencnn = new SqueezeNcnn();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try
{
initSqueezeNcnn();
}
catch (IOException e)
{
Log.e("MainActivity", "initSqueezeNcnn error");
}
infoResult = (TextView) findViewById(R.id.infoResult);
imageView = (ImageView) findViewById(R.id.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);
}
});
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);
if (result == null)
{
infoResult.setText("detect failed");
}
else
{
infoResult.setText(result);
}
}
});
}
private void initSqueezeNcnn() throws IOException
{
byte[] param = null;
byte[] bin = null;
byte[] words = null;
{
InputStream assetsInputStream = getAssets().open("squeezenet_v1.1.param.bin");
int available = assetsInputStream.available();
param = new byte[available];
int byteCode = assetsInputStream.read(param);
assetsInputStream.close();
}
{
InputStream assetsInputStream = getAssets().open("squeezenet_v1.1.bin");
int available = assetsInputStream.available();
bin = new byte[available];
int byteCode = assetsInputStream.read(bin);
assetsInputStream.close();
}
{
InputStream assetsInputStream = getAssets().open("synset_words.txt");
int available = assetsInputStream.available();
words = new byte[available];
int byteCode = assetsInputStream.read(words);
assetsInputStream.close();
}
squeezencnn.Init(param, bin, words);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK && null != data) {
Uri selectedImage = data.getData();
try
{
if (requestCode == SELECT_IMAGE) {
Bitmap bitmap = decodeUri(selectedImage);
Bitmap rgba = bitmap.copy(Bitmap.Config.ARGB_8888, true);
// resize to 227x227
yourSelectedImage = Bitmap.createScaledBitmap(rgba, 227, 227, false);
imageView.setImageBitmap(yourSelectedImage);
}
}
catch (FileNotFoundException e)
{
Log.e("MainActivity", "FileNotFoundException");
return;
}
}
}
private Bitmap decodeUri(Uri selectedImage) throws FileNotFoundException
{
// Decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(getContentResolver().openInputStream(selectedImage), null, o);
// The new size we want to scale to
final int REQUIRED_SIZE = 400;
// Find the correct scale value. It should be the power of 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);
}
}
(5)修改cpp层代码,squeezencnn_jni.cpp
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 jboolean JNICALL
Java_com_example_squeezeclassify_SqueezeNcnn_Init(JNIEnv *env, jobject thiz, jbyteArray param,
jbyteArray bin, jbyteArray words) {
// TODO: implement Init()
{
int len = env->GetArrayLength(param);
squeezenet_param.resize(len);
env->GetByteArrayRegion(param, 0, len, (jbyte*)squeezenet_param.data());
int ret = squeezenet.load_param(squeezenet_param.data());
__android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "load_param %d %d", ret, len);
}
// init bin
{
int len = env->GetArrayLength(bin);
squeezenet_bin.resize(len);
env->GetByteArrayRegion(bin, 0, len, (jbyte*)squeezenet_bin.data());
int ret = squeezenet.load_model(squeezenet_bin.data());
__android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "load_model %d %d", ret, len);
}
// init words
{
int len = env->GetArrayLength(words);
std::string words_buffer;
words_buffer.resize(len);
env->GetByteArrayRegion(words, 0, len, (jbyte*)words_buffer.data());
squeezenet_words = split_string(words_buffer, "\n");
}
return JNI_TRUE;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_squeezeclassify_SqueezeNcnn_Detect(JNIEnv *env, jobject thiz, jobject bitmap) {
// TODO: implement Detect()
bench_start();
// ncnn from bitmap
ncnn::Mat in;
{
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;
void* indata;
AndroidBitmap_lockPixels(env, bitmap, &indata);
in = ncnn::Mat::from_pixels((const unsigned char*)indata, ncnn::Mat::PIXEL_RGBA2BGR, width, height);
AndroidBitmap_unlockPixels(env, bitmap);
}
// squeezenet
// return top class
int top_class = 0;
float max_score = 0.f;
{
const float mean_vals[3] = {104.f, 117.f, 123.f};
in.substract_mean_normalize(mean_vals, 0);
ncnn::Extractor ex = squeezenet.create_extractor();
ex.set_light_mode(true);
ex.set_num_threads(4);
ex.input(squeezenet_v1_1_param_id::BLOB_data, in);
ncnn::Mat out;
ex.extract(squeezenet_v1_1_param_id::BLOB_prob, out);
for (int j=0; j<out.w; j++)
{
if (out[j]>max_score){
max_score =out[j];
top_class = j;
}
}
}
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());
bench_end("detect");
return result;
}
(6)修改CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
#include头文件目录
include_directories(include)
#添加ncnn库
#source directory源文件目录
file(GLOB squeeze_SRC *.h
*.cpp)
set(squeeze_COMPILE_CODE ${squeeze_SRC})
add_library(libncnn STATIC IMPORTED )
set_target_properties(libncnn
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libncnn.a)
add_library( # Sets the name of the library.
squeeze ## 为生成.so的文字最好直接和.c名字一样,需要更改
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
${squeeze_COMPILE_CODE})##cpp文件的name
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 )
target_link_libraries( # Specifies the target library.
squeeze
libncnn
android
jnigraphics
# Links the target library to the log library
# included in the NDK.
${log-lib} )
(7)修改app/build.gradle
externalNativeBuild {
cmake {
arguments "-DANDROID_TOOLCHAIN=clang"
cFlags "-fopenmp -O2 -fvisibility=hidden -fomit-frame-pointer -fstrict-aliasing -ffunction-sections -fdata-sections -ffast-math "
cppFlags "-fopenmp -O2 -fvisibility=hidden -fvisibility-inlines-hidden -fomit-frame-pointer -fstrict-aliasing -ffunction-sections -fdata-sections -ffast-math "
arguments "-DANDROID_STL=c++_shared", "-DANDROID_CPP_FEATURES=rtti exceptions"
cppFlags ""
cppFlags "-std=c++11"
cppFlags "-frtti"
cppFlags "-fexceptions"
}
}
ndk {
abiFilters 'armeabi-v7a'// , 'arm64-v8a' //,'x86', 'x86_64', 'armeabi'
stl "gnustl_static"
}
整体目录结构:
测试结果: