AOE工程实践-NCNN组件
作者:杨科
NCNN是腾讯开源的一个为手机端极致优化的高性能神经网络前向计算框架。在AOE开源工程里,我们提供了NCNN组件,下面我们以SqueezeNet物体识别这个Sample为例,来讲一讲NCNN组件的设计和用法。
直接集成NCNN缺点
为SqueezeNet接入NCNN,把相关的模型文件,NCNN的头文件和库,JNI调用,前处理和后处理相关业务逻辑等。把这些内容都放在SqueezeNet Sample工程里。这样简单直接的集成方法,问题也很明显,和业务耦合比较多,不具有通用性,前处理后处理都和SqueezeNcnn这个Sample有关,不能很方便地提供给其他业务组件使用。深入思考一下,如果我们把AI业务,作为一个一个单独的AI组件提供给业务的同学使用,会发生这样的情况:

每个组件都要依赖和包含NCNN的库,而且每个组件的开发同学,都要去熟悉NCNN的接口,写C的调用代码,写JNI。所以我们很自然地会想到要提取一个NCNN的组件出来,例如这样:

AOE SDK里的NCNN组件
在AOE开源SDK里,我们提供了NCNN组件,下面我们从4个方面来讲一讲NCNN组件:
- NCNN组件的设计
- 对SqueezeNet Sample的改造
- 应用如何接入NCNN组件
- 对NCNN组件的一些思考
NCNN组件的设计
NCNN组件的设计理念是组件里不包含具体的业务逻辑,只包含对NCNN接口的封装和调用。具体的业务逻辑,由业务方在外部实现。在接口定义和设计上,我们参考了TF Lite的源码和接口设计。目前提供的对外调用接口,主要有以下几个:
// 加载模型和param
void loadModelAndParam(...)
// 初始化是否成功
boolean isLoadModelSuccess()
// 输入rgba数据
void inputRgba(...)
// 进行推理
void run(...)
// 多输入多输出推理
void runForMultipleInputsOutputs(...)
// 得到推理结果
Tensor getOutputTensor(...)
// 关闭和清理内存
void close()
新的代码结构如下:
├── AndroidManifest.xml
├── cpp
│ └── ncnn
│ ├── c_api_internal.h
│ ├── include
│ ├── interpreter.cpp
│ ├── Interpreter.h
│ ├── jni_util.cpp
│ ├── jni_utils.h
│ ├── nativeinterpreterwrapper_jni.cpp
│ ├── nativeinterpreterwrapper_jni.h
│ ├── tensor_jni.cpp
│ └── tensor_jni.h
├── java
│ └── com
│ └── didi
│ └── aoe
│ └── runtime
│ └── ncnn
│ ├── Interpreter.java
│ ├── NativeInterpreterWrapper.java
│ └── Tensor.java
└── jniLibs
├── arm64-v8a
│ └── libncnn.a
└── armeabi-v7a
└── libncnn.a
- Interpreter,提供给外部调用,提供模型加载,推理这些方法。
- NativeInterpreterWrapper是具体的实现类,里面对native进行调用。
- Tensor,主要是一些数据和native层的交互。
AOE NCNN组件有以下几个特点:
- 支持多输入多输出。
- 使用ByteBuffer来提升效率。
- 使用Object作为输入和输出(实际支持了ByteBuffer和多维数组)。
下面我们来说说具体是如何做的。
如何支持多输入多输出
为了支持多输入和多输出,我们在Native层创建了一个Tensor对象的列表,每个Tensor对象里保存了相关的输入和输出数据。Native层的Tensor对象,通过tensor_jni提供给java层调用,java层维护这个指向native层tensor的“指针”地址。这样在有多输入和多输出的时候,只要拿到这个列表里的对应的Tensor,就可以就行数据的操作了。
ByteBuffer的使用
ByteBuffer,字节缓存区处理子节的,比传统的数组的效率要高。
DirectByteBuffer,使用的是堆外内存,省去了数据到内核的拷贝,因此效率比用ByteBuffer要高。
当然ByteBuffer的使用方法不是我们要说的重点,我们说说使用了ByteBuffer以后,给我们带来的好处:
1,接口里的字节操作更加便捷,例如里面的putInt,getInt,putFloat,getFloat,flip等一系列接口,可以很方便的对数据进行操作。
2,和native层做交互,使用DirectByteBuffer,提升了效率。我们可以简单理解为java层和native层可以直接对一块“共享”内存进行操作,减少了中间的字节的拷贝过程。
如何使用Object作为输入和输出
目前我们只支持了ByteBuffer和MultiDimensionalArray。在实际的操作过程中,如果是ByteBuffer,我们会判断是否是direct buffer,来进行不同的读写操作。如果是MultiDimensionalArray,我们会根据不同的数据类型(例如int, float等),维度等,来对数据进行读写操作。
对SqueezeNet Sample的改造
集成AOE NCNN组件以后,让SqueezeNet依赖NCNN Module,SqueezeNet Sample里面只包含了模型文件,前处理和后处理相关的业务逻辑,前处理和后处理可以用java,也可以用c来实现,由具体的业务实现来决定。新的代码结构变得非常简洁,目录如下:
├── AndroidManifest.xml
├── assets
│ └── squeeze
│ ├── model.config
│ ├── squeezenet_v1.1.bin
│ ├── squeezenet_v1.1.id.h
│ ├── squeezenet_v1.1.param.bin
│ └── synset_words.txt
└── java
└── com
└── didi
└── aoe
└── features
└── squeeze
└── SqueezeInterpreter.java
其他的AI业务组件对NCNN组件的调用,都可以参考SqueezeNet这个Sample。
应用如何接入NCNN组件
对NCNN组件的接入,有两种方式
直接接入

通过AOE SDK接入

两种接入方式比较:
| 功能特性 | 直接接入 | 通过AOE SDK接入 |
|---|---|---|
| 易用性 | 容易 | 容易 |
| 稳定性 | 不能确定,依赖实现方的实现 | 高,安卓有独立进程机制,更加稳定,推理过程不影响主进程 |
| 模型配置 | 无 | 有 |
| 模型下载和动态升级 | 无 | 有 |
| 模型配置 | 无 | 有 |
| 模型准确率,性能等数据分析 | 无 | 有 |
| 提供图像处理工具包 | 无 | AOE SDK提供了Vision组件 |
| 支持模型加密 | 要看具体的模型和框架 | AOE SDK提供了模型加密解密组件 |
通过比较,我们更建议是通过AOE SDK来对我们的NCNN组件进行接入。
对NCNN组件的总结和思考
通过对NCNN组件的封装,现在业务集成NCNN更加快捷方便了。之前我们一个新的业务集成NCNN,可能需要半天到一天的时间。使用AOE NCNN组件以后,可能只需要1-2小时的时间。
当然NCNN组件目前还存在很多不完善的地方,我们对NCNN还需要去加深学习和理解。后面会通过不断的学习,持续的对NCNN组件进行改造和优化。
欢迎大家来使用和提建议
AoE (AI on Edge,终端智能,边缘计算) 是一个终端侧AI集成运行时环境 (IRE),帮助开发者提升效率。 https://github.com/didi/aoe
Github地址:

欢迎star~
AOE工程实践-NCNN组件的更多相关文章
- AES加密原理和AOE工程实践
在AI业务的开发的过程中,我们常常需要对模型文件进行加密.我们从以下几个方面来说一说AES的加密原理以及AOE里的工程实践. 常见的加密算法 AOE对模型加密需求的思考 AES的加密原理 AOE工程实 ...
- AOE工程实践-银行卡OCR里的图像处理
作者:杨科 近期我们开发了一个银行卡 OCR 项目.需求是用手机对着银行卡拍摄以后,通过推理,可以识别出卡片上的卡号. 工程开发过程中,我们发现手机拍摄以后的图像,并不能满足模型的输入要求.以 And ...
- 不加班的秘诀:如何通过AOE快速集成NCNN?
作为我司头发储量前三的程序员 始终仗着头发多奋斗在加班的第一线 时时灵魂拷问自己 年轻人,你凭什么不加班? 虽然我没有女朋友但是,我有代码呀 但我不明白的是,隔壁工位那个,到岗比我迟,下班比我早,天天 ...
- webpack 从入门到工程实践
from:https://www.jianshu.com/p/9349c30a6b3e?utm_campaign=maleskine&utm_content=note&utm_medi ...
- 基于menu小插件探索工程实践
目录 一.准备工作 1.C/C++环境搭建 2.VSCode的配置 (1) 安装插件: (2) 设置配置文件: 二.工程化编程实战 1.模块化设计 2.可重用设计:进一步抽象 menu的进一步优化 可 ...
- panguan(判官):一个自研的任务执行引擎的工程实践
来某厂接近半年了,几乎没写过C++代码,说实话还真的有点手生.最近刚好有一个需求,然而我感觉我也没有办法用C++以外的语言去实现它.于是还是花了几天时间用C++完成编码,这是一个简单的任务执行引擎,它 ...
- 公司简介 - CCDI悉地国际-工程实践专业服务的引领者
公司简介 - CCDI悉地国际-工程实践专业服务的引领者 关于悉地国际 CCDI悉地国际(以下简称"CCDI")创立于1994年,是在城市建设和开发领域从事综合专业 ...
- Linux开源模块迁移概述暨交叉编译跨平台移植总结--从《嵌入式Linux驱动模板简洁和工程实践》
本文摘录<嵌入式Linux驱动模板简洁和工程实践>一本书"开发和调试技术". Linux强大的是,有那么多的开源项目可以使用.通常非常需要可以通过寻找相关的源模块被定义 ...
- LDA工程实践之算法篇之(一)算法实现正确性验证(转)
研究生二年级实习(2010年5月)开始,一直跟着王益(yiwang)和靳志辉(rickjin)学习LDA,包括对算法的理解.并行化和应用等等.毕业后进入了腾讯公司,也一直在从事相关工作,后边还在yiw ...
随机推荐
- Go中的指针
学Java以来,让程序员忽略了指针和内存地址这些概念,Java帮我们封装了对象,简化了对象引用之间的关系.在Go语言中,又帮我们回忆起这些概念. 我们创建的每一个对象在内存中都有一个位置去存储,每个内 ...
- Scala集合(四)
1. 集合 集合主要有三种: Sequence Map Set sequence是一种线性元素的集合,可能会是索引或者线性的(链表).map是包含键值对的集合,就像Java的Map,set是包含无重复 ...
- OSGi Bundle之Hello World
开发一个简单的Hello World的OSGi Bundle(OSGi绑定包) 在OSGi中,软件是以Bundle的形式发布的.一个Bundle由Java类和其它资源构成,它可为其它的Bundle提供 ...
- android ——悬浮按钮及可交互提示
一.悬浮按钮 FloatingActionButton是Design Support中的一个控件,它会默认colorAccent作为按钮的颜色,还可以给按钮一个图标. 这是没有图标的,这是有图标的. ...
- 在Docker for Windows中运行GUI程序
Docker运行GUI原理 Docker目前大多应用在服务器领域,那么在Docker中可以运行GUI程序吗?怀着好奇心google了一番,还真有人写了一篇文章 running-gui-applicat ...
- mybatis+spring+springMVC处理org.springframework.beans.factory.BeanDefinitionStoreException:java.lang.IllegalArgumentException异常
java.lang.IllegalArgumentException异常有三种情况 org.springframework.beans.factory.BeanDefinitionStoreExcep ...
- css3弹性盒子 flex布局
CSS3 弹性盒 1.display:flex 说明: 设置为弹性盒(父元素添加) 2.flex-direction(主轴排列方式) 说明: 顺序指定了弹性子元素在父容器中的位置 row 默认在一行内 ...
- Nacos(七):Nacos共享配置
前言 本文参考文章: SpringCloud Alibaba - Nacos Config 自定义共享配置 前景回顾: Nacos(六):多环境下如何"管理"及"隔离&q ...
- 初学HTML5做的小知识点
新增的HTML5标签 语义化标签 :<header> 头标签 <nav> 导航标签 <section> 表示文档的结构.栏目 < ...
- Spring学习之旅(三)--装配Bean
装配 Bean 的方式 在 XML 中进行显式配置 在 Java 中进行显式配置 隐式的 Bean 发现机制和自动装配 Spring 提供了以上三种方式进行 Bean 的配置,可以根据自己的需求选择一 ...