上一篇: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.binmobilenetv3.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并部署到安卓端运行相关推荐

  1. ncnn填坑记录五:在安卓端运行ncnn

    上一篇:ncnn填坑记录四:pytorch模型转ncnn 下一篇:ncnn填坑记录六:Android Studio打包APK 一.安装Android Studio 下载地址:https://devel ...

  2. ncnn填坑记录一:安装protobuf、cmake和opencv

    下一篇:ncnn填坑记录二:安装ncnn 一.protobuf安装 1.下载 git clone https://gitee.com/arcy/protobuf.git # 网络不好才在码云下载,建议 ...

  3. Mac Xcode opencv C++环境配置 保姆级教程 填坑记录 19年最新版本

    网上找了很多教程,照着做都失败了,整整弄了两天两夜,终于好了.网上根本没有人遇到我的坑么?都搜不到,国外也没搜到,呜呜- 版本说明: 1.(必备)MacBook系统:macOS Catalina(版本 ...

  4. Python美股量化交易填坑记录——13c.Vegas隧道交易机器人(实盘记录)

    1.背景 上一篇帖子介绍了思路调整的过程,我的目标从"消灭止损单"(越来越保守)改为"追大肉"(允许有止损单,盈利超过损失就行). Python美股量化交易填坑 ...

  5. Python美股量化交易填坑记录——13b.Vegas隧道交易机器人(实盘记录)

    1.背景 上一篇帖子介绍了该算法的原理和八天的实盘记录: Python美股量化交易填坑记录--13a.Vegas隧道交易机器人_ChristopherShen的博客-CSDN博客 经过八天实盘后,已经 ...

  6. ubuntu16.04安装Intel 9260AC无线网卡填坑记录

    由于本科毕设环境需要,本人在笔记本上安装了win10+ubuntu16.04双系统,但是由于之前给笔记本换过无线网卡,型号是intel 9260AC,当时还是比较新的,但是发现在Ubuntu系统里面无 ...

  7. 众里寻TiDB千百度,蓦然回首,这些填坑记录还在灯火阑珊处

    前言 俗话说得好,架构重构一时爽,一直重构一直爽,又到了架构重构的时节:俗话又说了,饱暖思X欲,在产品技术架构稳定运行了好久之后,又迎来了躁动的撩拨,重构之心蠢蠢欲动:俗话最后说了,技术有风险,重构需 ...

  8. 在ThinkPad X280加装M.2硬盘上安装 Ubuntu 18.04.3 填坑记录

    填坑背景 用了一段时间的X280后,突然想在M.2接口上加装一个 NVMe 2242 的SSD,发现 Lenovo 的BIOS设置的非常奇特.能够检测到这个硬盘,但是启动项里就是不能识别!或许是直接就 ...

  9. 深度学习框架(Pytorch)+ 机器人(ROS):ROS melodic 上安装 Turtlebot2 —> 安装 hokuyo 激光雷达 —> Python 3 的虚拟环境中调用 ROS 填坑记录

    复现论文需要ubuntu+ros+turtlebot2+hokuyo的配置,这里有一个关于turtlebot的教程: Learn TurtleBot and ROS 关于 turtlebot 的各种软 ...

最新文章

  1. java finally块_Java中的finally块是什么?
  2. java compareto方法怎么排序的_深入理解Java中Comparable和Comparator排序
  3. Java实现算法导论中Pollard的rho启发式方法
  4. elasticsearch5.x:查询建议介绍、Suggester 介绍以及Java-api实现
  5. VBA各种查询方法介绍和应用举例
  6. 遵化计算机培训,遵化人力资源培训
  7. 八皇后问题(非递归版)
  8. 动易生成栏目时报错“您的栏目页模板有误,缺少小类模板!”,一个解决办法...
  9. 树莓派安装TPLINK_WN725n v2网卡驱动
  10. selenium 实现循环点击_webdriver处理循环点击
  11. 定时任务Quartz 之 cron表达式(时间表达式)
  12. Charle分析实战
  13. 【翻译】200行代码讲透RUST FUTURES (7)
  14. 桌面计算机休眠快捷键,电脑休眠快捷键是什么
  15. Boom 3D全新2022版音频增强应用程序App
  16. Pyramidal Convolution Rethinking Convolutional Neural Networks for Visual Recognition
  17. Python高级-前端-01-HTML和CSS
  18. 手机python3.0编程软件-怎么用手机编写Python程序?
  19. App Inventor 2 题库设计制作流程
  20. 系统备用服务器连接配置异常,网络配置的DNS服务老是异常网络连接老是异常...._网络编辑_帮考网...

热门文章

  1. 9.cuBLAS开发指南中文版--cuBLAS中的原子模式的配置
  2. Initializing from file failed
  3. 计算机网络(五层协议)
  4. Unity 使用柏林噪声(Perlin Noise)生成网格地图
  5. UMPC和MID的电源系统设计挑战及解决办法
  6. 重磅推荐!任正非最新署名文章:星光不问赶路人
  7. 视频文件转换为flv的完整解决方案(ffmpeg、mencoder)
  8. Kubernetes参数:dryRun理解
  9. 2013暑假总结-廊坊人才人事档案管理软件
  10. ftp服务器怎么看不到文件夹,ftp服务器找不到文件夹