前言
代码全部抄袭自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));
}
网友评论