android yolov8 实时检测

前言

代码全部抄袭自FeiGeChuanShu/ncnn-android-yolov8: Real time yolov8 Android demo by ncnn (github.com)
虽然实现实时检测,但是由于数据全部在jni,java层无法调用,所以改了一下,以便在java层使用检测的数据。

注意

在Yolov8Ncnn类中新增的方法


    public native void detect(boolean start);  //开启或关闭绘制框

    public native void initGlobalObj();    // 全局化 jclass 和 jobject

    public native boolean getDetect();     // 获取 detect 状态

 /**
     * jni 调用的方法
     * 将 native层数据返回给java
     */
    public void processObjects(ArrayList<Object> objects) {
        // 在这里处理处理 jni 传过来 objects
        for (Object obj : objects) {
            // 处理每个 Object 对象
            Log.e(TAG, Thread.currentThread().getId() + "   Yolov8Ncnn======obj:" + obj.toString());
        }
    }

新增了heple类

#ifndef NCNN_ANDROID_YOLOV8_HEPLER_H
#define NCNN_ANDROID_YOLOV8_HEPLER_H

#include <jni.h>
#include "yolo.h"
#include <android/log.h>

class Helper {
public:
    Helper();
    /**
     * 
     * @param vm  用于JVN全局
     */
    void init(JavaVM *vm);
    
    /**
     * 
     * @param g_jclass 全局化jclass
     * @param thiz     全局化jobject
     */
    void initGlobal(jclass g_jclass ,jobject thiz);

    /**
     * 通过jvm 获取当前线程的jniEnv 
     */
    JNIEnv *getJNIEnv();

    /**
     * 将jni层数据传递给java层
     * @param vector 
     */
    void update2Android(std::vector<com::tencent::yolov8ncnn::Object> vector);

    /**
     * 销毁
     */
    void release();

private:
    JavaVM *g_jvm = NULL;
    jclass g_objectClass = NULL;
    jobject g_thiz = NULL;
};

#endif //NCNN_ANDROID_YOLOV8_HEPLER_H
#include "hepler.h"

Helper::Helper() {

}

void Helper::init(JavaVM *vm) {
    __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "=======执行Helper::init===========");
    g_jvm = vm;
}

// 调用原生android 方法
void Helper::update2Android(std::vector<com::tencent::yolov8ncnn::Object> objects) {
    JNIEnv *env = this->getJNIEnv();
    // 获取当前线程的 JNIEnv
    if (env == NULL) {
        return; // 获取 JNIEnv 失败
    }
    // 获取 Java 的 ArrayList 类引用
    jclass arrayListClass = env->FindClass("java/util/ArrayList");
    jmethodID arrayListConstructor = env->GetMethodID(arrayListClass, "<init>", "()V");
    jmethodID arrayListAddMethod = env->GetMethodID(arrayListClass, "add",
                                                    "(Ljava/lang/Object;)Z");

    // 创建 Java 的 ArrayList 对象
    jobject arrayListObject = env->NewObject(arrayListClass, arrayListConstructor);
    if (g_objectClass == NULL) {
        __android_log_print(ANDROID_LOG_ERROR, "ncnn", "没有找到java中的Object");
        return;
    }
    jmethodID objectConstructor = env->GetMethodID(g_objectClass, "<init>", "(IF)V");
    env->GetMethodID(g_objectClass, "getLabel", "()I");
    env->GetMethodID(g_objectClass, "getProb", "()F");

    // 将 std::vector<Object> 中的元素逐个转换为 Java 的 Object 对象,并添加到 ArrayList 中
    for (const auto &object: objects) {
        // 创建 Java 的 Object 对象
        jobject objectObject = env->NewObject(g_objectClass, objectConstructor,
                                              object.label,
                                              object.prob);
        // 添加 Object 对象到 ArrayList
        env->CallBooleanMethod(arrayListObject, arrayListAddMethod, objectObject);
        // 删除局部引用
        env->DeleteLocalRef(objectObject);
    }

    // 调用 Java 方法,并将 ArrayList 对象作为参数传递
    jclass javaClass = env->GetObjectClass(g_thiz);
    jmethodID javaMethodID = env->GetMethodID(javaClass, "processObjects",
                                              "(Ljava/util/ArrayList;)V");
    env->CallVoidMethod(g_thiz, javaMethodID, arrayListObject);

    // 删除局部引用
    env->DeleteLocalRef(arrayListObject);


}

// 通过过全局的JVM 找到当前线程jniEnv
JNIEnv *Helper::getJNIEnv() {
    JNIEnv *env;
    int getEnvStat = g_jvm->GetEnv((void **) &env, JNI_VERSION_1_4);
    if (getEnvStat == JNI_EDETACHED) {
        // 如果当前线程未附加到 Java VM,需要调用 AttachCurrentThread 进行附加
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", " 如果当前线程未附加到 Java VM");
        if (g_jvm->AttachCurrentThread(&env, NULL) != 0) {
            __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "当前线程未附加到 Java VM===失败");
            return NULL;
        }
    } else if (getEnvStat == JNI_EVERSION) {
        // JNI 版本不支持
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "当前线程未附加到 JNI 版本不支持");
        return NULL;
    }
    return env;
}

void Helper::initGlobal(jclass g_jclass, jobject thiz) {
    g_objectClass = g_jclass;
    g_thiz = thiz;
}

void Helper::release() {
    JNIEnv *pEnv = getJNIEnv();
    if (pEnv) {
        pEnv->DeleteGlobalRef(g_objectClass);
        pEnv->DeleteGlobalRef(g_thiz);
        g_thiz = NULL;
        g_objectClass = NULL;
    }

}

jniEnv跨线程调用问题以及局部全局化的问题

我处理数据回调的是在绘制检测框的时候调用,不管在时候时候,都会遇到该问题

void MyNdkCamera::on_image_render(cv::Mat &rgb) const {
    // nanodet
    {
        ncnn::MutexLockGuard g(lock);

        if (g_yolo && isDetected) {
            std::vector<com::tencent::yolov8ncnn::Object> objects;
            g_yolo->detect(rgb, objects);
            g_yolo->draw(rgb, objects);
            // 在此处将数据返回给java层
            helper->update2Android(objects);

        } else {
            draw_unsupported(rgb);
        }
    }

    draw_fps(rgb);
}

解决jniEnv跨线程调用问题 是在helper类中的getJNIEnv()方法

// 通过过全局的JVM 找到当前线程jniEnv
JNIEnv *Helper::getJNIEnv() {
    JNIEnv *env;
    int getEnvStat = g_jvm->GetEnv((void **) &env, JNI_VERSION_1_4);
    if (getEnvStat == JNI_EDETACHED) {
        // 如果当前线程未附加到 Java VM,需要调用 AttachCurrentThread 进行附加
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", " 如果当前线程未附加到 Java VM");
        if (g_jvm->AttachCurrentThread(&env, NULL) != 0) {
            __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "当前线程未附加到 Java VM===失败");
            return NULL;
        }
    } else if (getEnvStat == JNI_EVERSION) {
        // JNI 版本不支持
        __android_log_print(ANDROID_LOG_DEBUG, "ncnn", "当前线程未附加到 JNI 版本不支持");
        return NULL;
    }
    return env;
}

解决局部变量全局化的问题 就是通过NewGlobalRef方法将本地(Native)层的对象引用转换为全局引用


extern "C"
JNIEXPORT void JNICALL
Java_com_tencent_yolov8ncnn_Yolov8Ncnn_initGlobalObj(JNIEnv *env, jobject thiz) {
    jclass clazz = env->FindClass("com/tencent/yolov8ncnn/Object");
    helper->initGlobal((jclass) env->NewGlobalRef(clazz), env->NewGlobalRef(thiz));

}

END

代码ruirui1128/android-yolov8 (github.com)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,176评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 95,928评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 171,252评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,700评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,717评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,231评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,608评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,572评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,117评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,137评论 3 344
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,280评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,908评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,597评论 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,067评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,202评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 49,784评论 3 380
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,308评论 2 365

推荐阅读更多精彩内容