NCNN

ncnn 是腾讯提供的移动端框架 非常时候在手机玩

ncnn 是一个为手机端极致优化的高性能神经网络前向计算框架。ncnn 从设计之初深刻考虑手机端的部署和使用。无第三方依赖,跨平台,手机端 cpu 的速度快于目前所有已知的开源框架。基于 ncnn,开发者能够将深度学习算法轻松移植到手机端高效执行,开发出人工智能 APP,将 AI 带到你的指尖。ncnn 目前已在腾讯多款应用中使用,如 QQ,Qzone,微信,天天P图等。

 

 

功能概述

  • 支持卷积神经网络,支持多输入和多分支结构,可计算部分分支
  • 无任何第三方库依赖,不依赖 BLAS/NNPACK 等计算框架
  • 纯 C++ 实现,跨平台,支持 android ios 等
  • ARM NEON 汇编级良心优化,计算速度极快
  • 精细的内存管理和数据结构设计,内存占用极低
  • 支持多核并行计算加速,ARM big.LITTLE cpu 调度优化
  • 支持基于全新低消耗的 vulkan api GPU 加速
  • 整体库体积小于 700K,并可轻松精简到小于 300K
  • 可扩展的模型设计,支持 8bit 量化和半精度浮点存储,可导入 caffe/pytorch/mxnet/onnx/darknet 模型
  • 支持直接内存零拷贝引用加载网络模型
  • 可注册自定义层实现并扩展
  • 恩,很强就是了,不怕被塞卷 QvQ

 

demo功能演示

1.人脸识别

2.身份证,银行卡识别

3.语音识别

4.车票识别

5.人体部位识别

 

DEMO解决了几个问题:

1.替换了模型,检测不出结果的问题

2.模型加载不了的问题或者奔溃

3.模型加载耗时6s。问题修复

4.识别内存溢出问题修复

 

demo地址:模型加载和自动识别

https://github.com/nihui/ncnn-android-mobilenetssd

3分钟实现demo:

step1

https://github.com/Tencent/ncnn/releases

download ncnn-android-vulkan-lib.zip or build ncnn for android yourself

下载库ncnn-android-vulkan-lib:

step2

extract ncnn-android-vulkan-lib.zip into app/src/main/jni or change the ncnn path to yours in app/src/main/jni/CMakeLists.txt

配置cmake

 

android ios 预编译库 20200616 622879a

 

编译版本,默认配置,android-ndk-r21d,cctools-port 895 + ld64-274.2 + ios 10.2 sdk libc++
ncnn-android-lib 是 android 的静态库(armeabi-v7a + arm64-v8a + x86 + x86_64)
ncnn-android-vulkan-lib 是 android 的静态库(armeabi-v7a + arm64-v8a + x86 + x86_64,包含vulkan支持)
ncnn.framework.zip 是 ios 的静态库(armv7 + arm64 + i386 + x86_64,bitcode)
ncnn-vulkan.framework.zip 是 ios 的静态库(arm64 + x86_64,bitcode,包含vulkan支持,MoltenVK-1.1.82.0)
openmp.framework.zip 是 ios ncnn openmp 运行时静态库(armv7 + arm64 + i386 + x86_64,bitcode)

adreno gpu image存储+fp16p/fp16s/fp16pa/fp16sa优化,在qcom855之前的高通芯片上默认启用,包括全部gpu shader
新增darknet转换器,支持yolov4和efficientnetb0-yolov3(by zhiliu6)
新增simplestl,可替代std::string/std::vector,默认不启用(by scarsty)
新增NCNN_LOGE宏,android自动在adb logcat输出信息(by maxint)
运行时生成spirv,大幅减小gpu库体积
新增python绑定链接
新增查询当前可用gpu显存接口
gpu fp16/fp32转换,buffer/image

 

源码分析:

第一步:加载模型。量化模型

// init param
{
    int ret = mobilenetssd.load_param(mgr, "mobilenet_ssd_voc_ncnn.param");
    if (ret != 0)
    {
        __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "load_param failed");
        return JNI_FALSE;
    }
    __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "load_param success");
}

// init bin
{
    int ret = mobilenetssd.load_model(mgr, "mobilenet_ssd_voc_ncnn.bin");
    if (ret != 0)
    {
        __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "load_model failed");
        return JNI_FALSE;
    }

    __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "load_model success");
}

第二步:初始化java需要的一些参数

// init jni glue
jclass localObjCls = env->FindClass("com/tencent/mobilenetssdncnn/MobilenetSSDNcnn$Obj");
objCls = reinterpret_cast<jclass>(env->NewGlobalRef(localObjCls));

__android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "start");

constructortorId = env->GetMethodID(objCls, "<init>", "(Lcom/tencent/mobilenetssdncnn/MobilenetSSDNcnn;)V");

xId = env->GetFieldID(objCls, "x", "F");
yId = env->GetFieldID(objCls, "y", "F");
wId = env->GetFieldID(objCls, "w", "F");
hId = env->GetFieldID(objCls, "h", "F");
labelId = env->GetFieldID(objCls, "label", "Ljava/lang/String;");
probId = env->GetFieldID(objCls, "prob", "F");

__android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "end");

第三步:识别模型

}

double start_time = ncnn::get_current_time();

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

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

// mobilenetssd
std::vector<Object> objects;
{
    const float mean_vals[3] = { 98.1f, 106.0f, 133.1f };
    const float norm_vals[3] = { 0.2207f, 0.2281f, 0.2640f };
    in.substract_mean_normalize(mean_vals, norm_vals);

    ncnn::Extractor ex = mobilenetssd.create_extractor();

    ex.set_vulkan_compute(use_gpu);

    ex.input("data", in);

    ncnn::Mat out;
    ex.extract("detection_out", out);

    for (int i=0; i<out.h; i++)
    {
        const float* values = out.row(i);
        if(values[0]==1&&values[1]>0.7){
            Object object;
            object.label = values[0];
            object.prob = values[1];
            object.x = values[2] * width;
            object.y = values[3] * height;
            object.w = values[4] * width - object.x;
            object.h = values[5] * height - object.y;

            objects.push_back(object);
        } else{
            continue;
        }

    }
}

第4步:根据坐标进行车子的绘制

    for (int i = 0; i < objects.length; i++)

    {

        Log.d("peng","objects"+objects[i].toString());
        canvas.drawRect(objects[i].x, objects[i].y, objects[i].x + objects[i].w, objects[i].y + objects[i].h, paint);

        // draw filled text inside image
        {
            String text = objects[i].label + " = " + String.format("%.1f", objects[i].prob * 100) + "%";

            float text_width = textpaint.measureText(text);
            float text_height = - textpaint.ascent() + textpaint.descent();

            float x = objects[i].x;
            float y = objects[i].y - text_height;
            if (y < 0)
                y = 0;
            if (x + text_width > rgba.getWidth())
                x = rgba.getWidth() - text_width;

            canvas.drawRect(x, y, x + text_width, y + text_height, textbgpaint);

            canvas.drawText(text, x, y - textpaint.ascent(), textpaint);
        }
    }

    imageView.setImageBitmap(rgba);
}
#include <android/asset_manager_jni.h>
#include <android/bitmap.h>
#include <android/log.h>

#include <jni.h>

#include <string>
#include <vector>
#include <gpu.h>

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

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

static ncnn::Net mobilenetssd;

struct Object
{
    float x;
    float y;
    float w;
    float h;
    int label;
    float prob;
};

extern "C" {

// FIXME DeleteGlobalRef is missing for objCls
static jclass objCls = NULL;
static jmethodID constructortorId;
static jfieldID xId;
static jfieldID yId;
static jfieldID wId;
static jfieldID hId;
static jfieldID labelId;
static jfieldID probId;

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "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, "MobilenetSSDNcnn", "JNI_OnUnload");

    ncnn::destroy_gpu_instance();
}

// public native boolean Init(AssetManager mgr);
JNIEXPORT jboolean JNICALL Java_com_tencent_mobilenetssdncnn_MobilenetSSDNcnn_Init(JNIEnv* env, jobject thiz, jobject assetManager)
{
    ncnn::Option opt;
    opt.lightmode = true;
    opt.num_threads = 4;
    opt.blob_allocator = &g_blob_pool_allocator;
    opt.workspace_allocator = &g_workspace_pool_allocator;
    opt.use_packing_layout = true;

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

    AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);

    mobilenetssd.opt = opt;

    // init param
    {
        int ret = mobilenetssd.load_param(mgr, "mobilenet_ssd_voc_ncnn.param");
        if (ret != 0)
        {
            __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "load_param failed");
            return JNI_FALSE;
        }
        __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "load_param success");
    }

    // init bin
    {
        int ret = mobilenetssd.load_model(mgr, "mobilenet_ssd_voc_ncnn.bin");
        if (ret != 0)
        {
            __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "load_model failed");
            return JNI_FALSE;
        }

        __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "load_model success");
    }

    // init jni glue
    jclass localObjCls = env->FindClass("com/tencent/mobilenetssdncnn/MobilenetSSDNcnn$Obj");
    objCls = reinterpret_cast<jclass>(env->NewGlobalRef(localObjCls));

    __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "start");

    constructortorId = env->GetMethodID(objCls, "<init>", "(Lcom/tencent/mobilenetssdncnn/MobilenetSSDNcnn;)V");

    xId = env->GetFieldID(objCls, "x", "F");
    yId = env->GetFieldID(objCls, "y", "F");
    wId = env->GetFieldID(objCls, "w", "F");
    hId = env->GetFieldID(objCls, "h", "F");
    labelId = env->GetFieldID(objCls, "label", "Ljava/lang/String;");
    probId = env->GetFieldID(objCls, "prob", "F");

    __android_log_print(ANDROID_LOG_DEBUG, "MobilenetSSDNcnn", "end");

    return JNI_TRUE;
}

// public native Obj[] Detect(Bitmap bitmap, boolean use_gpu);
JNIEXPORT jobjectArray JNICALL Java_com_tencent_mobilenetssdncnn_MobilenetSSDNcnn_Detect(JNIEnv* env, jobject thiz, jobject bitmap, jboolean use_gpu)
{
    if (use_gpu == JNI_TRUE && ncnn::get_gpu_count() == 0)
    {
        return NULL;
        //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 (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
        return NULL;

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

    // mobilenetssd
    std::vector<Object> objects;
    {
        const float mean_vals[3] = { 98.1f, 106.0f, 133.1f };
        const float norm_vals[3] = { 0.2207f, 0.2281f, 0.2640f };
        in.substract_mean_normalize(mean_vals, norm_vals);

        ncnn::Extractor ex = mobilenetssd.create_extractor();

        ex.set_vulkan_compute(use_gpu);

        ex.input("data", in);

        ncnn::Mat out;
        ex.extract("detection_out", out);

        for (int i=0; i<out.h; i++)
        {
            const float* values = out.row(i);
            if(values[0]==1&&values[1]>0.7){
                Object object;
                object.label = values[0];
                object.prob = values[1];
                object.x = values[2] * width;
                object.y = values[3] * height;
                object.w = values[4] * width - object.x;
                object.h = values[5] * height - object.y;

                objects.push_back(object);
            } else{
                continue;
            }

        }
    }

    // objects to Obj[]
    static const char* class_names[] = {"background",
        "tongue", "furred-tongue", "tooth-mark", "crack"};

    jobjectArray jObjArray = env->NewObjectArray(objects.size(), objCls, NULL);

    for (size_t i=0; i<objects.size(); i++)
    {
        jobject jObj = env->NewObject(objCls, constructortorId, thiz);

        env->SetFloatField(jObj, xId, objects[i].x);
        env->SetFloatField(jObj, yId, objects[i].y);
        env->SetFloatField(jObj, wId, objects[i].w);
        env->SetFloatField(jObj, hId, objects[i].h);
        env->SetObjectField(jObj, labelId, env->NewStringUTF(class_names[objects[i].label]));
        env->SetFloatField(jObj, probId, objects[i].prob);

        env->SetObjectArrayElement(jObjArray, i, jObj);
    }

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

    return jObjArray;
}

}

 

 

 

 

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐