admin 管理员组

文章数量: 887021

YOLOX模型部署Android端-NCNN方法

  • 1.YOLOX代码仓库的下载
  • 2.ONNX模型转换
  • 3.NCNN框架模型转换
  • 4 安装Android Studio
  • 5 准备Android项目文件
  • 6 连接手机

将自己任务的YOLOX-nano模型和YOLOX-tiny模型通过NCNN架构的转换方式部署到Android手机端。

1.YOLOX代码仓库的下载

对于自己的任务,需要修改以下几处代码:

  • 1.1数据集的准备(使用的VOC数据格式):在datasets文件夹下新建一个VOCdevkit文件夹->接着在VOCdevkit文件夹下新建VOC2007文件夹->接着在VOC2007文件夹下新建Annotations、ImageSets、JPEGImages三个文件夹,分别存储.xml标签文件、划分数据集为训练集和验证集的文本、和图片数据。
    大概如下图所示:
  • 1.2 根据自己的任务修改几处代码:(1)如果我们使用YOLOX-nano模型,建议在.\exps\example\yolox_voc\文件夹下新建一个yolox_voc_nano.py,该文件与yolox_voc_s.py并列。yolox_voc_nano.py的代码是参考别人的,如下所示:
# 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)最后就是模型的训练了。

2.ONNX模型转换

默认我们已经训练好了自己的模型,并得到了yolox_nano.pth和yolox_tiny.pth(就是每次得到的best_ckpt.pth进行重命名就可以)。

  • 使用YOLOX官方自带的export_onnx.py进行转换。其中**–output-name需要修改转换模型的保存路径,-f同样需要进行指定文件default="exps/example/yolox_voc/yolox_voc_s.py,-c加载的就是我们训练好的.pt的权值文件。转换提示如下图:(nano和tiny模型需要修改两次,-f指定的yolox_voc_s.py文件需要按着每次转换的模型修改深度和宽度的系数)

3.NCNN框架模型转换

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

  • 3.1 安装protobuf:下载后解压:https://github/google/protobuf/archive/v3.4.0.zip ,然后使用

    绿色终端执行命令:
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
  • 3.2 编译NCNN,从该地址下载:https://github/Tencent/ncnn/releases。同样打开刚才的(VS2017X64)的命令终端(进入到ncnn根目录下),执行以下语句:
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/s/1aKNmAvApLsnKtBl0BXA22Q
提取码:8ewm

  • 3.3 生成NCNN文件。拷贝yolox_nano.onnx和yolox_tiny.onnx文件到onnx2ncnn.exe,ncnnoptimize.exe文件下,生成ncnn相应的param和bin文件。(命令如下)
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 ! 
  • 3.4 因为ncnn不支持Focus模块,所以需要对yolox_nano.param、yolox_tiny.param进行修改。
    (1)yolo_nano.param模型修改前:
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为模型的参数文件。

4 安装Android Studio

网址:https://developer.android.google/studio/
安装时会提示安装SDK
注意:Android SDK安装路径中不要有空格
注意配置:
File->Settings->Appearance & Behavior ->System Settings->Android SDK
SDK Platforms选中面向手机的Android版本
SDK Tools选中NDK, CMake
(值得注意的是:校园网会出现加载不出来SDK Tools的选项,所以要使用手机热点)

5 准备Android项目文件

  • 5.1 首先下载Android开发文件:https://github/nihui/ncnn-android-nanodet(里面已经有参考和步骤,我下载好的:链接:https://pan.baidu/s/14S05OMCx-O2BAvMx5cnJdA 提取码:5epp)
  • 5.2 放置ncnn的安卓文件:https://github/Tencent/ncnn/releases
    下载ncnn-YYYYMMDD-android-vulkan.zip
    课程中使用ncnn-20210720-android-vulkan.zip
    解压ncnn-YYYYMMDD-android-vulkan.zip后放置到app/src/main/jni 并修改 app/src/main/jni/CMakeLists.txt中的ncnn_DIR
  • 5.3 放置opencv的安卓文件:https://github/nihui/opencv-mobile
    下载opencv-mobile-XYZ-android.zip
    课程中使用opencv-mobile-4.5.3-android.zip
    解压opencv-mobile-XYZ-android.zip后放置到app/src/main/jni并修改 app/src/main/jni/CMakeLists.txt中的 OpenCV_DIR
  • 5.4 修改CMakeLists.txt文件
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)
  • 5.5 自己数据集训练模型的部署
    asset文件下,替换自己训练及转化的模型
    修改yolox.cpp和yoloxncnn.cpp文件中的class_names(自己按着自己的任务修改)

6 连接手机

  • 6.1 手机打开开发者模型,并打开开发者模式下的USB调试功能。
  • 6.2 此时手机界面弹出提示窗口,选择允许,Android Studio就会连接到手机了。
  • 6.3 点击右上方app左侧的绿色小锤子(不知道叫啥)再加载一次需要的环境,然后点击右侧的绿色三角开始键,apk就部署到手机上了。
    下面是我最新的部署结果:
    部署前:

    自己的模型部署后:

    有疑问的小伙伴可以联系我一起解决~~

本文标签: 模型 方法 YOLOX ncnn Android