本文基于ncnn-android-yolov8(GitHub开源项目)撰写

项目源:请为飞哥点个♥

主要参考:集中了主要的一些问题的讨论板块

以及nihui大佬、ncnn社区的大家上传的开源项目为我提供提高理解能力的样例(♥爱你们哦♥)

只是效果图~ o(* ̄▽ ̄*)o

👇如果只是个别部分有问题👇

这篇文章是为了对YOLO有了解但是不是很熟悉的新手的,如果大家对项目和目标检测神经网络有一定认知了,想要寻求部署移动端(特指Android端)的方法,但在复现飞哥的项目时仍旧出现了一些问题的话。欢迎大家移步我对这个项目的FIX板块:

FIX板块(如果有帮助的话求个点赞嚄)


😄好啦,到这里就是正式的教程的开始了!😄

相信能找到文章的大家都是对YOLO有一定了解的:

目前YOLO在ultralytics公司的更新下已经迭代至yolo11了(不知道为什么没了v),而在github社区中研究和更新较多的开源项目都来自v5和v8,围绕这两个版本的yolo进行的应用会多一些。

相对与yolo11,yolov8已经有了足够多的功能了,而且在功能大类中二者相差不大(可以完成检测、分类、分割任务),故而我也认为版本号的大小不代表应用能力的大小。

所以本文将基于腾讯优图实验室的NCNN以及飞哥的开源项目,主要介绍YOLO-Detect,NCNN-Android,以及一些Android的知识,我的个人联系方式在个人简介,在本项目中出现任何问题欢迎在GitHub,CSDN以及通过我的私人联系方式与我一起交流学习!

考虑到本文受众多为新手,我会尽量使用脚本的方式执行各流程

对本文有任何细节上的问题欢迎移步我的FIX板块,里面有一些常见问题的详细解答

点击前往👉FIX板块


===全流程的步骤描述===

Ⅰ:准备组件

主项目:ncnn-android-yolov8

包外支持①:opencv-mobile(推荐4.10.0)

包外支持②:ncnn(推荐20240410)

Ⅱ:创建环境

创建两个环境:

conda environment:

        (pt2onnx):负责pt模型导出onnx(ultralytics版本号一致)

        (ncnn-main):负责模型训练,调参,测试等一系列主事件(ultralytics版本号一致)

原因:

        本项目导出模型的ultralytcis包是需要修改的而修改的ultralytics无法对新的数据进行训练,故而需要一个环境保持原封不动的ultralytics包用于训练和测试模型,一个环境更改环境中的ultralytics用于负责pt模型导出onnx模型。

        这样一来就避免了每次训练模型和导出模型反复修改ultralytics包,提高操作效率。

Ⅲ:配置环境

需求包:
ultralytics==8.0.50
onnx==1.16.1
onnxruntime==任意
onnx-simplifier==任意
onnxsim==任意

        飞哥的项目在开源之初需要对ultralytics包内函数进行修改,替换的语句和方法与现今的ultralytics包内函数不尽相同,有些是语句没有,有些是方法没了,理论上是可以一层层去读现在ultralytics包的函数和方法和飞哥的修改逻辑,重构modules来实现对现在的函数修改的,但是工程量有些大,笔者只是一个弱小的大二学生,实在无暇照料这么多。项目开源距今时间一年了,ultralytics一直在更新,内置大大小小的函数和包也在不断的更替变化。飞哥对ultralytics包内py文件及其函数的更改已经不再适配现在的版本,一句句去找注释掉的代码再按照飞哥的逻辑去重组函数有一定的挑战性(很显然我不是喜欢挑战的人)。

        所以,对此我的解决方案是回退到去年飞哥开源时的ultralytics的版本,我采用的ultralytics版本version==8.0.50

pip install ultralytics==8.0.50

至于onnx=1.16.1则是因为就我测试而言他是最稳定的版本,更高的版本可能在pip的时候会出问题

接下来按照以上导入ultralytics的格式导入其他必备函数即可。

Ⅳ:模型训练与测试

因为本文主旨在教会大家如何部署,所以直接使用带预训练参数的yolov8模型进行迁移训练演示,如果有更高精度要求的个性化需求,请下载ultralytics-main使用yaml文件进行训练!

正在前往:ultralytics-cfg-models-v8(或其他version)

一、数据集准备:
整个文件夹看起来会像是:

images下放置数据集的图像文件

labels下放置数据集的标签文件

如果不清楚图像和标签具体指什么,请看我的这篇博文:欢迎点赞嚄❤

二、yaml文件配置:
可以参照我的模板编写yaml:
# data.yaml
train: D:/package-python/YOLO/datasets/bvn/images/train
val: D:/package-python/YOLO/datasets/bvn/images/val
nc: 8  # number of classes
names: ["Lois","Chris","Meg","Stewie","Peter","Brain","Glenn","Joeg"]  # class names
参数讲解:

train&val:分别配置图像数据集的train图像文件夹和val图像文件夹路径,推荐使用绝对路径

nc:labels的classes,填入数据集中类别的数量即可

names:labels的个体,以字符串列表形式按照class.txt的顺序填写labels

三、训练脚本:
参照我的模板:
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
from ultralytics import YOLO
import multiprocessing
def train_model():
    # 加载预训练模型
    model = YOLO('要训练的模型.pt')
    # 开始训练
    results = model.train(
        data='data.yaml',  # 数据配置文件
        epochs=100,        # 训练轮数
        imgsz=320,         # 输入图像大小
        batch=32,          # 批处理大小
        device='cuda:0',   # 使用GPU设备编号(如果可用),或设置为'cpu'
        workers=0,         # DataLoader使用的线程数
    )
# 确保主模块代码被包含在这个块中
if __name__ == '__main__':
    train_model()
参数讲解:

我好像写了注释欸O(∩_∩)O

四、测试脚本:
参照我的模板:
from ultralytics import YOLO
model=YOLO("runs/detect/train/weights/best.pt",task="detect")
results=model(source="./vedio/cs-family.mp4",conf=0.15,show=True,device="cuda:0")

区别于torch.load(),YOLO模型需要通过ultralytics.YOLO()加载

参数讲解:

声明model实例接收任务,第一个参数就是model=无需设置”形参=“格式,就是需要测试的模型相对路径,后参数设置“task=”指定给实例的任务是什么,如“detect”即检测。

results让model go work,设置source=”图像或视频文件路径“或“0”(摄像头)或“screen”(屏幕)

conf=“0~1”,设定值确定了出框的最低置信度,当预测结果达到了最低要求才会出框显示锁住的目标,默认值为0.3,一开始模型精度不高建议设的低一些保证自己有反馈。

show=bool,设定值T/F决定了是否向我们图形化展示检测过程

save=bool,设定值T/F决定了是否存储预测结果,一般save存储的路径是在脚本同级文件夹下新建的run下,新建以task命名的文件夹下,带train(version,第几次训练version就是几)。

device=“训练的设备”,一般是”cuda:X“(第X块GPU)或“cpu”

其余比较重要的参数:
mod=“要求进行预测要是训练”

model=“训练的模型”

Ⅴ:修改函数(ultralytics)

从这部分开始,我们已经进入了模型转换的前提预备了,通过前文的学习,聪明的你应该不难发现,一直到模型测试部分,我们的模型后缀提示他所属的类型都是PT,而在这个项目中,为了部署Android,我们需要让这个后缀变成ncnn格式(param&bin)

转换流程为:pt2onnx、onnx2ncnn

NCNN简介:目前移动端部署效率最高的框架,原先这个活是给ONNX干的(由nihui大神的不懈努力,现在有PNNX了)

一、pt2onnx

不想慢慢找的话可以直接clone我的modules.py:看完别忘记点♥嚄

C2F层改动:

修改路径样例:

        D:\For_anaconda\envs\ncnn\Lib\site-packages\ultralytics\nn\modules.C2F

class C2f(nn.Module):
    # CSP Bottleneck with 2 convolutions
    def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5):  # ch_in, ch_out, number, shortcut, groups, expansion
        super().__init__()
        self.c = int(c2 * e)  # hidden channels
        self.cv1 = Conv(c1, 2 * self.c, 1, 1)
        self.cv2 = Conv((2 + n) * self.c, c2, 1)  # optional act=FReLU(c2)
        self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n))
 
    def forward(self, x):
        # y = list(self.cv1(x).chunk(2, 1))
        # y.extend(m(y[-1]) for m in self.m)
        # return self.cv2(torch.cat(y, 1))
        print('forward C2f')
        x = self.cv1(x)
        x = [x, x[:, self.c:, ...]]
        x.extend(m(x[-1]) for m in self.m)
        x.pop(1)
        return self.cv2(torch.cat(x, 1))
 
    def forward_split(self, x):
        y = list(self.cv1(x).split((self.c, self.c), 1))
        y.extend(m(y[-1]) for m in self.m)
        return self.cv2(torch.cat(y, 1))
Detect层改动:

修改路径样例:

        D:\For_anaconda\envs\ncnn\Lib\site-packages\ultralytics\nn\modules.Detect

class Detect(nn.Module):
    # YOLOv8 Detect head for detection models
    dynamic = False  # force grid reconstruction
    export = False  # export mode
    shape = None
    anchors = torch.empty(0)  # init
    strides = torch.empty(0)  # init
 
    def __init__(self, nc=80, ch=()):  # detection layer
        super().__init__()
        self.nc = nc  # number of classes
        self.nl = len(ch)  # number of detection layers
        self.reg_max = 16  # DFL channels (ch[0] // 16 to scale 4/8/12/16/20 for n/s/m/l/x)
        self.no = nc + self.reg_max * 4  # number of outputs per anchor
        self.stride = torch.zeros(self.nl)  # strides computed during build
 
        c2, c3 = max((16, ch[0] // 4, self.reg_max * 4)), max(ch[0], self.nc)  # channels
        self.cv2 = nn.ModuleList(
            nn.Sequential(Conv(x, c2, 3), Conv(c2, c2, 3), nn.Conv2d(c2, 4 * self.reg_max, 1)) for x in ch)
        self.cv3 = nn.ModuleList(nn.Sequential(Conv(x, c3, 3), Conv(c3, c3, 3), nn.Conv2d(c3, self.nc, 1)) for x in ch)
        self.dfl = DFL(self.reg_max) if self.reg_max > 1 else nn.Identity()
 
    def forward(self, x):
        shape = x[0].shape  # BCHW
        for i in range(self.nl):
            x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
        if self.training:
            return x
        elif self.dynamic or self.shape != shape:
            self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
            self.shape = shape
 
        # if self.export and self.format == 'edgetpu':  # FlexSplitV ops issue
        #     x_cat = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2)
        #     box = x_cat[:, :self.reg_max * 4]
        #     cls = x_cat[:, self.reg_max * 4:]
        # else:
        #     box, cls = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).split((self.reg_max * 4, self.nc), 1)
        # dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides
        # y = torch.cat((dbox, cls.sigmoid()), 1)
        # return y if self.export else (y, x)
        print('forward Detect')
        pred = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).permute(0, 2, 1)
        return pred
 
    def bias_init(self):
        # Initialize Detect() biases, WARNING: requires stride availability
        m = self  # self.model[-1]  # Detect() module
        # cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1
        # ncf = math.log(0.6 / (m.nc - 0.999999)) if cf is None else torch.log(cf / cf.sum())  # nominal class frequency
        for a, b, s in zip(m.cv2, m.cv3, m.stride):  # from
            a[-1].bias.data[:] = 1.0  # box
            b[-1].bias.data[:m.nc] = math.log(5 / m.nc / (640 / s) ** 2)  # cls (.01 objects, 80 classes, 640 img)

完成上述修改后,在环境中建立pt2onnx.py

采用以下代码导出onnx模型,onnx将位于选定pt模型同级文件夹下,可以用netron查看网络

from ultralytics import YOLO
model = YOLO("训练好的模型.pt")
success = model.export(format="onnx", opset=12, simplify=True)

至此,pt2onnx这个模块我们就完美通过了!

二、onnx2ncnn

这个部分比较麻烦,需要使用CMake,且需要加载NCNN支持和protobuf、Vulkan

我自己用Ubantu比较多,但是考虑到新手可能用Windows还是占大多数的,对CMake和一些命令行操作不甚了解,于是我就把这部分操作为大家省去了,命令行和CMake还是在大家今后做相关项目再慢慢了解吧,单纯对这个项目来说,这不重要,不用太担心不会就少了什么,操作只是手段,当前阶段我们的目的是让输入有输出。(我想知道我们这代计算机学生到底讨不讨厌命令行,作为菜鸡,原本我更喜欢脚本QAQ,但是命令行用多了,感觉命令行好快,难道再也回不去了嘛QAQ)

麻烦的事情我就帮大家做啦!

使用我的GIT

是急切猫猫大人的GIT!(有帮助的话千万别忘了点赞支持嚄♥)v

Linux:下载库中的onnx2ncnn

Windows:下载库中的onnx2ncnn.exe

使用方法:

Ubantu下如果会一些bash操作的话,可以使用bash直接操作conda环境,但是对新手不太友好

在此,我使用pycharm的terminal为大家做演示操作

因为pycharm在配置python编辑器时已经选择了conda环境,所以terminal就自适应配置了选择的conda环境,在我的示例中,环境名称为ncnn,环境配置了我先前说明的所有包。

命令行操作:

./onnx2ncnn last.onnx last.param last.bin

可以看到,在执行bash语句后,在我放置onnx2ncnn的同级文件夹下生成了ncnn格式的模型,即param&bin

至此,我们的onnx2ncnn模块也完美结束啦!


Ⅵ:模型转换

额(⊙﹏⊙)额

好尴尬......

我好像已经在改函数的部分把这部分都讲完了,怎么办,标题都给了,不能空在这里!

哼哼,这就是万能的急切猫猫大人的水字数方法!

O(∩_∩)O

Ⅶ:模型调用

从这里开始,我们就要正式移步Android Studio啦!

Android Studio:

点击前往👉Android Studio CN

推荐使用:android-studio-2024.1.2.12-windows.exe

这个版本是可以自行加载JB家族软件的汉化插件的,最新的测试版不行(我有个朋友),太悲催了,我用了好久纯英文界面,再回头回去汉化真的有点不太适应

下载完成之后全选支持就完了,反正后面都得用到

JB家族汉化插件:点击前往👉JB汉化包(推荐选择241.271)

可能会遇到proxy(HTTP代替)问题:

进入setting,将HTTP代理设置为(阿里云的速度我还是认可的)

mirrors.aliyun.com

如果你之前用过Android Studio,有可能Gradle不知道在什么时候被配置了一个错误的代理

如果出现多个镜像源冲突的问题,就进入C:\Users\***\.gradle\gradle.properties

比如这样的:

## For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx1024m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
#
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
#Tue May 15 12:14:36 CST 2018
systemProp.https.proxyPort=80
systemProp.http.proxyHost=mirrors.neusoft.edu.cn
systemProp.https.proxyHost=mirrors.neusoft.edu.cn 
systemProp.http.proxyPort=80

删除最后四行就好了,这四行是用来写HTTP镜像源的,删了他们,再去Android Studio中图形化配置即可。

以上就是下载部分可能出的问题


ncnn-android-yolov8:

接下来先来瞻仰一下飞哥的项目源:用完别忘了给飞哥点个♥嚄

因为我自己的项目因为要打比赛所以改过很多地方啦(会在赛后向大家开源的),所以在本篇教程中,我将从头clone飞哥的项目,一步步向大家展示如何玩转他。

支持问题:

在使用项目前,我们需要额外配置两个必须的支持:

支持在本项目中特指:

opencv-mobile-A.B.C-android(A、B、C代指数字)

ncnn-YYYYMMDD-android-vulkan(YMD代指版本发布时间)

正在前往👉opencv-mobile(爱来自nihui)

正在前往👉ncnn(还是来自nihui)

在飞哥的工程项目源码中,这个修改项在README中被提及,飞哥的开源项目中并未包含这两个包,而在我们部署时,这两个包是必备的。具体部署位置已经写明在README中。

飞哥开源项目自配的版本号,按理讲是最合适的

飞哥使用的版本号为:

opencv-mobile-4.5.1-android

ncnn-20220420-android-vulkan

当操作一个工程项目时,我总是喜欢用最新的支持,但是在飞哥这个项目上,我尝到了严重的挫败,并开始理解,对程序员的学习来说,永远是旧的东西更加方便,毕竟计算机的东西,任何教程都有滞后性。

目前最新的ncnn支持版本时间序号已经来到了20240820,opencv支持也来到了4.10.0,但在我调试多个版本对照兼容问题后,得出了一个暴论:

也许目前支持该项目的最新包组合是:

opencv-mobile-4.10.0-android

ncnn-20240410-android-vulkan

放置支持:

在解压完成后,我们需要将这两项支持放置在项目中,具体路径为:

ncnn-android-yolov8\app\src\main\jni下

欢迎参考我的文件夹:

更改CMakelist:

放置完别忘记替换cmakelist中的path哦!

当然也可以参考我的CMakelist:

project(yolov8ncnn)
 
cmake_minimum_required(VERSION 3.10)
 
set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/opencv-4.10.0-android-sdk/sdk/native/jni)
find_package(OpenCV REQUIRED core imgproc)
 
set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20240410-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn)
find_package(ncnn REQUIRED)
 
add_library(yolov8ncnn SHARED yolov8ncnn.cpp yolo.cpp ndkcamera.cpp)
 
target_link_libraries(yolov8ncnn ncnn ${OpenCV_LIBS} camera2ndk mediandk)
Gradle、AGP以及CMake的适配问题:
        cmake:3.10.2.4988404+

        以现在为基准,clone完飞哥的项目后在本地加载的第一步会给你来个大惊喜,一个加粗了SDK字眼的红色大三角(悲),这一般是因为我们没给项目配置cmake的path。

        操作位置:local-properties
        操作描述:其中的sdk.dir会在加载该项目时auto配置

        此外,我们需要配置cmake.dir,cmake的位置就在sdk文件夹之下,sdk位置auto配置后,在文件资源管理器中找到sdk,打开它,并在其下一级单位中找到名为cmake的file,打开它,现在就会显示你安装的各版本cmake,复制他们的绝对路径,写在cmake.dir=“path”。

        样例:他看起来会像是↓(Ubantu记得使用单斜杠替换路径)

## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Tue Oct 22 17:10:46 CST 2024
sdk.dir=C\:\\Users\\FOX_1\\AppData\\Local\\Android\\Sdk
cmake.dir=C\:\\Users\\FOX_1\\AppData\\Local\\Android\\Sdk\\cmake\\3.30.5

注意事项:如果不用飞哥自配的3.10.2.4988404版本,想要用新版本的cmake是可行的,需要再配置build.gradle(:app)

样例:他看起来会像是↓

apply plugin: 'com.android.application'
 
android {
    compileSdkVersion 24
    buildToolsVersion "29.0.2"
 
    defaultConfig {
        applicationId "com.tencent.yolov8ncnn"
        archivesBaseName = "$applicationId"
        minSdkVersion 24
    }
 
    // 添加命名空间属性
    namespace 'com.tencent.yolov8ncnn'  // 这里使用你的 applicationId 作为命名空间
 
    externalNativeBuild {
        cmake {
            version "3.30.5"
            path file('src/main/jni/CMakeLists.txt')
        }
    }
 
    dependencies {
        implementation 'com.android.support:support-v4:24.0.0'
    }
}
        AGP:8.5.0+

        操作位置:build.gradle(ncnn-android-yolov8)

        样例:他看起来会像是↓

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        jcenter()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:8.5.0'
    }
}
 
allprojects {
    repositories {
        jcenter()
        google()
    }
}

        就算安装了8.5.0也会出现如下提示框:

这很正常,不必在意(反正不是红的)

        gradle:8.7+

        操作位置:gradle-wrapper.properties

        样例:他看起来会像是↓

#Tue Oct 22 18:07:55 CST 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

至此我们在模型调用部分的所有预备工作都完结啦!

到这里,整个项目其实已经可以跑通了


Device设置:

飞哥在项目原件中为我们预备了可用的ncnn模型模板,分别是由yolov8n和yolov8s导出的两个ncnn模型。

目前的运行结果是这两个模型的预测结果,我们可以点击RUN查看运行效果。

在这里大家看到的Xiaomi 23013RK75C是我的真机,Windows环境下无需额外编译即可直接使用数据传输线链接真机,Ubantu下需要编译支持的组件才行。

注意是数据传输线,不是普通的充电线,这俩玩意还是有点差别的(最初拿着自己的充电线调试了半天疑惑怎么连不上电脑,才知道现在这俩东西不是一个东西了,买手机送的快充充电线只是单纯的充电线而已......)

如果目前手头没有数据传输线或者暂时缺少真机,可以在Android Studio中create一个虚拟机,相当方便(只是没有摄像头,不好展示效果,但是可以测试程序正确性,是否能跑通不闪退)

点击右侧Device Manager

点击Add a new device(就是那个小加号)

选择Create Virtual Device创建虚拟机

进入这个界面之后按照你的想法选择虚拟机的大小和型号即可,还能给虚拟机取名

这个API35在SDK Manager中可以加载

点击OK,顷刻创建

导航栏中选择TOOL,下属选择SDK Manager

进入SDK Manager后即可看到我们适配的一些插件,比如在本项目中的Android API(虚拟机)

NDK摄像机、CMake等

注意取消Hide历史版本,Show detail,这样方便选择我们适配兼容的支持


点击运行程序!

👇他看起来会像是这样的👇

👇或是这样的👇

虚拟机CPU占用太高了,刚开始启动不了,有一两次闪退是很正常的现象,多启动几次就OK了

当我们出错时可用对照样例查看自己的模型是否规范

至此我们的程序就可以跑起来啦!芜湖!o(* ̄▽ ̄*)ブ


Ⅷ:修改函数(ncnn-android-yolov8)

😄这部分开始我将教大家怎么自定义部署自己的模型😄

先放一张我的自定义模型效果图

如果你有自己的图像数据集文件,那就直接开标就完事了,没啥好说的,但是如果是视频文件,可以参考我的抽帧脚本对视频文件进行抽帧获取图片👇

视频抽帧(可选)

创建py脚本cpimg.py

import cv2
import os

# 视频文件路径
video_path = "./vedio/cs-family.mp4"

# 创建一个与视频同级的文件夹来保存帧
output_dir = os.path.join(os.path.dirname(video_path), 'frames')

# 如果输出目录不存在,则创建它
if not os.path.exists(output_dir):
    os.makedirs(output_dir)

# 初始化VideoCapture对象
video = cv2.VideoCapture(video_path)

num = 0  # 计数器
save_step = 30  # 间隔帧

while True:
    ret, frame = video.read()
    if not ret:
        break
    num += 1
    if num % save_step == 0:
        # 构建图片保存路径
        output_path = os.path.join(output_dir, f"{num}.jpg")
        cv2.imwrite(output_path, frame)

# 释放资源
video.release()

抽出的帧将被保存在video同级文件夹下

处理完数据后重复我的训练→测试→导出工作,最后使用netron查看网络,与飞哥的样例对照,直到得到有效ncnn格式模型,我们就可以进入下一步了


我们现在得到了有效的ncnn模型,那么让我们开始吧!

预备:修改yolo.cpp&yolov8ncnn.cpp

修改位置一:
yolo.cpp

此处与yolov8ncnn.cpp联动组合路径,%s拿取的modeltype来自yolov8ncnn.cpp的字串组

yolov8ncnn.cpp

飞哥的原文是:

yolo.cpp
yolov8ncnn.cpp

对于自定义部署模型来说,我觉得这两个分开写有点小麻烦,就做了些更改,只要把modeltype组中字串改名即可链接我们的模型路径。

修改位置二:

注意模型的IO,images/output是飞哥的样例模型的IO,当我们使用onnx2ncnn工具时,导出的模型IO为images/output0

Func1:靠拢模板,修改param最后层output0为output。

Fucn2:靠拢模型,修改yolo.cpp下IO为images/output0。

yolo.cpp
修改位置Ⅲ:

注意修改模型的类,需要同时修改类的数量(num_class)和标签(class_names),数量应<=标签数。

yolo.cpp
yolo.cpp

如果按照我的步伐一步步下来,仍有乱框和闪退问题的,可以移步我的FIX板块寻求更精准的回答

前往急切猫猫的FIX板块

放个乱框在这里,希望大家不要出这个问题<( ̄︶ ̄)↗[GO!]  

现在开始已经可以自定义部署我们的YOLOV8模型啦,继续点击RUN测试吧,享受你的成果!

O(∩_∩)O


Ⅸ:一些DIY方法(可选)

可以看到我的程序界面的按钮是”出生之家“,哈哈,想知道怎么改嘛,修改位置位于jni同级文件夹res中values下string.xml文件

在这里我们可以随意更改我们各个按钮的名称,不影响其他模块。

而在res下另一文件夹layout中有个main.xml文件。

这个文件与java中的MainActivity与Yolov8Ncnn联动,负责我们的图形界面,这个不方便在这里教学,感兴趣的小伙伴们可以多去学一些安卓开发的知识。


Ⅹ:最终调试

这个模块的意义就是查漏补缺嘛

放一些能做到的成品的样子给大家一些信心

 至此全流程板块就彻底完结啦,真的是花了好久写这个文档呢,给个免费的赞应该可以吧♥


同时欢迎关注专栏Android端部署YOLOV8

我将在专栏中持续更新相关内容

再次感谢腾讯优图实验室的开源ncnn项目和ncnn社区一众大佬的开源项目帮助!

爱你们!

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐