YOLOX模型部署Android端-NCNN方法
将自己任务的YOLOX-nano模型和YOLOX-tiny模型通过NCNN架构的转换方式部署到Android手机端。
对于自己的任务,需要修改以下几处代码:

# encoding: utf-8
import os
import torch
# 需要加上这个
import torch.nn as nn
import torch.distributed as dist
from yolox.data import get_yolox_datadir
from yolox.exp import Exp as MyExp
class Exp(MyExp):
def __init__(self):
super(Exp, self).__init__()
# 修改网络深度和宽度
self.depth = 0.33
self.width = 0.25
self.input_size = (416, 416)
self.mosaic_scale = (0.5, 1.5)
self.random_size = (10, 20)
self.test_size = (416, 416)
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
self.enable_mixup = False
# 修改类别数
self.num_classes = 1
# 之前没有加上这个get_model函数,就训练有问题
def get_model(self, sublinear=False):
def init_yolo(M):
for m in M.modules():
if isinstance(m, nn.BatchNorm2d):
m.eps = 1e-3
m.momentum = 0.03
if "model" not in self.__dict__:
from yolox.models import YOLOX, YOLOPAFPN, YOLOXHead
in_channels = [256, 512, 1024]
# NANO model use depthwise = True, which is main difference.
backbone = YOLOPAFPN(self.depth, self.width, in_channels=in_channels, depthwise=True)
head = YOLOXHead(self.num_classes, self.width, in_channels=in_channels, depthwise=True)
self.model = YOLOX(backbone, head)
self.model.apply(init_yolo)
self.model.head.initialize_biases(1e-2)
return self.model
def get_data_loader(self, batch_size, is_distributed, no_aug=False, cache_img=False):
from yolox.data import (
VOCDetection,
TrainTransform,
YoloBatchSampler,
DataLoader,
InfiniteSampler,
MosaicDetection,
worker_init_reset_seed,
)
from yolox.utils import (
wait_for_the_master,
get_local_rank,
)
local_rank = get_local_rank()
with wait_for_the_master(local_rank):
dataset = VOCDetection(
data_dir=os.path.join(get_yolox_datadir(), "VOCdevkit"),
# image_sets=[('2007', 'trainval'), ('2012', 'trainval')],
# 训练的时候只有VOC2007的数据集,所以需要改这里
image_sets=[('2007', 'trainval')],
img_size=self.input_size,
preproc=TrainTransform(
max_labels=50,
flip_prob=self.flip_prob,
hsv_prob=self.hsv_prob),
cache=cache_img,
)
dataset = MosaicDetection(
dataset,
mosaic=not no_aug,
img_size=self.input_size,
preproc=TrainTransform(
max_labels=120,
flip_prob=self.flip_prob,
hsv_prob=self.hsv_prob),
degrees=self.degrees,
translate=self.translate,
mosaic_scale=self.mosaic_scale,
mixup_scale=self.mixup_scale,
shear=self.shear,
enable_mixup=self.enable_mixup,
mosaic_prob=self.mosaic_prob,
mixup_prob=self.mixup_prob,
)
self.dataset = dataset
if is_distributed:
batch_size = batch_size // dist.get_world_size()
sampler = InfiniteSampler(
len(self.dataset), seed=self.seed if self.seed else 0
)
batch_sampler = YoloBatchSampler(
sampler=sampler,
batch_size=batch_size,
drop_last=False,
mosaic=not no_aug,
)
dataloader_kwargs = {"num_workers": self.data_num_workers, "pin_memory": True}
dataloader_kwargs["batch_sampler"] = batch_sampler
# Make sure each process has different random seed, especially for 'fork' method
dataloader_kwargs["worker_init_fn"] = worker_init_reset_seed
train_loader = DataLoader(self.dataset, **dataloader_kwargs)
return train_loader
def get_eval_loader(self, batch_size, is_distributed, testdev=False, legacy=False):
from yolox.data import VOCDetection, ValTransform
valdataset = VOCDetection(
data_dir=os.path.join(get_yolox_datadir(), "VOCdevkit"),
image_sets=[('2007', 'test')],
img_size=self.test_size,
preproc=ValTransform(legacy=legacy),
)
if is_distributed:
batch_size = batch_size // dist.get_world_size()
sampler = torch.utils.data.distributed.DistributedSampler(
valdataset, shuffle=False
)
else:
sampler = torch.utils.data.SequentialSampler(valdataset)
dataloader_kwargs = {
"num_workers": self.data_num_workers,
"pin_memory": True,
"sampler": sampler,
}
dataloader_kwargs["batch_size"] = batch_size
val_loader = torch.utils.data.DataLoader(valdataset, **dataloader_kwargs)
return val_loader
def get_evaluator(self, batch_size, is_distributed, testdev=False, legacy=False):
from yolox.evaluators import VOCEvaluator
val_loader = self.get_eval_loader(batch_size, is_distributed, testdev, legacy)
evaluator = VOCEvaluator(
dataloader=val_loader,
img_size=self.test_size,
confthre=self.test_conf,
nmsthre=self.nmsthre,
num_classes=self.num_classes,
)
return evaluator
当然yolox_voc_nano.py代码你也可以不用新建(只训练nano模型时),只需要在yolox_voc_s.py代码的基础上进行修改(nano和tiny模型都在这上面修改即可,类别数及模型大小)。self.num_classes 类别数、self.depth = 0.33、self.width = 0.25(nano模型:0.33,0.25;tiny模型:0.33,0.375)以及wait_for_the_master函数下的data_dir=路径直接修改为数据集的绝对路径,比如:data_dir=“E:\Android_studio\YOLOX\datasets\VOCdevkit”,而下面的image_sets函数修改为:image_sets=[(‘2007’, ‘trainval’)],。同理,下面的get_eval_loade函数这两处也修改绝对路径和image_sets=[(‘2007’, ‘test’)],。
(2)还需要修改.\yolox\exp\yolox_base.py文件。其中的self.num_classes 类别数、self.depth = 0.33、self.width = 0.25(nano模型:0.33,0.25;tiny模型:0.33,0.375),self.input_size =(416,416)建议为416。其中还可以根据自己的调参习惯进行其余项的修改,最大的epoch数,热重启的学习率,以及多少个epoch进行验证等。
(3)接下来就是.\tools\train.py文件的修改。其中-b为batch size的大小,-f需要修改:default=“exps/example/yolox_voc/yolox_voc_s.py”,-c是加载预训练模型default="weights/yolox_nano.pth"其余的按着自己电脑配置自行设置。
(4)最后就是模型的训练了。
默认我们已经训练好了自己的模型,并得到了yolox_nano.pth和yolox_tiny.pth(就是每次得到的best_ckpt.pth进行重命名就可以)。

接下来就稍微有些麻烦了~~

cd <protobuf-root-dir>
mkdir build-vs2017
cd build-vs2017
cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -
DCMAKE_INSTALL_PREFIX=%cd%/install -Dprotobuf_BUILD_TESTS=OFF -
Dprotobuf_MSVC_STATIC_RUNTIME=OFF ../cmake
nmake
nmake install
编译后执行,验证是否安装。
protoc.exe --version
cd <ncnn-root-dir>
mkdir -p build-vs2017
cd build-vs2017
cmake -G"NMake Makefiles" -DCMAKE_BUILD_TYPE=Release -
DCMAKE_INSTALL_PREFIX=%cd%/install -DProtobuf_INCLUDE_DIR=D:/protobuf3.4.0/build-vs2019/install/include -DProtobuf_LIBRARIES=D:/protobuf-3.4.0/buildvs2019/install/lib/libprotobuf.lib -DProtobuf_PROTOC_EXECUTABLE=D:/protobuf3.4.0/build-vs2019/install/bin/protoc.exe -DNCNN_VULKAN=OFF ..
nmake
nmake install
编译后.\ncnn\build-vs2019\tools\onnx下有onnx2ncnn.exe。
注:这里其实还可以使用另一种官方的库,实在是忘记参考哪个博客下载的,该文件下包含转换的exe程序,而且不需要下载编译,可直接用该文件夹.\X64\bin\下的onnx2ncnn.exe,ncnnoptimize.exe:网盘地址:链接:https://pan.baidu.com/s/1aKNmAvApLsnKtBl0BXA22Q
提取码:8ewm
onnx2ncnn.exe yolox_nano.onnx yolox_nano.param yolox_nano.bin
onnx2ncnn.exe yolox_tiny.onnx yolox_tiny.param yolox_tiny.bin
因为ncnn不支持Focus模块,会有警告:(没关系,不用管)
Unsupported slice step !
Unsupported slice step !
Unsupported slice step !
7767517
295 328
Input images 0 1 images
Split splitncnn_input0 1 4 images images_splitncnn_0
images_splitncnn_1 images_splitncnn_2 images_splitncnn_3
Crop Slice_4 1 1 images_splitncnn_3 647 -23309=1,0
-23310=1,2147483647 -23311=1,1
Crop Slice_9 1 1 647 652 -23309=1,0
-23310=1,2147483647 -23311=1,2
Crop Slice_14 1 1 images_splitncnn_2 657 -23309=1,0
-23310=1,2147483647 -23311=1,1
Crop Slice_19 1 1 657 662 -23309=1,1
-23310=1,2147483647 -23311=1,2
Crop Slice_24 1 1 images_splitncnn_1 667 -23309=1,1
-23310=1,2147483647 -23311=1,1
Crop Slice_29 1 1 667 672 -23309=1,0
-23310=1,2147483647 -23311=1,2
Crop Slice_34 1 1 images_splitncnn_0 677 -23309=1,1
-23310=1,2147483647 -23311=1,1
Crop Slice_39 1 1 677 682 -23309=1,1
-23310=1,2147483647 -23311=1,2
Concat Concat_40 4 1 652 672 662 682 683 0=0
Convolution Conv_41 1 1 683 1177 0=16 1=3 11=3 2=1 12=1
3=1 13=1 4=1 14=1 15=1 16=1 5=1 6=1728
Swish Mul_43 1 1 1177 687
ConvolutionDepthWise Conv_44 1 1 687 1180 0=16 1=3 11=3 2=1
12=1 3=2 13=2 4=1 14=1 15=1 16=1 5=1 6=144 7=16
Swish Mul_46 1 1 1180 691
Convolution Conv_47 1 1 691 1183 0=32 1=1 11=1 2=1 12=1
3=1 13=1 4=0 14=0 15=0 16=0 5=1 6=512
Swish Mul_49 1 1 1183 695
把 295修改为295 - 9 = 286 (由于我们将删除 10 层并添加 1 层,因此总层数应减去 9)。然后从 Split 到 Concat 删除 10 行代码,但记住Concat一行最后倒数第二个数字:683。在输入后添加 YoloV5Focus 层(使用之前的数字 683):
YoloV5Focus focus 1 1 images 683
yolox_nano.param文件修改后:(注:YoloV5Focus一定不要写错大小写,app闪退就是因为大小写)
286 328
Input images 0 1 images
YoloV5Focus focus 1 1 images 683
(2)yolo_tiny.param模型修改前:
7767517
235 268
Input images 0 1 images
Split splitncnn_input0 1 4 images images_splitncnn_0
images_splitncnn_1 images_splitncnn_2 images_splitncnn_3
Crop Slice_4 1 1 images_splitncnn_3 467 -23309=1,0
-23310=1,2147483647 -23311=1,1
Crop Slice_9 1 1 467 472 -23309=1,0
-23310=1,2147483647 -23311=1,2
Crop Slice_14 1 1 images_splitncnn_2 477 -23309=1,0
-23310=1,2147483647 -23311=1,1
Crop Slice_19 1 1 477 482 -23309=1,1
-23310=1,2147483647 -23311=1,2
Crop Slice_24 1 1 images_splitncnn_1 487 -23309=1,1
-23310=1,2147483647 -23311=1,1
Crop Slice_29 1 1 487 492 -23309=1,0
-23310=1,2147483647 -23311=1,2
Crop Slice_34 1 1 images_splitncnn_0 497 -23309=1,1
-23310=1,2147483647 -23311=1,1
Crop Slice_39 1 1 497 502 -23309=1,1
-23310=1,2147483647 -23311=1,2
Concat Concat_40 4 1 472 492 482 502 503 0=0
Convolution Conv_41 1 1 503 877 0=24 1=3 11=3 2=1 12=1 3=1
13=1 4=1 14=1 15=1 16=1 5=1 6=2592
Swish Mul_43 1 1 877 507
Convolution Conv_44 1 1 507 880 0=48 1=3 11=3 2=1 12=1 3=2
13=2 4=1 14=1 15=1 16=1 5=1 6=10368
Swish Mul_46 1 1 880 511
Split splitncnn_0 1 2 511 511_splitncnn_0
511_splitncnn_1
Convolution Conv_47 1 1 511_splitncnn_1 883 0=24 1=1 11=1
2=1 12=1 3=1 13=1 4=0 14=0 15=0 16=0 5=1 6=1152
把 235修改为235 - 9 = 226 (由于我们将删除 10 层并添加 1 层,因此总层数应减去 9);然后从 Split 到 Concat 删除 10 行代码,记住Concat一行最后倒数第二个数字:503。在输入后添加 YoloV5Focus 层(使用之前的数字 503)。修改后为:
7767517
226 268
Input images 0 1 images
YoloV5Focus focus 1 1 images 503
(3)对算子量化。使用ncnnoptimize.exe进行模型的量化。基于刚刚修改的.param和.bin文件,在终端输入:
ncnnoptimize.exe yolox_nano.param yolox_nano.bin yolox_nano.param yolox_nano.bin 65536
yolox_nano.param文件中开头的286改280,328改310(自动改的)
ncnnoptimize.exe yolox_tiny.param yolox_tiny.bin yolox_tiny.param yolox_tiny.bin 65536
yolox_tiny.param文件中开头的226改220,268改250(自动改的)
其中.param为模型的结构文件,.bin为模型的参数文件。
网址:https://developer.android.google.cn/studio/
安装时会提示安装SDK
注意:Android SDK安装路径中不要有空格
注意配置:
File->Settings->Appearance & Behavior ->System Settings->Android SDK
SDK Platforms选中面向手机的Android版本
SDK Tools选中NDK, CMake
(值得注意的是:校园网会出现加载不出来SDK Tools的选项,所以要使用手机热点)
project(ncnnyolox)
cmake_minimum_required(VERSION 3.10)
set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/opencv-mobile-4.5.3-android/sdk/native/jni)
find_package(OpenCV REQUIRED core imgproc)
set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20210720-androidvulkan/${ANDROID_ABI}/lib/cmake/ncnn)
find_package(ncnn REQUIRED)
add_library(ncnnyolox SHARED yoloxncnn.cpp yolox.cpp ndkcamera.cpp)
target_link_libraries(ncnnyolox ncnn ${OpenCV_LIBS} camera2ndk mediandk)


我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
我正在尝试设置一个puppet节点,但rubygems似乎不正常。如果我通过它自己的二进制文件(/usr/lib/ruby/gems/1.8/gems/facter-1.5.8/bin/facter)在cli上运行facter,它工作正常,但如果我通过由rubygems(/usr/bin/facter)安装的二进制文件,它抛出:/usr/lib/ruby/1.8/facter/uptime.rb:11:undefinedmethod`get_uptime'forFacter::Util::Uptime:Module(NoMethodError)from/usr/lib/ruby
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我想了解Ruby方法methods()是如何工作的。我尝试使用“ruby方法”在Google上搜索,但这不是我需要的。我也看过ruby-doc.org,但我没有找到这种方法。你能详细解释一下它是如何工作的或者给我一个链接吗?更新我用methods()方法做了实验,得到了这样的结果:'labrat'代码classFirstdeffirst_instance_mymethodenddefself.first_class_mymethodendendclassSecond使用类#returnsavailablemethodslistforclassandancestorsputsSeco
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我在我的项目中添加了一个系统来重置用户密码并通过电子邮件将密码发送给他,以防他忘记密码。昨天它运行良好(当我实现它时)。当我今天尝试启动服务器时,出现以下错误。=>BootingWEBrick=>Rails3.2.1applicationstartingindevelopmentonhttp://0.0.0.0:3000=>Callwith-dtodetach=>Ctrl-CtoshutdownserverExiting/Users/vinayshenoy/.rvm/gems/ruby-1.9.3-p0/gems/actionmailer-3.2.1/lib/action_mailer
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
设置:狂欢ruby1.9.2高线(1.6.13)描述:我已经相当习惯在其他一些项目中使用highline,但已经有几个月没有使用它了。现在,在Ruby1.9.2上全新安装时,它似乎不允许在同一行回答提示。所以以前我会看到类似的东西:require"highline/import"ask"Whatisyourfavoritecolor?"并得到:Whatisyourfavoritecolor?|现在我看到类似的东西:Whatisyourfavoritecolor?|竖线(|)符号是我的终端光标。知道为什么会发生这种变化吗? 最佳答案