ncnn填坑记录八:将自己训练的模型打包为APK并部署到安卓端运行
上一篇:ncnn填坑记录七:examples/squeezenet.cpp代码阅读
做一个分类任务,模型选取的mobilenetv3,训练好模型,并按前文依次转换为onnx、ncnn后,参考官方https://github.com/nihui/ncnn-android-squeezenet进行修改。
一. 在PC端运行
ncnn/examples
文件夹下:squeezenet.cpp -->mobilenetv3.cpp
修改如下:
#include "net.h"#include <algorithm>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <stdio.h>
#include <vector>static int detect_mobilenetv3(const cv::Mat& bgr, std::vector<float>& cls_scores)
{// 申明Net对象ncnn::Net mobilenetv3; // 使用GPUmobilenetv3.opt.use_vulkan_compute = true; // the ncnn model https://github.com/nihui/ncnn-assets/tree/master/models// 加载模型网络结构mobilenetv3.load_param("mobilenetv3.param");// 加载模型权重参数mobilenetv3.load_model("mobilenetv3.bin");// 将图片转换为ncnn的格式,并resize到227*227,后面4个参数为原图w,h,缩放后的w,h// 还可以实现BGR的转换,如PIXEL_RGB2GRAY、RGB2BGR,若模型输入为BGR,则用PIXEL_BGR;若模型输入为RGB,则用PIXEL_BGR2RGBncnn::Mat in = ncnn::Mat::from_pixels_resize(bgr.data, ncnn::Mat::PIXEL_RGB, bgr.cols, bgr.rows, 224, 224);const float mean_vals[3] = {123.68f, 116.28f, 103.53f};const float std_vals[3] = {0.0171f, 0.0175f, 0.0174f};// 对图片进行归一化in.substract_mean_normalize(mean_vals, std_vals);// 实例化Extractorncnn::Extractor ex = mobilenetv3.create_extractor();// 可以设置线程个数,加快计算// ex.set_num_threads(4);// 设置执行器是否使用轻量模式// ex.set_light_mode(true); // 输入,将图片放入网络中,进行前向推理,"data"换为自己网络的输入名ex.input("input.1", in);ncnn::Mat out;// 提取输出,"prob"换为自己网络的输出名ex.extract("653", out);// printf(out);// 将out中的值转化为cls_scores,返回不同类别的得分cls_scores.resize(out.w);for (int j = 0; j < out.w; j++){cls_scores[j] = out[j];}return 0;
}static int print_topk(const std::vector<float>& cls_scores, int topk)
{// partial sort topk with indexint size = cls_scores.size();// 声明一个pair容器std::vector<std::pair<float, int> > vec;vec.resize(size);for (int i = 0; i < size; i++){vec[i] = std::make_pair(cls_scores[i], i);}// 部分排序算法,只要topk的,返回一个当前vector容器中起始元素的迭代器// greator,降序排列,应该是使用第一个floatstd::partial_sort(vec.begin(), vec.begin() + topk, vec.end(),std::greater<std::pair<float, int> >());// print topk and scorefor (int i = 0; i < topk; i++){float score = vec[i].first;int index = vec[i].second;fprintf(stderr, "%d = %f\n", index, score);}return 0;
}// argv有2个参数,argv[0]指向程序中的可执行文件的文件名,arg[1]表示imagepath
int main(int argc, char** argv)
{if (argc != 2){fprintf(stderr, "Usage: %s [imagepath]\n", argv[0]);return -1;}const char* imagepath = argv[1];cv::Mat m = cv::imread(imagepath, 1);// cv读出的图片为空if (m.empty()){fprintf(stderr, "cv::imread %s failed\n", imagepath);return -1;}// 定义用来存储最终各类别的得分std::vector<float> cls_scores;// 调用推理函数detect_mobilenetv3(m, cls_scores);// 打印top3print_topk(cls_scores, 1);return 0;
}
需要注意的是,模型的输入输出用netron打开后查找
对图片进行归一化的mena_vals和std_vals按实际修改
由于我是用PIL读取读片进行训练的,故此处改为ncnn::Mat::PIXEL_RGB
(可能有点问题?)
打印top1即可。
同文件夹下,CMakeLists.txt
添加:ncnn_add_example(mobilenetv3)
编译后运行,分类的结果能对上
(base) lgy@lgy:~/tools/ncnn/build/examples$ ./mobilenetv3 ./2_95.jpg
[0 GeForce GTX 1060 6GB] queueC=2[8] queueG=0[16] queueT=1[2]
[0 GeForce GTX 1060 6GB] bugsbn1=0 bugbilz=0 bugcopc=0 bugihfa=0
[0 GeForce GTX 1060 6GB] fp16-p/s/a=1/1/0 int8-p/s/a=1/1/1
[0 GeForce GTX 1060 6GB] subgroup=32 basic=1 vote=1 ballot=1 shuffle=1
1 = 2.361328
(base) lgy@lgy:~/tools/ncnn/build/examples$ ./mobilenetv3 ./1_114.jpg
[0 GeForce GTX 1060 6GB] queueC=2[8] queueG=0[16] queueT=1[2]
[0 GeForce GTX 1060 6GB] bugsbn1=0 bugbilz=0 bugcopc=0 bugihfa=0
[0 GeForce GTX 1060 6GB] fp16-p/s/a=1/1/0 int8-p/s/a=1/1/1
[0 GeForce GTX 1060 6GB] subgroup=32 basic=1 vote=1 ballot=1 shuffle=1
0 = 3.609375
二. 在安卓端运行
app/src/main/jni文件夹内
1.解压 ncnn-20210525-android-vulkan
至该文件夹下
2.CMakeLists.txt
里
squeezencnn_jni.cpp
改为 mobilenetv3ncnn_jni.cpp
project(squeezencnn)cmake_minimum_required(VERSION 3.10)set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20210525-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn)
find_package(ncnn REQUIRED)add_library(squeezencnn SHARED mobilenetv3ncnn_jni.cpp)target_link_libraries(squeezencnn ncnn)
3.squeezenet_v1.1.id.h
是去除可见字符串得到的
(base) lgy@lgy:~/tools/ncnn/build/tools$ ./ncnn2mem mobilenetv3.param mobilenetv3.bin mobilenetv3.id.h mobilenetv3.mem.h
mobilenetv3.id.h
移至该文件夹下
mobilenetv3.bin
与 mobilenetv3.param.bin
移至 my_ncnn-android-mobilenetv3/app/src/main/assets
文件夹下
app/src/main/assets/synset_words.txt
修改为自己的类别
A
B
4.mobilenetv3ncnn_jni.cpp
修改:
①.头文件
#include "mobilenetv3.id.h"
②.权重导入
这是加密后的权重导入,明文的换另一种导入方式
int ret = squeezenet.load_param_bin(mgr, "mobilenetv3.param.bin");
int ret = squeezenet.load_model(mgr, "mobilenetv3.bin");
③.图片resize
if (width != 227 || height != 227)return NULL;if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)return NULL;// ncnn from bitmapncnn::Mat in = ncnn::Mat::from_android_bitmap(env, bitmap, ncnn::Mat::PIXEL_BGR);
可以看到,mobilenetv3ncnn_jni.cpp
只有判断是否为227的代码,并无resize的。
在src/main/java/com/tencent/squeezencnn/MainActivity.java
里
// resize to 227x227
yourSelectedImage = Bitmap.createScaledBitmap(rgba, 227, 227, false);
将227都改为224。
rgba
为读入图片的格式,上面ncnn::Mat::PIXEL_BGR
为格式转换,
由于我是用PIL读取读片进行训练的,故此处改为ncnn::Mat::PIXEL_RGB
(可能有点问题?)
④.图片归一化
const float mean_vals[3] = {123.68f, 116.28f, 103.53f};const float std_vals[3] = {0.0171f, 0.0175f, 0.0174f};in.substract_mean_normalize(mean_vals, std_vals);
⑤.输入输出
ex.input(mobilenetv3_param_id::BLOB_input_1, in);ncnn::Mat out;ex.extract(mobilenetv3_param_id::BLOB_653, out);
⑥.输出标签
std::string result_str = std::string(word.c_str()) + " = " + tmp;
原来为
std::string result_str = std::string(word.c_str() + 10) + " = " + tmp;// +10 to skip leading n03179701
是因为squeezenet的synset_words.txt
为:
n01440764 tench, Tinca tinca
n01443537 goldfish, Carassius auratus
......
前面刚好有9位数加1个空格。
5.app改名
/app/src/main/AndroidManifest.xml
<application android:label="my_mobilenetv3ncnn" > // 这是安装app时屏幕上显示的名字<activity android:name="MainActivity"android:label="****分类"> // 这是安装后app的名字和界面左上角显示的名字
/app/build.gradle
applicationId "com.tencent.mobilenetv3ncnn" // 这是生成的安装包的名字
6.同一个项目生成多个apk安装到同一手机上
由于我使用mobilenetv3训练了2个不同的分类数据集,项目是同一个,只改了相关数据集,在生成第二个项目的apk安装到手机上时,虽然已经改了名,但还是提示已经安装同版本的apk。
参考https://blog.csdn.net/xiaoluer/article/details/78430191,包名是应用程序的唯一标识
解决方法为:在build.gradle里, 把applicationId改成不同的名称。
三.补充
界面显示的是类别名称等于一个概率,而我使用时,显示的概率经常是大于了1,后来研究mobilenetv3ncnn_jni.cpp
代码,发现原代码在进行后处理时,和我的模型有一点区别。
cls_scores.resize(out.w);
for (int j=0; j<out.w; j++)
{cls_scores[j] = out[j];
}
这一步是把模型的输出降维,shape由[1,n]变为[n],n为类别数
// return top classint top_class = 0;float max_score = 0.f;for (size_t i=0; i<cls_scores.size(); i++){float s = cls_scores[i];
// __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "%d %f", i, s);if (s > max_score){top_class = i;max_score = s;}
这一步求的是cls_scores中最大值的索引,此处是直接用排序算法进行比较的,cls_scores中的值为每个类别的置信度,而我的模型输出后,是经过torch.argmax得到最大值索引的,故导致界面显示出来的值存在大于1的情况。
可以直接修改为只显示类别,不显示概率。
不过,类别一旦多了,总感觉不好,查询c++的argmax实现,修改代码
新增一个求argmax的函数
#include <algorithm>
#include <array>template<class ForwardIterator>
inline size_t argmax(ForwardIterator first, ForwardIterator last)
{return std::distance(first, std::max_element(first, last));
}
将//return top class
及之后部分改为:
// return top class/*int top_class = 0;float max_score = 0.f;for (size_t i=0; i<cls_scores.size(); i++){float s = cls_scores[i];
// __android_log_print(ANDROID_LOG_DEBUG, "SqueezeNcnn", "%d %f", i, s);if (s > max_score){top_class = i;max_score = s;}}*/size_t top_class = argmax(cls_scores.begin(), cls_scores.end());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()) + " = " + tmp;std::string result_str = std::string(word.c_str());
使用的时候,模型遇到不认识的物体,还是会识别为预先设定的某类,看实际项目是否每次检测的图,都在设定的类别范围之内,不然还是按每类打分,取大于阈值的最大值更好。
ncnn填坑记录八:将自己训练的模型打包为APK并部署到安卓端运行相关推荐
- ncnn填坑记录五:在安卓端运行ncnn
上一篇:ncnn填坑记录四:pytorch模型转ncnn 下一篇:ncnn填坑记录六:Android Studio打包APK 一.安装Android Studio 下载地址:https://devel ...
- ncnn填坑记录一:安装protobuf、cmake和opencv
下一篇:ncnn填坑记录二:安装ncnn 一.protobuf安装 1.下载 git clone https://gitee.com/arcy/protobuf.git # 网络不好才在码云下载,建议 ...
- Mac Xcode opencv C++环境配置 保姆级教程 填坑记录 19年最新版本
网上找了很多教程,照着做都失败了,整整弄了两天两夜,终于好了.网上根本没有人遇到我的坑么?都搜不到,国外也没搜到,呜呜- 版本说明: 1.(必备)MacBook系统:macOS Catalina(版本 ...
- Python美股量化交易填坑记录——13c.Vegas隧道交易机器人(实盘记录)
1.背景 上一篇帖子介绍了思路调整的过程,我的目标从"消灭止损单"(越来越保守)改为"追大肉"(允许有止损单,盈利超过损失就行). Python美股量化交易填坑 ...
- Python美股量化交易填坑记录——13b.Vegas隧道交易机器人(实盘记录)
1.背景 上一篇帖子介绍了该算法的原理和八天的实盘记录: Python美股量化交易填坑记录--13a.Vegas隧道交易机器人_ChristopherShen的博客-CSDN博客 经过八天实盘后,已经 ...
- ubuntu16.04安装Intel 9260AC无线网卡填坑记录
由于本科毕设环境需要,本人在笔记本上安装了win10+ubuntu16.04双系统,但是由于之前给笔记本换过无线网卡,型号是intel 9260AC,当时还是比较新的,但是发现在Ubuntu系统里面无 ...
- 众里寻TiDB千百度,蓦然回首,这些填坑记录还在灯火阑珊处
前言 俗话说得好,架构重构一时爽,一直重构一直爽,又到了架构重构的时节:俗话又说了,饱暖思X欲,在产品技术架构稳定运行了好久之后,又迎来了躁动的撩拨,重构之心蠢蠢欲动:俗话最后说了,技术有风险,重构需 ...
- 在ThinkPad X280加装M.2硬盘上安装 Ubuntu 18.04.3 填坑记录
填坑背景 用了一段时间的X280后,突然想在M.2接口上加装一个 NVMe 2242 的SSD,发现 Lenovo 的BIOS设置的非常奇特.能够检测到这个硬盘,但是启动项里就是不能识别!或许是直接就 ...
- 深度学习框架(Pytorch)+ 机器人(ROS):ROS melodic 上安装 Turtlebot2 —> 安装 hokuyo 激光雷达 —> Python 3 的虚拟环境中调用 ROS 填坑记录
复现论文需要ubuntu+ros+turtlebot2+hokuyo的配置,这里有一个关于turtlebot的教程: Learn TurtleBot and ROS 关于 turtlebot 的各种软 ...
最新文章
- java finally块_Java中的finally块是什么?
- java compareto方法怎么排序的_深入理解Java中Comparable和Comparator排序
- Java实现算法导论中Pollard的rho启发式方法
- elasticsearch5.x:查询建议介绍、Suggester 介绍以及Java-api实现
- VBA各种查询方法介绍和应用举例
- 遵化计算机培训,遵化人力资源培训
- 八皇后问题(非递归版)
- 动易生成栏目时报错“您的栏目页模板有误,缺少小类模板!”,一个解决办法...
- 树莓派安装TPLINK_WN725n v2网卡驱动
- selenium 实现循环点击_webdriver处理循环点击
- 定时任务Quartz 之 cron表达式(时间表达式)
- Charle分析实战
- 【翻译】200行代码讲透RUST FUTURES (7)
- 桌面计算机休眠快捷键,电脑休眠快捷键是什么
- Boom 3D全新2022版音频增强应用程序App
- Pyramidal Convolution Rethinking Convolutional Neural Networks for Visual Recognition
- Python高级-前端-01-HTML和CSS
- 手机python3.0编程软件-怎么用手机编写Python程序?
- App Inventor 2 题库设计制作流程
- 系统备用服务器连接配置异常,网络配置的DNS服务老是异常网络连接老是异常...._网络编辑_帮考网...