1 Star 27 Fork 12

CoderPeng/ncnn-yolov8-android

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
BSD-3-Clause

ncnn-yolov8-android

腾讯高性能神经网络前向计算框架——ncnn联合yolov8模型、OpenCV框架交叉编译移植到Android平台。

1、课题背景

本课题原本采用Android端采集实时画面帧,然后通过网络将画面帧传递到媒体服务器,服务器再用Python+Yolov8对画面帧做检测和识别,最后将结果返回给Android端去绘制目标检测结果。这样做最大的问题就是延时,经过局域网、4/5G/WiFi网络测试,延时大概1-2s,此方案并不是最优解。为了优化(解决)此痛点,就必须将目标检测和识别移植到Android端,否则这个延时不可能会降下来。

2、解决方案

如题,采用 ncnn + yolov8 + opencv 三个框架实现这一目标

3、集成三个框架移植到Android端

3.1、Android Studio 新建C++项目

注意,必须是C++项目,否则交叉编译环境会把人搞疯,不要走弯路了,步骤如下: 微信截图_20240603170904.png

微信截图_20240603170934.png

微信截图_20240603170944.png

图二根据自己熟悉的语言选择即可,不一定非得Kotlin(Java也是可以的)。至于其他的选项,建议按我的来,不然出问题了,会把人搞疯。

项目建完之后,按照Android Studio Giraffe版本的Android Studio会自动给你下载8.x版本的Gradle,高版本的Gradle的在Android Studio Giraffe上面不咋好使,咱改成本地的Gradle仓库路径(如果本地没有Gradle的,点击下载 ),如下图:

微信截图_20240603171618.png

然后修改项目根目录下的 settings.gradle 如下:

rootProject.name = "Test"
include ':app'

注意,rootProject.name修改为自己的项目名。其余代码全部删除,用不到。

再修改项目根目录下的 build.gradle 如下:

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext.kotlin_version = '1.6.10'
    repositories {
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
        mavenCentral()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.2.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        //阿里云镜像
        maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
        //依赖库
        maven { url 'https://jitpack.io' }
        mavenCentral()
        google()
    }
}

tasks.register('clean', Delete) {
    delete rootProject.buildDir
}

这个不用改,直接复制进去

再然后修改 app 目录下面的 build.gradle 如下:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
    compileSdkVersion 33

    defaultConfig {
        applicationId "com.casic.test"
        minSdkVersion 24
        targetSdkVersion 33
        versionCode 1000
        versionName "1.0.0.0"
        ndkVersion "26.1.10909125"
        ndk {
            abiFilters "arm64-v8a", "armeabi-v7a", "x86_64", "x86"
        }
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = '1.8'
    }

    externalNativeBuild {
        cmake {
            path file('src/main/cpp/CMakeLists.txt')
            version '3.22.1'
        }
    }

    buildFeatures {
        viewBinding true
    }
}

dependencies {
    implementation 'androidx.core:core-ktx:1.9.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.6.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
    //基础依赖库
    implementation 'com.github.AndroidCoderPeng:Kotlin-lite-lib:1.0.10'
}

对着自己项目的修改此文件,dependencies里面的内容可以复制过去

然后点击OK,最后在主界面点击”Try Again“即可。到此,C++项目的的环境已经配置完毕,接下来修改Android的几个基本配置。

首先修改 AndroidManifest.xml 如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.casic.test">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature 
        android:name="android.hardware.camera" 
        android:required="false" />

    <application 
        android:allowBackup="true" 
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name" 
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true" 
        android:theme="@style/Theme.Test">
        <activity android:name=".MainActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

对着自己项目的修改此文件。

然后修改 MainActivity.kt 如下:

class MainActivity : KotlinBaseActivity<ActivityMainBinding>() {
    override fun initEvent() {

    }

    override fun initOnCreate(savedInstanceState: Bundle?) {

    }

    override fun initViewBinding(): ActivityMainBinding {
        return ActivityMainBinding.inflate(layoutInflater)
    }

    override fun observeRequestState() {

    }

    override fun setupTopBarLayout() {

    }
}

把原来自带的那部分代码全删了,用不到。

最后修改 gradle.properties 如下:

org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
kotlin.code.style=official
android.nonTransitiveRClass=true
android.enableJetifier=true

直接复制即可。然后点击”Sync Now“

最后点击顶部三角形,如果能运行,就表示已经完全完成C++项目配置。

3.2、集成腾讯神经网络框架-ncnn

先下载腾讯ncnn开源库最新的框架,如图:

微信截图_20240603180404.png

然后解压,复制到项目的cpp目录下,不要改任何文件以及代码,如下图: 微信截图_20240603180840.png

修改新建项目时侯生成的 CMakeLists.txt,如下:

project("test")

cmake_minimum_required(VERSION 3.22.1)

set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20240410-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn)
find_package(ncnn REQUIRED)

project根据实际情况修改即可,然后点击”Sync Now“

最后点击顶部三角形,如果能运行,就表示已经完成ncnn框架配置。

3.3、集成opencv-mobile框架

同理,先去opencv-mobile 下载最新框架,如图(最新版本是:4.10.0):

微信截图_20240603181800.png

然后解压,复制到项目的cpp目录下,不要改任何文件以及代码,如下图(最新版本是:4.10.0):

微信截图_20240603182024.png

修改上一步的 CMakeLists.txt,添加如下代码:

set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/opencv-mobile-4.10.0-android/sdk/native/jni)
find_package(OpenCV REQUIRED core imgproc)

在之前修改过的cmake文件里面加如上两行,然后点击”Sync Now“。最后点击顶部三角形,如果能运行,就表示已经完成opencv-mobile框架配置。

3.4、集成OpenCV框架

这里的OpenCV和3.3里面的opencv-mobile是有区别的,opencv-mobile是专门针对移动端做了优化。此处引入OpenCV的目的是为了后面的画面预览的数据矩阵。同样去OpenCV 官网下载最新的Android端SDK,如下图:

微信截图_20240604083058.png

然后解压在桌面备用,按如下步骤操作:

QQ截图20240604083401.png

微信截图_20240604083549.png

导入进去之后会报错,别慌,按下面步骤修改即可:

  1. 首先修改app目录下面的build.gradle,在dependencies括号里面添加一行代码,如下图:
implementation project(':sdk')

这里的”sdk“就是刚刚导入进来的OpenCV依赖库的名字,如果按照我的步骤来没有改过名字的应该就是这个,如果自己改过名字的,这里填写你改过的依赖库名字。

  1. 然后再修改sdk里面的build.gradle ,将里面的compileSdkVersion、minSdkVersion和targetSdkVersion三个字段改为了主项目一致,并删掉其中的如下代码:
    publishing {
        singleVariant('release') {
            withSourcesJar()
            withJavadocJar()
        }
    }
  1. 最后在主界面点击”Try Again“即可完成OpenCV的集成,最后效果如下(最新版本是:4.10.0):

微信截图_20240604084455.png

到此,三大框架集成完毕。

4、导入自研yolov8的模型

在 app 的 main 目录下新建 assets 文件夹(一定要这个名字,别自己另辟蹊径),将Python导出的yolov8模型(后缀是 *.bin 和 * .param,如果不是这俩后缀的自行查找解决方案)复制进去即可。暂时先不用管,备用。

5、JNI配置

5.1、什么是JNI?

JNI(Java Native Interface),是方便Java/Kotlin调用C/C++等Native代码封装的一层接口。简单来说就是Java/Kotlin与C/C++沟通的桥梁。

5.2、新建 Yolov8ncnn.java

app/src/main/java/自己的包名 目录下新建 Yolov8ncnn.kt(Yolov8ncnn.java 也是可以的),代码如下:

class Yolov8ncnn {
  companion object {
    init {
      System.loadLibrary("yolov8ncnn")
    }
  }

  /**
   * @param mgr 手机内存资源管理器
   * @param modelId 模型ID
   * @param useGpu 是否使用GPU
   * @param useClassify 是否使用分类模型
   * @param useSegmentation 是否使用分割模型
   * @param useDetect 是否使用检测模型
   * */
  external fun loadModel(
    mgr: AssetManager, modelId: Int, useGpu: Boolean, useClassify: Boolean,
    useSegmentation: Boolean, useDetect: Boolean
  ): Boolean

  /**
   * @param mgr 手机内存资源管理器
   * @param ids 多模型ID数组
   * @param useGpu 是否使用GPU
   * */
  external fun loadMultiModel(mgr: AssetManager, ids: IntArray, useGpu: Boolean): Boolean

  /**
   * @param facing 相机 0-前置镜头,1-后置镜头
   * */
  external fun openCamera(facing: Int): Boolean

  external fun closeCamera(): Boolean

  external fun setOutputWindow(
    surface: Surface, nativeObjAddr: Long, callBack: INativeCallback
  ): Boolean

  external fun onPause(): Boolean

  external fun onRestart(): Boolean
}

companion object(伴生对象,类似Java里面的static关键字)包裹的代码意思是C/C++代码编译之后动态链接库的名字(此时还没有,因为还没有添加C/C++代码)。另外四个方法和普通的Kotlin方法的区别在于全部都有external关键字修饰(Java里面是native),表明这几个方法需要调用C/C++代码,也就是前文提到的”桥梁“。此时代码会报错,是因为还没有在C/C++里面实现它们,先不用管。

5.3、将本项目的 ndkcamera.cppndkcamera.h 复制到自己项目

这是底层相机相关的代码逻辑,包括相机打开、关闭、预览、数据回调等,通用代码,无需修改。此时会爆一堆错误提示,别慌,先不用管。

5.4、将本项目的 yolo.cppyolo.h 复制到自己项目

这两代码文件主要的功能是对相机预览产生的nv21数据进行处理,包括nv21转换、nv21转Mat矩阵、图像裁剪、灰度处理、调用模型检测目标、显示检测结果、回调等一些列操作,底层逻辑就在此实现。此时依旧会爆一堆错误提示,别慌,先不用管。

5.5、将本项目的 yolov8ncnn.cpp 复制到自己项目

此代码文件主要包括相机初始化,参数初始化。整体来说就是各种初始化以及状态管理。

5.6、修改 app/src/main/cpp 目录下的 CMakeLists.txt

添加如下两行代码:

add_library(yolov8ncnn SHARED yolov8ncnn.cpp yolo.cpp ndkcamera.cpp)

target_link_libraries(yolov8ncnn ncnn ${OpenCV_LIBS} camera2ndk mediandk)

最终的cmake代码如下图:

project(yolov8ncnn)

cmake_minimum_required(VERSION 3.10)

set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/opencv-mobile-4.10.0-android/sdk/native/jni)
find_package(OpenCV REQUIRED core imgproc)

set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20240102-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)

5.7、Java/C/C++代码调整

复制过去的yolov8ncnn.cpp文件,有四个函数一定是没有高亮的,如下图: 微信截图_20240707213433.png

此时需要将此函数根据情况修改为自己项目包名_函数名的方式,”.“用”_ “代替,比如:com.casic.test.Yolov8ncnn应改为Java_com_casic_test_Yolov8ncnn,改了之后就会发现,这四个函数已经高亮了,说明桥接代码已经生效。

  • Java 微信截图_20240707214218.png

  • Cpp 微信截图_20240707214345.png

可以看到Java和C++代码左侧已经出现相对应的代码标识。另外还有两个文件,就是setOutputWindow方法里面的DetectResult和INativeCallback,这俩都属于回调部分的代码,一个是数据模型类,一个是回调接口,直接复制即可。

如果没有出现以上效果的,先点击”Sync Now“,再”Build-Clean Project“,再”Build-Rebuild Project“,再”Build-Refresh Linked C++ Projects“,最后关闭工程重新启动Android Studio,此时应该就没问题了。

6、JNI编码

此模块需要有能看懂的C/C++代码的的能力,以及非常熟练的使用Kotlin/Java的能力。

6.1、修改yolov8ncnn.cpp

  • 定义全局指针变量
 static JavaVM *javaVM = nullptr;
  • 修改JNI_OnLoad方法,在相机初始化之前添加一行如下代码:
 javaVM = vm;
  • 修改Java_com_casic_test_Yolov8ncnn_loadModel方法(注意自己的包名)

将model_types、target_sizes、mean_values、norm_values改为如下代码:

//分割、分类、检测
const char *model_types[] = {"best-sim-opt-fp16", "model.ncnn", "yolov8s-detect-sim-opt-fp16"};
const int target_sizes[] = {320, 320, 320};
const float mean_values[][3] = {
        {103.53f, 116.28f, 123.675f},
        {103.53f, 116.28f, 123.675f},
        {103.53f, 116.28f, 123.675f}
};
const float norm_values[][3] = {
        {1 / 255.f, 1 / 255.f, 1 / 255.f},
        {1 / 255.f, 1 / 255.f, 1 / 255.f},
        {1 / 255.f, 1 / 255.f, 1 / 255.f}
};

其中model_types里面的值是你yolov8模型去掉后缀后剩下的部分,一定要注意,否则会报错,找不到模型。

  • 修改Java_com_casic_test_Yolov8ncnn_setOutputWindow方法(同样注意包名),在return前面加一行代码:
g_yolo->initNativeCallback(javaVM, nativeObjAddr, native_callback);

以上这些,我在代码里面已经加好,注意下就可以了。有个值得注意的地方,在此文件的on_image_render函数,里面的注释我也写清楚了,可以根据需求选择draw和draw_fps,如果不需要,可以都注释掉,不影响后面的逻辑。

6.2、修改Yolo.h

  • 添加全局变量
    JavaVM *javaVM;
    //Java传过来的Mat对象内存地址
    jlong j_mat_addr;
    //回调类
    jobject j_callback;
  • 添加Java/C++初始化函数
void initNativeCallback(JavaVM *vm, jlong nativeObjAddr, jobject pJobject);

6.3、修改Yolo.cpp

  • 修改generate_proposals函数

根据自己模型能够识别的目标种类修改此函数的num_class字段,比如,此处我已改为如下:

const int num_class = 43;
  • 实现自己在Yolo.h里面定义的setNativeCallback函数
void Yolo::initNativeCallback(JavaVM *vm, jobject input, jlong nativeObjAddr, jobject pJobject) {
    javaVM = vm;

    /**
     * JNIEnv不支持跨线程调用
     * */
    JNIEnv *env;
    vm->AttachCurrentThread(&env, nullptr);
    j_mat_addr = nativeObjAddr;

    j_callback = env->NewGlobalRef(pJobject);
}

有个注意点,JNIEnv不支持跨线程调用,一定要注意,否则会报错,之前在Yolo.h定义的全局变量也需要在此处初始化。 以上这些,我在代码里面已经加好,如果要加自己的逻辑,知道在此处改就行了。

  • 修改detect函数(划重点!划重点!划重点!)

其实在以上步骤完成时候就已经能把自定义的模型在Android端跑起来了(运行了一下,没效果???那是自然,因为 MainActivity.kt 还没有实现逻辑),已经可以检测目标了。

但是有缺陷,第一就是检测的结果只能在C++层面使用,Java/Kotlin层无法用,所以修改此函数的目的就是把检测结果回传到应用层,让应用层去做具体业务逻辑处理。第二个就是,C++底层只能渲染英文字符,中文的显示不出来或者显示乱码,当然也不是没有解决思路,需要交叉编译freetype2这个库,有兴趣的可以自行实现。

知识点预热

  1. 基本类型签名
Java JNI 签名
byte jbyte B
char jchar C
double jdouble D
float jfloat F
int jint I
short jshort S
long jlong J
boolean jboolean Z
void void V
  1. 引用数据类型的转换
Java JNI 签名
所有对象 jobject L+classname +;
Class jclass Ljava/lang/Class;
String jstring Ljava/lang/String;
Throwable jthrowable Ljava/lang/Throwable;
Object[] jobjectArray [L+classname +;
byte[] jbyteArray [B
char[] jcharArray [C
double[] jdoubleArray [D
float[] jfloatArray [F
int[] jintArray [I
short[] jshortArray [S
long[] jlongArray [J
boolean[] jbooleanArray [Z

预热完毕,举几个例子:

函数:int add(int a, int b)
签名:(II)I
说明:入参两个整型,返回值为整型

函数:String concat(String str1, String str2)
签名:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
说明:入参两个String类型,返回值为String类型

[Ljava/lang/String;
表示 String 类型的一维数组

回归正题,回传结果给上层,理清步骤就很简单,用过Java反射的这里理解起来应该不难

  1. 获取Java代码的回调接口,得到回调接口的jclass
    JNIEnv *env;
    javaVM->AttachCurrentThread(&env, nullptr);
    jclass callback_clazz = env->GetObjectClass(j_callback);

注意:JNIEnv不支持跨线程,所以必须通过之前定义的全局指针变量 javaVM 得到当前线程的JNIEnv。

  1. 根据或取到的jclass获取接口回调方法名,得到jmethodID
jmethodID j_method_id = env->GetMethodID(callback_clazz, "onDetect", "(Ljava/util/ArrayList;)V");
  1. 给回调入参的jobject设置值。此处只举个复杂点例子,基本类型的很简单就不展示了,具体返回值要看自己的逻辑
          for (const auto &item: objects) {
                auto rect = item.rect;

                float array[5];
                array[0] = rect.x;
                array[1] = rect.y;
                array[2] = rect.x + rect.width;
                array[3] = rect.y + rect.height;
                array[4] = (float) item.label;

                jfloatArray result_array = env->NewFloatArray(5);
                env->SetFloatArrayRegion(result_array, 0, 5, array);

                //add
                env->CallBooleanMethod(segment_array_obj, arraylist_add, result_array);
          }

上面的代码意思是给float[]赋值,从签名”[F“可以看出来,然后将float[]数组通过反射添加进ArrayList

  1. 发起回调

回调就很简单了,看清参数含义就行。意思就是在什么类里面调用什么方法,填入什么值

env->CallVoidMethod(j_callback, j_method_id, arraylist_obj);

至此,JNI产生的目标检测结果已经回调到上层,上层可以接下来就可以用回调结果处理相应的业务逻辑。但是这里只能传常见的数据类型,还有一种数据无法回传上去,那就是图像的Mat矩阵,这个到后面会介绍。

7、Kotlin编码

7.1、搭建界面布局

修改 app/src/main/res/layout/activity_main.xml 如下:

微信截图_20240707215753.png

7.2、初始化Yolov8ncnn和矩阵Mat对象,懒汉模式

private val yolov8ncnn by lazy { Yolov8ncnn() }
private val mat by lazy { Mat() }

7.3、在initOnCreate中加载模型以及初始化OpenCV和SurfaceView

override fun initOnCreate(savedInstanceState: Bundle?) {
  window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

  val openCVNativeLoader = OpenCVNativeLoader()
  openCVNativeLoader.init()

  yolov8ncnn.loadModel(
    assets, 2, useGpu = false,
    useClassify = false, useSegmentation = false, useDetect = true
  )

  binding.surfaceView.holder.setFormat(PixelFormat.RGBA_8888)
  binding.surfaceView.holder.addCallback(this)
}

7.4、在onResume里面打开相机

打开之前需要给应用授予相机的权限,否则会报错

override fun onResume() {
    super.onResume()
    if (ContextCompat.checkSelfPermission(
            this, Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_DENIED
    ) {
        ActivityCompat.requestPermissions(
            this, arrayOf(Manifest.permission.CAMERA), 100
        )
    }
    yolov8ncnn.openCamera(1)
}

7.5、实现SurfaceHolder.Callback回调

只需要实现surfaceChanged方法,surfaceCreated和surfaceDestroyed不必管

override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
  yolov8ncnn.setOutputWindow(holder.surface, mat.nativeObjAddr, this)
}

7.6、实现INativeCallback回调

override fun onDetect(output: ArrayList<FloatArray>) {
  if (output.isEmpty()) {
    return
  }

  //暂停算法
  yolov8ncnn.onPause()
  weakReferenceHandler.sendEmptyMessageDelayed(2024082901, 1000)
}

调用单模型(检测)获取目标所对应的场景,然后再根据场景切换为多模型(分割+检测)

    override fun handleMessage(msg: Message): Boolean {
        when (msg.what) {
            2024082901 -> {
                AlertControlDialog.Builder()
                    .setContext(this)
                    .setTitle("温馨提示")
                    .setMessage("识别到目标场景,是否开始识别目标?")
                    .setNegativeButton("重新识别")
                    .setPositiveButton("开始检查")
                    .setOnDialogButtonClickListener(object :
                        AlertControlDialog.OnDialogButtonClickListener {
                        override fun onConfirmClick() {
                            //调用多模型
                            yolov8ncnn.loadMultiModel(assets, intArrayOf(0, 2), false)
                        }

                        override fun onCancelClick() {
                            yolov8ncnn.onRestart()
                        }
                    }).build().show()
            }
        }
        return true
    }

处理多模型结果

    override fun onSegmentation(segmentationOutput: ArrayList<FloatArray>, detectOutput: ArrayList<FloatArray>) {
        //转成泛型集合
        val segmentationResults = ArrayList<YoloResult>()
        segmentationOutput.forEach {
            segmentationResults.add(it.convert2YoloResult(this))
        }

        val detectResults = ArrayList<YoloResult>()
        detectOutput.forEach {
            detectResults.add(it.convert2YoloResult(this))
        }

        binding.detectView.updateTargetPosition(segmentationResults, detectResults)

//        if (mat.width() > 0 || mat.height() > 0) {
//            val bitmap = Bitmap.createBitmap(mat.width(), mat.height(), Bitmap.Config.ARGB_8888)
//            Utils.matToBitmap(mat, bitmap, true)
//            bitmap.saveImage("${createImageFileDir()}/${System.currentTimeMillis()}.png")
//        } else {
//            Log.d(kTag, "width: ${mat.width()}, height: ${mat.height()}")
//        }
    }

此时detectView会报错,因为这是个自定义控件,可先注释掉,后面再说。

这里还有个隐藏的细节,那就是mat,哪来的值?

setOutputWindow里面有个入参,mat.nativeObjAddr,这个是Java/Kotlin层通过JNI往C++传入内存地址(可以理解为指针),然后在在 yolo.cpp 里面给此指针赋值,那么,这样就实现了Mat矩阵数据回传的效果。 在 int Yolo::detect(const cv::Mat &rgb, std::vector &objects, float prob_threshold, float nms_threshold) 的 return 前面加上如下代码:

    auto *res = (cv::Mat *) j_mat_addr;
    res->create(rgb.rows, rgb.cols, rgb.type());
    memcpy(res->data, rgb.data, rgb.rows * rgb.step);

7.7、在onPause里面关闭相机

override fun onPause() {
    super.onPause()
    yolov8ncnn.closeCamera()
}

自此,ncnn + yolov8 + opencv 这三个框架已完成在Android端的移植。将报错的地方先注释掉

// binding.detectView.updateTargetPosition(results)
  • 结果回调:

微信截图_20240604141117.png

  • Mat矩阵转PNG结果: 微信截图_20240604141334.png

7.8、实现自定义控件

如果没有特殊要求,直接复制过去即可,但是需要将里面的 classNames 改为自己模型对应的类别,虽然不会报错,但是会显示成错误的类别,注意下就行了。 然后修改app/src/main/res/layout/activity_main.xml 里面的内容如下: 微信截图_20240707220511.png

红框里面改成自己包名,然后编译运行即可。这样既方便了后续逻辑处理,也规避了C++不方便渲染中文的尴尬,效果如下:

20240604142258.png

以上就是完整的ncnn + yolov8 + opencv 移植到Android端的详细步骤,希望能帮到需要此需求的人!最后感谢各个开源者的辛勤付出(排名不分先后):

  • ncnn :腾讯开源的一个为手机端极致优化的高性能神经网络前向计算框架,无第三方依赖,跨平台,ncnn主要基于C++和caffe。
  • yolov8: YOLO(You Only Look Once)是一种流行的物体检测和图像分割模型,由华盛顿大学的约瑟夫-雷德蒙(Joseph Redmon)和阿里-法哈迪(Ali Farhadi)开发,YOLO 于 2015 年推出。
  • opencv-mobile: The minimal opencv for Android, iOS, ARM Linux, Windows, Linux, MacOS, WebAssembly
  • OpenCV: OpenCV(open source computer vision library)是一个基于BSD许可(开源)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android和Mac OS操作系统上。

BSD 3-Clause License Copyright (c) 2024, CoderPeng Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

简介

腾讯高性能神经网络前向计算框架——ncnn联合yolov8模型移植到Android平台 展开 收起
README
BSD-3-Clause
取消

发行版

暂无发行版

贡献者

全部

近期动态

不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/AndroidCoderPeng/ncnn-yolov8-android.git
git@gitee.com:AndroidCoderPeng/ncnn-yolov8-android.git
AndroidCoderPeng
ncnn-yolov8-android
ncnn-yolov8-android
master

搜索帮助