当前位置:   article > 正文

YOLOV5从训练到部署测试NCNN安卓端部署保姆级教程_requirements: tensorboard>=2.4.1 not found and is

requirements: tensorboard>=2.4.1 not found and is required by yolov5, attemp

一、基础配置

  • 为防止环境差异,造成教程中部分步骤出现差异,造成部署失败,下面将组件版本列举出来。

组件

版本

anaconda

4.8.3

python

3.8.13

yolov5

6.0

System

Ubuntu 22.04.1 x86_64

System Core

5.10.102.1

gcc

11.2.0

g++

11.2.0

其中System、System Core、gcc、g++版本也许其他低版本也可以,但需自测。建议在docker中进行环境搭建。

  • 初始化系统配置
  1. sudo apt update
  2. sudo apt install git gcc g++ vim curl wget cmake -y

1、Anaconda

conda config --set show_channel_urls yes #在用户目录下生成.condarc文件
  • 为了加快下载速度,我们可以配置大陆境内conda加速镜像。打开用户目录下的.condarc文件,编辑内部信息
  1. channels:
  2. - defaults
  3. show_channel_urls: true
  4. default_channels:
  5. - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
  6. - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
  7. - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
  8. custom_channels:
  9. conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  10. msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  11. bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  12. menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  13. pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  14. pytorch-lts: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  15. simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/clou
  • 创建YOLO基础环境
conda create -n yolo python=3.8 # 创建一个名为yolo的python3.8环境

2、YoloV5

  1. git clone https://github.com/ultralytics/yolov5.git # 下载yolov5源码
  2. cd yolov5
  3. git checkout v6.0 -b v6.0 # 切换为6.0版本
  4. git checkout
  5. conda activate yolo # 切换到前面创建的conda环境
  • 打开requirements.txt文件,修改其中内容
  1. # pip install -r requirements.txt
  2. # Base ----------------------------------------
  3. matplotlib>=3.2.2
  4. numpy>=1.18.5
  5. opencv-python>=4.1.2
  6. Pillow>=7.1.2
  7. PyYAML>=5.3.1
  8. requests>=2.23.0
  9. scipy>=1.4.1
  10. torch==1.7.1
  11. torchvision==0.8.1
  12. tqdm>=4.41.0
  13. # Logging -------------------------------------
  14. tensorboard==2.4.1
  15. # wandb
  16. # Plotting ------------------------------------
  17. pandas>=1.1.4
  18. seaborn>=0.11.0
  19. # Export --------------------------------------
  20. # coremltools>=4.1 # CoreML export
  21. # onnx>=1.9.0 # ONNX export
  22. # onnx-simplifier>=0.3.6 # ONNX simplifier
  23. # scikit-learn==0.19.2 # CoreML quantization
  24. # tensorflow>=2.4.1 # TFLite export
  25. # tensorflowjs>=3.9.0 # TF.js export
  26. # Extras --------------------------------------
  27. # albumentations>=1.0.3
  28. # Cython # for pycocotools https://github.com/cocodataset/cocoapi/issues/172
  29. # pycocotools>=2.0 # COCO mAP
  30. # roboflow
  31. thop # FLOPs computation
  • 安装Yolo基础环境
  1. pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
  2. conda install onnx
  3. pip install onnxsim
  4. pip install onnx-simplifier
  5. pip install onnxruntime

二、训练样本

  • 准备labelimg环境
  1. conda create -n labelimg python=3.8
  2. conda activate labelimg
  3. conda install labelimg
  4. labelimg

此时,系统会打开labelimg工具

  • 创建样本
  1. 我们在系统系统目录下创建metadata目录,在目录下分别创建images、labels文件夹。
  2. 将包含需要识别目标的图片全部放入到images文件夹下。
  3. 打开labelimg,点击打开文件夹(打开目录),选择images所在文件夹。
  4. 点击改变存放目录,选择labels所在文件夹
  5. 点击标记模式为yolo模式,如下图所示。

点击查看 -> 自动保存模式,即打开自动保存模式。

  • 开始标注

labelimg快捷键:W,开启标注框。A,上一张。D,下一张。将全部图片标注完毕。(准备的单个图片不需要包含所有需要识别的类别)

  • 创建配置 xxx.yml
  1. # 训练集,yolo会读取此文件夹下所有图片进行训练,有条件建议切分样本或者分批标注训练集、测试集、验证集。此处路径使用了相对路径,因此yolo会以 train.py 所在目录为根目录进行样本的搜索
  2. train: ./metadata/images
  3. # 验证集
  4. val: ./metadata/images
  5. # 验证集
  6. test: ./metadata/images
  7. # 类别个数,标注的时候共涉及到了几个类别,就填几,跟下面names下的个数相等
  8. nc: 3
  9. # 类别列表, 枚举所有类别, 可以直接复制 labels/classes.txt 下的所有类别,粘贴到此处,记得带引号,与classes.txt 顺序不要错
  10. names: ["a", "b", "c"]

此处我们将训练集、验证集、测试集放到了相同位置,可能造成结果与样本拟合,我们也可以将训练集、测试集、验证集按照下面的目录结构放置样本及配置

  1. ── images // 存放所有图像数据
  2. ├── test // 测试集图片存放位置
  3. │ ├── xxx.jpg
  4. ├── train // 训练集图片存放位置
  5. │ ├── xxx.jpg
  6. └── val // 验证集图片存放位置
  7. ├── xxx.jpg
  8. ── labels // 所有标注完毕后的数据
  9. ├── test // 测试集标注标签文件所在位置
  10. │ ├── xxx.txt
  11. ├── train// 训练集标注标签文件所在位置
  12. │ ├── xxx.txt
  13. └── val// 验证集标注标签文件所在位置
  14. ├── xxx.txt

注意:images下样本集各自文件夹下的样本名称必须与labels下各自文件夹下的样本配置名称相同。

此处提供一个可以自动切分样本的py脚本

  1. import os
  2. import random
  3. import argparse
  4. import shutil
  5. # 默认会从指定的目录的上级目录下的labels下查找。例如sourcepath指定为 ./dataset/images 则会认为 ./dataset/labels中存放的是所有标注完毕后的标签
  6. LABELS_PATH = "./labels"
  7. CLASSES_PROPERTIES = LABELS_PATH + "/classes.txt"
  8. parser = argparse.ArgumentParser()
  9. # 未切分的数据集地址
  10. parser.add_argument('--source_path', type=str, help='input images path')
  11. # 数据集划分完毕后存放的位置,不存在会自己创建
  12. parser.add_argument('--save_path', default='./save_path', type=str, help='new train dataset path')
  13. # parser.add_argument("--val_path", default="./val", type=str, help="new verify dataset path")
  14. # parser.add_argument("--test_path", default="./test", type=str, help="new verify dataset path")
  15. # 分割比例
  16. parser.add_argument("--dataset_split_rate", default="7:2:1", type=str, help="dataset split rate, default: 7:2:1 as train:val:test")
  17. opt = parser.parse_args()
  18. source_path = opt.source_path
  19. train_path = os.path.join(opt.save_path, "./images/train")
  20. val_path = os.path.join(opt.save_path, "./images/val")
  21. test_path = os.path.join(opt.save_path, "./images/test")
  22. dataset_split_rate = opt.dataset_split_rate
  23. train_dataset_rate = 0
  24. val_dataset_rate = 0
  25. test_dataset_rate = 0
  26. if not os.path.exists(source_path):
  27. raise Exception(source_path + " not found exists")
  28. if not os.path.exists(train_path):
  29. os.makedirs(train_path)
  30. if not os.path.exists(val_path):
  31. os.makedirs(val_path)
  32. if not os.path.exists(test_path):
  33. os.makedirs(test_path)
  34. dataset_split_rate = dataset_split_rate.split(":")
  35. if len(dataset_split_rate) != 3:
  36. raise Exception("dataset_split_rate must be third numbers exp: 7:2:1")
  37. train_dataset_rate = dataset_split_rate[0]
  38. val_dataset_rate = dataset_split_rate[1]
  39. test_dataset_rate = dataset_split_rate[2]
  40. if not train_dataset_rate.isdigit():
  41. raise Exception("dataset_split_rate must be third numbers exp: 7:2:1")
  42. if not val_dataset_rate.isdigit():
  43. raise Exception("dataset_split_rate must be third numbers exp: 7:2:1")
  44. if not test_dataset_rate.isdigit():
  45. raise Exception("dataset_split_rate must be third numbers exp: 7:2:1")
  46. train_dataset_rate = int(train_dataset_rate)
  47. val_dataset_rate = int(val_dataset_rate)
  48. test_dataset_rate = int(test_dataset_rate)
  49. if train_dataset_rate + val_dataset_rate + test_dataset_rate != 10:
  50. raise Exception("dataset sum rate must be 10, current val : " + str(train_dataset_rate + val_dataset_rate + test_dataset_rate))
  51. train_dataset_rate = train_dataset_rate * 0.1
  52. val_dataset_rate = val_dataset_rate * 0.1
  53. test_dataset_rate = test_dataset_rate * 0.1
  54. # 读取源数据路径
  55. all_dataset = {}
  56. for image in os.listdir(source_path):
  57. image_path = os.path.abspath(os.path.join(source_path, image))
  58. fname = os.path.splitext(image)[-2]
  59. label_path = os.path.abspath(os.path.join(source_path, "../" + LABELS_PATH + "/" + fname + '.txt'))
  60. all_dataset[image_path] = label_path
  61. if len(all_dataset) < 3:
  62. raise Exception("dataset images number must > 3...")
  63. image_orders = list(all_dataset.keys())
  64. random.shuffle(image_orders)
  65. train_dataset = {}
  66. val_dataset = {}
  67. test_dataset = {}
  68. # 分割样本
  69. train_dataset_max_index = int(train_dataset_rate * len(image_orders))
  70. val_dataset_max_index = int(val_dataset_rate * len(image_orders))
  71. test_dataset_max_index = int(test_dataset_rate * len(image_orders))
  72. for k in image_orders:
  73. if len(train_dataset) >= train_dataset_max_index:
  74. if len(val_dataset) >= val_dataset_max_index:
  75. test_dataset[k] = all_dataset[k]
  76. else:
  77. val_dataset[k] = all_dataset[k]
  78. else:
  79. train_dataset[k] = all_dataset[k]
  80. # 保存训练集
  81. for f in train_dataset.keys():
  82. f = str(f)
  83. shutil.copy(f, train_path)
  84. clas_path = os.path.abspath(os.path.join(train_path, "../../" + LABELS_PATH + "/train"))
  85. if not os.path.exists(clas_path):
  86. os.makedirs(clas_path)
  87. shutil.copy(train_dataset[f], clas_path)
  88. for f in val_dataset.keys():
  89. f = str(f)
  90. shutil.copy(f, val_path)
  91. clas_path = os.path.abspath(os.path.join(train_path, "../../" + LABELS_PATH + "/val"))
  92. if not os.path.exists(clas_path):
  93. os.makedirs(clas_path)
  94. shutil.copy(val_dataset[f], clas_path)
  95. for f in test_dataset.keys():
  96. f = str(f)
  97. shutil.copy(f, test_path)
  98. clas_path = os.path.abspath(os.path.join(train_path, "../../" + LABELS_PATH + "/test"))
  99. if not os.path.exists(clas_path):
  100. os.makedirs(clas_path)
  101. shutil.copy(test_dataset[f], clas_path)

执行分割

  1. # source_path 所有的样本图片所在目录。save_path:分割完毕后的图片存放目录。data_split_rate:样本切分比例(训练集:验证集:测试集)
  2. python xxx.py --source_path ./metadata/images --save_path ./dataset --dataset_split_rate 7:2:1

样本被保存到了 dataset下,我们在dataset下创建xxx.yaml配置文件。

  1. # 例如根目录名称为dataset
  2. train: ./dataset/images/train
  3. # 验证集
  4. val: ./dataset/images/val
  5. # 测试集
  6. test: ./dataset/images/test
  7. # 类别个数,标注的时候共涉及到了几个类别,就填几
  8. nc: 3
  9. # 类别列表, 枚举所有类别, 可以直接复制 labels/classes.txt 下的所有类别,粘贴到此处,记得带引号,与ckasses.txt 顺序不要错
  10. names: ["a", "b", "c"]
  • 训练样本
  1. conda activate yolo
  2. # 进入yolov5源代码目录
  3. cd yolov5
  4. # 手动下载基础模型,指定为6.0版本
  5. wget https://github.com/ultralytics/yolov5/releases/download/v6.0/yolov5s.pt
  6. # 测试模型,执行完毕后,控制台会输出推理结果,Results saved to runs/detect/xxx 打开此文件夹,查看推理完毕后的结果是否正常
  7. python detect.py --weights yolov5s.pt --img 640 --conf 0.25 --source data/images/
  8. # 开始训练, 注意 --data 后参数,指向上面创建的yaml文件, 训练完毕后,系统会提示样本保存的目录:Optimizer stripped from runs/train/exp/weights/xxx.pt
  9. python train.py --img 640 --batch 50 --epochs 100 --data ../metadata/xxx.yaml --weights yolov5s.pt --nosave --cache
  10. # 如果出现 requirements: tensorboard>=2.4.1 not found and is required by YOLOv5,手动执行下面的命令
  11. pip install tensorboard==2.4.1
  12. # 测试模型是否正常 --weights 为训练完毕后的模型文件, --img 同训练时 img参数 --conf 最低置信度,当分值低于此分数时,不在图片中标注。--source 需要被推理的图片所在目录
  13. python detect.py --weights runs/train/exp/weights/xxx.pt --img 640 --conf 0.25 --source data/images/
  • 检出为ncnn可用模型
  1. # 检出为onnx模型,注意weights指向上一步训练出来的模型
  2. python export.py --weights ./run/train/exp/weights/xxx.pt --img 640 640 --batch 1 --train --simplify --include onnx --opset 11
  3. # 简化onnx模型
  4. python -m onnxsim ./run/train/exp/weights/xxx.onnx ./run/train/exp/weights/yolov5s-sim.onnx
  5. # 转换为ncnn模型,可以打开此网站 https://convertmodel.com/ 选择onnx转ncnn,如果自行编译了ncnn,可以打开install/bin 执行下边的命令
  6. ./onnx2ncnn ./run/train/exp/weights/yolov5s-sim.onnx ./run/train/exp/weights/yolov5s.param ./run/train/exp/weights/yolov5s.bin
  • 修改配置

打开生成的yolov5s.param文件,修改Reshape参数,将0=6400、1600、400全部改为 0=-1

 如下图

 记录三组Permute数值的倒数第二列信息,如下图,例如我的样本配置结果为 "output", "365", "385"。如果你的Permute倒数第二列参数不是纯数值(除output之外),此时即代表后面所有的步骤都可能出现无法控制的结果,请重新开始训练,及检查你的yolo版本。

 三、安卓端部署

  • 打开Android Studio项目,创建或打开一个项目,切换项目显示模式为project

  • 创建模块,模块类型选择Android Native Library,注意选择c++11

  • 点击Android Studio工具栏 Tools -> SDK Manager,检查cmake 3.10.2是否已安装,如果没有安装,请安装此工具

  • 修改插件目录下 src -> main -> cpp -> CMakeLists.txt文件。
  1. project(yolov5ncnn) # 记录此名称,建议设置为插件名称
  2. cmake_minimum_required(VERSION 3.4.1)
  3. set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnnvulkan/${ANDROID_ABI}/lib/cmake/ncnn)
  4. find_package(ncnn REQUIRED)
  5. # yolov5ncnn 参数与上名称对应,yolov5ncnn_jni.cpp即为模块创建时,自动创建在与CMakeLists.txt 平级的cpp文件,当然也可以后面手动创建
  6. add_library(yolov5ncnn SHARED yolov5ncnn_jni.cpp)
  7. # yolov5ncnn同上,下边两个参数不变
  8. target_link_libraries(yolov5ncnn
  9. ncnn
  10. jnigraphics
  11. )

其中ncnnvulkan需要自行下载编译,或到此下载发行包,选择ncnn-xxx-android-vulkan.zip。如果github打不开,请在此下载(希望大家在此处下载,创作不易,支持一下)。

将ncnnvulkan依赖放入与CMakeLists.txt平级目录,结构如下

  1. ncnnvulkan
  2. ├─arm64-v8a
  3. │ ├─include
  4. │ │ ├─glslang
  5. │ │ │ ├─Include
  6. │ │ │ ├─MachineIndependent
  7. │ │ │ │ └─preprocessor
  8. │ │ │ ├─Public
  9. │ │ │ └─SPIRV
  10. │ │ └─ncnn
  11. │ └─lib
  12. │ └─cmake
  13. │ └─ncnn
  14. ├─armeabi-v7a
  15. │ ├─include
  16. │ │ ├─glslang
  17. │ │ │ ├─Include
  18. │ │ │ ├─MachineIndependent
  19. │ │ │ │ └─preprocessor
  20. │ │ │ ├─Public
  21. │ │ │ └─SPIRV
  22. │ │ └─ncnn
  23. │ └─lib
  24. │ └─cmake
  25. │ │ ├─Include
  26. │ │ ├─MachineIndependent
  27. │ │ │ └─preprocessor
  28. │ │ ├─Public
  29. │ │ └─SPIRV
  30. │ └─ncnn
  31. └─lib
  32. └─cmake
  33. └─ncnn
  34. CMakeLists.txt
  • 修改与CMakeLists.txt平级的yolov5ncnn_jni.cpp文件(CMakeLists.txt中指定的cpp文件)
  1. // Tencent is pleased to support the open source community by making ncnn available.
  2. //
  3. // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
  4. //
  5. // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
  6. // in compliance with the License. You may obtain a copy of the License at
  7. //
  8. // https://opensource.org/licenses/BSD-3-Clause
  9. //
  10. // Unless required by applicable law or agreed to in writing, software distributed
  11. // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  12. // CONDITIONS OF ANY KIND, either express or implied. See the License for the
  13. // specific language governing permissions and limitations under the License.
  14. #include <android/asset_manager_jni.h>
  15. #include <android/bitmap.h>
  16. #include <android/log.h>
  17. #include <jni.h>
  18. #include <string>
  19. #include <vector>
  20. // ncnn
  21. #include "layer.h"
  22. #include "net.h"
  23. #include "benchmark.h"
  24. static ncnn::UnlockedPoolAllocator g_blob_pool_allocator;
  25. static ncnn::PoolAllocator g_workspace_pool_allocator;
  26. static ncnn::Net yolov5;
  27. class YoloV5Focus : public ncnn::Layer
  28. {
  29. public:
  30. YoloV5Focus()
  31. {
  32. one_blob_only = true;
  33. }
  34. virtual int forward(const ncnn::Mat& bottom_blob, ncnn::Mat& top_blob, const ncnn::Option& opt) const
  35. {
  36. int w = bottom_blob.w;
  37. int h = bottom_blob.h;
  38. int channels = bottom_blob.c;
  39. int outw = w / 2;
  40. int outh = h / 2;
  41. int outc = channels * 4;
  42. top_blob.create(outw, outh, outc, 4u, 1, opt.blob_allocator);
  43. if (top_blob.empty())
  44. return -100;
  45. #pragma omp parallel for num_threads(opt.num_threads)
  46. for (int p = 0; p < outc; p++)
  47. {
  48. const float* ptr = bottom_blob.channel(p % channels).row((p / channels) % 2) + ((p / channels) / 2);
  49. float* outptr = top_blob.channel(p);
  50. for (int i = 0; i < outh; i++)
  51. {
  52. for (int j = 0; j < outw; j++)
  53. {
  54. *outptr = *ptr;
  55. outptr += 1;
  56. ptr += 2;
  57. }
  58. ptr += w;
  59. }
  60. }
  61. return 0;
  62. }
  63. };
  64. DEFINE_LAYER_CREATOR(YoloV5Focus)
  65. struct Object
  66. {
  67. float x;
  68. float y;
  69. float w;
  70. float h;
  71. int label;
  72. float prob;
  73. };
  74. static inline float intersection_area(const Object& a, const Object& b)
  75. {
  76. if (a.x > b.x + b.w || a.x + a.w < b.x || a.y > b.y + b.h || a.y + a.h < b.y)
  77. {
  78. // no intersection
  79. return 0.f;
  80. }
  81. float inter_width = std::min(a.x + a.w, b.x + b.w) - std::max(a.x, b.x);
  82. float inter_height = std::min(a.y + a.h, b.y + b.h) - std::max(a.y, b.y);
  83. return inter_width * inter_height;
  84. }
  85. static void qsort_descent_inplace(std::vector<Object>& faceobjects, int left, int right)
  86. {
  87. int i = left;
  88. int j = right;
  89. float p = faceobjects[(left + right) / 2].prob;
  90. while (i <= j)
  91. {
  92. while (faceobjects[i].prob > p)
  93. i++;
  94. while (faceobjects[j].prob < p)
  95. j--;
  96. if (i <= j)
  97. {
  98. // swap
  99. std::swap(faceobjects[i], faceobjects[j]);
  100. i++;
  101. j--;
  102. }
  103. }
  104. #pragma omp parallel sections
  105. {
  106. #pragma omp section
  107. {
  108. if (left < j) qsort_descent_inplace(faceobjects, left, j);
  109. }
  110. #pragma omp section
  111. {
  112. if (i < right) qsort_descent_inplace(faceobjects, i, right);
  113. }
  114. }
  115. }
  116. static void qsort_descent_inplace(std::vector<Object>& faceobjects)
  117. {
  118. if (faceobjects.empty())
  119. return;
  120. qsort_descent_inplace(faceobjects, 0, faceobjects.size() - 1);
  121. }
  122. static void nms_sorted_bboxes(const std::vector<Object>& faceobjects, std::vector<int>& picked, float nms_threshold)
  123. {
  124. picked.clear();
  125. const int n = faceobjects.size();
  126. std::vector<float> areas(n);
  127. for (int i = 0; i < n; i++)
  128. {
  129. areas[i] = faceobjects[i].w * faceobjects[i].h;
  130. }
  131. for (int i = 0; i < n; i++)
  132. {
  133. const Object& a = faceobjects[i];
  134. int keep = 1;
  135. for (int j = 0; j < (int)picked.size(); j++)
  136. {
  137. const Object& b = faceobjects[picked[j]];
  138. // intersection over union
  139. float inter_area = intersection_area(a, b);
  140. float union_area = areas[i] + areas[picked[j]] - inter_area;
  141. // float IoU = inter_area / union_area
  142. if (inter_area / union_area > nms_threshold)
  143. keep = 0;
  144. }
  145. if (keep)
  146. picked.push_back(i);
  147. }
  148. }
  149. static inline float sigmoid(float x)
  150. {
  151. return static_cast<float>(1.f / (1.f + exp(-x)));
  152. }
  153. static void generate_proposals(const ncnn::Mat& anchors, int stride, const ncnn::Mat& in_pad, const ncnn::Mat& feat_blob, float prob_threshold, std::vector<Object>& objects)
  154. {
  155. const int num_grid = feat_blob.h;
  156. int num_grid_x;
  157. int num_grid_y;
  158. if (in_pad.w > in_pad.h)
  159. {
  160. num_grid_x = in_pad.w / stride;
  161. num_grid_y = num_grid / num_grid_x;
  162. }
  163. else
  164. {
  165. num_grid_y = in_pad.h / stride;
  166. num_grid_x = num_grid / num_grid_y;
  167. }
  168. const int num_class = feat_blob.w - 5;
  169. const int num_anchors = anchors.w / 2;
  170. for (int q = 0; q < num_anchors; q++)
  171. {
  172. const float anchor_w = anchors[q * 2];
  173. const float anchor_h = anchors[q * 2 + 1];
  174. const ncnn::Mat feat = feat_blob.channel(q);
  175. for (int i = 0; i < num_grid_y; i++)
  176. {
  177. for (int j = 0; j < num_grid_x; j++)
  178. {
  179. const float* featptr = feat.row(i * num_grid_x + j);
  180. // find class index with max class score
  181. int class_index = 0;
  182. float class_score = -FLT_MAX;
  183. for (int k = 0; k < num_class; k++)
  184. {
  185. float score = featptr[5 + k];
  186. if (score > class_score)
  187. {
  188. class_index = k;
  189. class_score = score;
  190. }
  191. }
  192. float box_score = featptr[4];
  193. float confidence = sigmoid(box_score) * sigmoid(class_score);
  194. if (confidence >= prob_threshold)
  195. {
  196. // yolov5/models/yolo.py Detect forward
  197. // y = x[i].sigmoid()
  198. // y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i] # xy
  199. // y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
  200. float dx = sigmoid(featptr[0]);
  201. float dy = sigmoid(featptr[1]);
  202. float dw = sigmoid(featptr[2]);
  203. float dh = sigmoid(featptr[3]);
  204. float pb_cx = (dx * 2.f - 0.5f + j) * stride;
  205. float pb_cy = (dy * 2.f - 0.5f + i) * stride;
  206. float pb_w = pow(dw * 2.f, 2) * anchor_w;
  207. float pb_h = pow(dh * 2.f, 2) * anchor_h;
  208. float x0 = pb_cx - pb_w * 0.5f;
  209. float y0 = pb_cy - pb_h * 0.5f;
  210. float x1 = pb_cx + pb_w * 0.5f;
  211. float y1 = pb_cy + pb_h * 0.5f;
  212. Object obj;
  213. obj.x = x0;
  214. obj.y = y0;
  215. obj.w = x1 - x0;
  216. obj.h = y1 - y0;
  217. obj.label = class_index;
  218. obj.prob = confidence;
  219. objects.push_back(obj);
  220. }
  221. }
  222. }
  223. }
  224. }
  225. extern "C" {
  226. // FIXME DeleteGlobalRef is missing for objCls
  227. static jclass objCls = NULL;
  228. static jmethodID constructortorId;
  229. static jfieldID xId;
  230. static jfieldID yId;
  231. static jfieldID wId;
  232. static jfieldID hId;
  233. static jfieldID labelId;
  234. static jfieldID probId;
  235. JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
  236. {
  237. __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "JNI_OnLoad");
  238. ncnn::create_gpu_instance();
  239. return JNI_VERSION_1_4;
  240. }
  241. JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
  242. {
  243. __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "JNI_OnUnload");
  244. ncnn::destroy_gpu_instance();
  245. }
  246. // public native boolean Init(AssetManager mgr);
  247. JNIEXPORT jboolean JNICALL Java_com_tencent_yolov5ncnn_YoloV5Ncnn_Init(JNIEnv* env, jobject thiz, jobject assetManager)
  248. {
  249. ncnn::Option opt;
  250. opt.lightmode = true;
  251. opt.num_threads = 4;
  252. opt.blob_allocator = &g_blob_pool_allocator;
  253. opt.workspace_allocator = &g_workspace_pool_allocator;
  254. opt.use_packing_layout = true;
  255. // use vulkan compute
  256. if (ncnn::get_gpu_count() != 0)
  257. opt.use_vulkan_compute = true;
  258. AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
  259. yolov5.opt = opt;
  260. yolov5.register_custom_layer("YoloV5Focus", YoloV5Focus_layer_creator);
  261. // init param
  262. {
  263. int ret = yolov5.load_param(mgr, "yolov5s.param");
  264. if (ret != 0)
  265. {
  266. __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "load_param failed");
  267. return JNI_FALSE;
  268. }
  269. }
  270. // init bin
  271. {
  272. int ret = yolov5.load_model(mgr, "yolov5s.bin");
  273. if (ret != 0)
  274. {
  275. __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "load_model failed");
  276. return JNI_FALSE;
  277. }
  278. }
  279. // init jni glue
  280. jclass localObjCls = env->FindClass("com/tencent/yolov5ncnn/YoloV5Ncnn$Obj");
  281. objCls = reinterpret_cast<jclass>(env->NewGlobalRef(localObjCls));
  282. constructortorId = env->GetMethodID(objCls, "<init>", "(Lcom/tencent/yolov5ncnn/YoloV5Ncnn;)V");
  283. xId = env->GetFieldID(objCls, "x", "F");
  284. yId = env->GetFieldID(objCls, "y", "F");
  285. wId = env->GetFieldID(objCls, "w", "F");
  286. hId = env->GetFieldID(objCls, "h", "F");
  287. labelId = env->GetFieldID(objCls, "label", "Ljava/lang/String;");
  288. probId = env->GetFieldID(objCls, "prob", "F");
  289. return JNI_TRUE;
  290. }
  291. // public native Obj[] Detect(Bitmap bitmap, boolean use_gpu);
  292. JNIEXPORT jobjectArray JNICALL Java_com_tencent_yolov5ncnn_YoloV5Ncnn_Detect(JNIEnv* env, jobject thiz, jobject bitmap, jboolean use_gpu)
  293. {
  294. if (use_gpu == JNI_TRUE && ncnn::get_gpu_count() == 0)
  295. {
  296. return NULL;
  297. //return env->NewStringUTF("no vulkan capable gpu");
  298. }
  299. double start_time = ncnn::get_current_time();
  300. AndroidBitmapInfo info;
  301. AndroidBitmap_getInfo(env, bitmap, &info);
  302. const int width = info.width;
  303. const int height = info.height;
  304. if (info.format != ANDROID_BITMAP_FORMAT_RGBA_8888)
  305. return NULL;
  306. // ncnn from bitmap
  307. const int target_size = 640;
  308. // letterbox pad to multiple of 32
  309. int w = width;
  310. int h = height;
  311. float scale = 1.f;
  312. if (w > h)
  313. {
  314. scale = (float)target_size / w;
  315. w = target_size;
  316. h = h * scale;
  317. }
  318. else
  319. {
  320. scale = (float)target_size / h;
  321. h = target_size;
  322. w = w * scale;
  323. }
  324. ncnn::Mat in = ncnn::Mat::from_android_bitmap_resize(env, bitmap, ncnn::Mat::PIXEL_RGB, w, h);
  325. // pad to target_size rectangle
  326. // yolov5/utils/datasets.py letterbox
  327. int wpad = (w + 31) / 32 * 32 - w;
  328. int hpad = (h + 31) / 32 * 32 - h;
  329. ncnn::Mat in_pad;
  330. ncnn::copy_make_border(in, in_pad, hpad / 2, hpad - hpad / 2, wpad / 2, wpad - wpad / 2, ncnn::BORDER_CONSTANT, 114.f);
  331. // yolov5
  332. std::vector<Object> objects;
  333. {
  334. const float prob_threshold = 0.25f;
  335. const float nms_threshold = 0.45f;
  336. const float norm_vals[3] = {1 / 255.f, 1 / 255.f, 1 / 255.f};
  337. in_pad.substract_mean_normalize(0, norm_vals);
  338. ncnn::Extractor ex = yolov5.create_extractor();
  339. ex.set_vulkan_compute(use_gpu);
  340. ex.input("images", in_pad);
  341. std::vector<Object> proposals;
  342. // anchor setting from yolov5/models/yolov5s.yaml
  343. // stride 8
  344. {
  345. ncnn::Mat out;
  346. ex.extract("output", out);
  347. ncnn::Mat anchors(6);
  348. anchors[0] = 10.f;
  349. anchors[1] = 13.f;
  350. anchors[2] = 16.f;
  351. anchors[3] = 30.f;
  352. anchors[4] = 33.f;
  353. anchors[5] = 23.f;
  354. std::vector<Object> objects8;
  355. generate_proposals(anchors, 8, in_pad, out, prob_threshold, objects8);
  356. proposals.insert(proposals.end(), objects8.begin(), objects8.end());
  357. }
  358. // stride 16
  359. {
  360. ncnn::Mat out;
  361. ex.extract("365", out);
  362. ncnn::Mat anchors(6);
  363. anchors[0] = 30.f;
  364. anchors[1] = 61.f;
  365. anchors[2] = 62.f;
  366. anchors[3] = 45.f;
  367. anchors[4] = 59.f;
  368. anchors[5] = 119.f;
  369. std::vector<Object> objects16;
  370. generate_proposals(anchors, 16, in_pad, out, prob_threshold, objects16);
  371. proposals.insert(proposals.end(), objects16.begin(), objects16.end());
  372. }
  373. // stride 32
  374. {
  375. ncnn::Mat out;
  376. ex.extract("385", out);
  377. ncnn::Mat anchors(6);
  378. anchors[0] = 116.f;
  379. anchors[1] = 90.f;
  380. anchors[2] = 156.f;
  381. anchors[3] = 198.f;
  382. anchors[4] = 373.f;
  383. anchors[5] = 326.f;
  384. std::vector<Object> objects32;
  385. generate_proposals(anchors, 32, in_pad, out, prob_threshold, objects32);
  386. proposals.insert(proposals.end(), objects32.begin(), objects32.end());
  387. }
  388. // sort all proposals by score from highest to lowest
  389. qsort_descent_inplace(proposals);
  390. // apply nms with nms_threshold
  391. std::vector<int> picked;
  392. nms_sorted_bboxes(proposals, picked, nms_threshold);
  393. int count = picked.size();
  394. objects.resize(count);
  395. for (int i = 0; i < count; i++)
  396. {
  397. objects[i] = proposals[picked[i]];
  398. // adjust offset to original unpadded
  399. float x0 = (objects[i].x - (wpad / 2)) / scale;
  400. float y0 = (objects[i].y - (hpad / 2)) / scale;
  401. float x1 = (objects[i].x + objects[i].w - (wpad / 2)) / scale;
  402. float y1 = (objects[i].y + objects[i].h - (hpad / 2)) / scale;
  403. // clip
  404. x0 = std::max(std::min(x0, (float)(width - 1)), 0.f);
  405. y0 = std::max(std::min(y0, (float)(height - 1)), 0.f);
  406. x1 = std::max(std::min(x1, (float)(width - 1)), 0.f);
  407. y1 = std::max(std::min(y1, (float)(height - 1)), 0.f);
  408. objects[i].x = x0;
  409. objects[i].y = y0;
  410. objects[i].w = x1 - x0;
  411. objects[i].h = y1 - y0;
  412. }
  413. }
  414. // objects to Obj[]
  415. static const char* class_names[] = {
  416. "bag", "task_info_area", "task_single_trigger", "task_success"
  417. };
  418. jobjectArray jObjArray = env->NewObjectArray(objects.size(), objCls, NULL);
  419. for (size_t i=0; i<objects.size(); i++)
  420. {
  421. jobject jObj = env->NewObject(objCls, constructortorId, thiz);
  422. env->SetFloatField(jObj, xId, objects[i].x);
  423. env->SetFloatField(jObj, yId, objects[i].y);
  424. env->SetFloatField(jObj, wId, objects[i].w);
  425. env->SetFloatField(jObj, hId, objects[i].h);
  426. env->SetObjectField(jObj, labelId, env->NewStringUTF(class_names[objects[i].label]));
  427. env->SetFloatField(jObj, probId, objects[i].prob);
  428. env->SetObjectArrayElement(jObjArray, i, jObj);
  429. }
  430. double elasped = ncnn::get_current_time() - start_time;
  431. __android_log_print(ANDROID_LOG_DEBUG, "YoloV5Ncnn", "%.2fms detect", elasped);
  432. return jObjArray;
  433. }
  434. }
  1. 注意 432、451、470行,在第 “二” 步中,“修改配置”步骤中Permute的值,将三处参数依次修改为上同参数值,即 432行修改为 **ex.extract("output", out);** 451行修改为 **ex.extract("365", out);** 470行修改为"ex.extract("385", out);"。
  2. 修改521行列表,与样本集中 labels下的classes.txt列表对应。
  3. 修改351行 constructortorId = env->GetMethodID(objCls, "", "(Lcom/tencent/yolov5ncnn/YoloV5Ncnn;)V");,将“com/tencent/yolov5ncnn/YoloV5Ncnn” 修改为自定义java类,注意前部的“L”和尾部的“V”保留(或者直接按照此包结构在模块下的src/main/java下创建此包和类文件)
  4. 修改348行 jclass localObjCls = env->FindClass("com/tencent/yolov5ncnn/YoloV5Ncnn$Obj"); 中 "com/tencent/yolov5ncnn/YoloV5Ncnn$Obj" 为自定义java类,同上,但不可丢掉后面的$Obj。
  • 创建反射类
  1. // Tencent is pleased to support the open source community by making ncnn available.
  2. //
  3. // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
  4. //
  5. // Licensed under the BSD 3-Clause License (the "License"); you may not use this file except
  6. // in compliance with the License. You may obtain a copy of the License at
  7. //
  8. // https://opensource.org/licenses/BSD-3-Clause
  9. //
  10. // Unless required by applicable law or agreed to in writing, software distributed
  11. // under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  12. // CONDITIONS OF ANY KIND, either express or implied. See the License for the
  13. // specific language governing permissions and limitations under the License.
  14. package com.tencent.yolov5ncnn;
  15. import android.content.res.AssetManager;
  16. import android.graphics.Bitmap;
  17. public class YoloV5Ncnn
  18. {
  19. public native boolean Init(AssetManager mgr);
  20. public class Obj {
  21. // x
  22. public float x;
  23. // y
  24. public float y;
  25. // w
  26. public float w;
  27. // h
  28. public float h;
  29. // 标签
  30. public String label;
  31. // 置信度
  32. public float prob;
  33. @Override
  34. public String toString() {
  35. return "Obj{" +
  36. "x=" + x +
  37. ", y=" + y +
  38. ", w=" + w +
  39. ", h=" + h +
  40. ", label='" + label + '\'' +
  41. ", prob=" + prob +
  42. '}';
  43. }
  44. }
  45. public native Obj[] Detect(Bitmap bitmap, boolean use_gpu);
  46. static {
  47. // 为CMakeLists.txt中配置的project名称
  48. System.loadLibrary("yolov5ncnn");
  49. }
  50. }
  • 修改模块的build.grade配置
  1. plugins {
  2. id 'com.android.library'
  3. }
  4. android {
  5. namespace 'cn.tiktok.ncnn'
  6. compileSdk 24
  7. defaultConfig {
  8. minSdk 24
  9. archivesBaseName = "$applicationId"
  10. testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  11. consumerProguardFiles "consumer-rules.pro"
  12. externalNativeBuild {
  13. cmake {
  14. cppFlags "-std=c++11"
  15. }
  16. }
  17. ndk {
  18. // 可减少平台支持,减小包体积
  19. abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
  20. }
  21. }
  22. buildTypes {
  23. release {
  24. minifyEnabled false
  25. proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  26. }
  27. }
  28. externalNativeBuild {
  29. cmake {
  30. // 此处指向CMakeLists.txt 所在文件夹
  31. path "src/main/jni/CMakeLists.txt"
  32. version "3.10.2"
  33. }
  34. }
  35. compileOptions {
  36. sourceCompatibility JavaVersion.VERSION_1_8
  37. targetCompatibility JavaVersion.VERSION_1_8
  38. }
  39. }
  40. dependencies {
  41. implementation 'androidx.appcompat:appcompat:1.4.1'
  42. implementation 'com.google.android.material:material:1.5.0'
  43. testImplementation 'junit:junit:4.13.2'
  44. androidTestImplementation 'androidx.test.ext:junit:1.1.3'
  45. androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
  46. }
  • 部署模型

在模块的src/main目录下创建 assets 文件夹,然后检查文件夹属性。如果被创建为了普通文件夹,如下图所示。

则在此文件夹上右键,选择 Mark Directory as,选择Resources root。

我们在第“二”步的“检出为ncnn可用模型”步骤,生成了 yolov5s.param 和 yolov5s.bin两个文件,将两个文件复制到assets文件夹下。

然后在app中引入此模块,并使用,注意修改为模块名称

 最后选择Android Studio菜单栏 File -> Sync Project wth Gradle FIles,等待执行完毕。

四、测试使用

  1. public class MainActivity extends Activity{
  2. private YoloV5Ncnn yolov5ncnn = new YoloV5Ncnn();
  3. /** Called when the activity is first created. */
  4. @Override
  5. public void onCreate(Bundle savedInstanceState){
  6. super.onCreate(savedInstanceState);
  7. // 初始化
  8. boolean ret_init = yolov5ncnn.Init(getAssets());
  9. if (!ret_init){
  10. Log.e("MainActivity", "yolov5ncnn Init failed");
  11. }
  12. // 图片、是否使用GPU
  13. YoloV5Ncnn.Obj[] objects = yolov5ncnn.Detect(imageBitmap, false);
  14. // 根据注释解析YoloV5Ncnn.Obj
  15. ...
  16. }
  17. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/569618
推荐阅读
相关标签
  

闽ICP备14008679号