一、下载扩展文件(最耗时,所以放第一步)
1.opencv下载
1)官网:Releases - OpenCV
2)下载最新版本的android包
2.NCNN下载
1)NCNN下载地址(20220420版本):https://github.com/Tencent/ncnn/releases/download/20220420/ncnn-20220420-android-vulkan.zip
3.在你的android app目录下的build.gradle里面的dependencies添加
implementation 'org.opencv:opencv:4.10.0'
二、使用
1.下载代码示例https://github.com/FeiGeChuanShu/ncnn-android-yolov8
1)把ncnn-android-yolov8-main/ncnn-android-yolov8解压出来打开
2)复制你下载的opencv;ncnn包以及示例代码到这里
3)把这些文件复制进assets目录
4)在res/values/string.xml里面添加
<string-array name="model_array">
<item>n</item>
<item>s</item>
</string-array>
<string-array name="cpugpu_array">
<item>CPU</item>
<item>GPU</item>
</string-array>
5)创建jniLibs文件夹把OpenCV-android-sdk/sdk/native/libs里面的东西全放进来
6)修改CMakeLists.txt文件
# 项目名称
project(yolov8ncnn)
# 指定了构建项目所需的最小 CMake 版本为 3.10
cmake_minimum_required(VERSION 3.10)
# opencv下载地址
# https://opencv.org/releases
# 设置了 OpenCV 目录的路径,并使用 find_package 命令来查找 OpenCV 包。REQUIRED 参数表示如果找不到 OpenCV,则构建会失败。core 和 imgproc 是指定要使用的 OpenCV 组件
set(OpenCV_DIR ${CMAKE_SOURCE_DIR}/OpenCV-android-sdk/sdk/native/jni)
find_package(OpenCV REQUIRED core imgproc)
# ncnn下载地址
# https://github.com/Tencent/ncnn/releases
# 设置了 ncnn 目录的路径,并查找 ncnn 包。ncnn 是一个高性能神经网络推理框架,常用于移动设备上运行深度学习模型
set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20220420-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn)
find_package(ncnn REQUIRED)
# 定义了一个名为 yolov8ncnn 的共享库(.so 文件),它包含了 yolov8ncnn.cpp, yolo.cpp, 和 ndkcamera.cpp 这三个源文件
add_library(yolov8ncnn SHARED yolov8ncnn.cpp yolo.cpp ndkcamera.cpp)
# 指定了 yolov8ncnn 库需要链接的其他库。具体来说,它将链接 ncnn 库、OpenCV 库(通过 ${OpenCV_LIBS} 变量)、以及 camera2ndk 和 mediandk 这两个库
target_link_libraries(yolov8ncnn ncnn ${OpenCV_LIBS} camera2ndk mediandk)
7)创建SurfaceCallBack和Yolov8Ncnn文件
SurfaceCallBack文件内容
package com.youlian.weight.yolov8;
import android.util.Log;
import android.view.SurfaceHolder;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
public class SurfaceCallBack extends AppCompatActivity implements SurfaceHolder.Callback {
private String Tag = "SurfaceCallBack";
private Yolov8Ncnn yolov8ncnn;
public void setYolov8ncnn(Yolov8Ncnn yolov8ncnn){
this.yolov8ncnn = yolov8ncnn;
}
// 在 Surface 被创建时调用
@Override
public void surfaceCreated(SurfaceHolder holder) {
}
// 在 Surface 尺寸改变时调用
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
// 如果需要调整预览大小,可以在 surfaceChanged 中进行
Log.d(Tag, "尺寸改变");
yolov8ncnn.setOutputWindow(holder.getSurface(), this);
}
// 在 Surface 被销毁时调用
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
/**
* C++检测到商品的回调
* @param output
*/
public void onDetect(ArrayList<float[]> output) {
if (output != null) {
Log.e(Tag,"检测到" + output.size() + "个目标");
for (int i = 0; i < output.size(); i++) {
float[] array = output.get(i);
float x = array[0];
float y = array[1];
float width = array[2];
float height = array[3];
float floatValue = array[4];
int label = (int) floatValue;
float prob = array[5];
Log.d(Tag, "x:" + x + ", y:" + y + ", width:" + width + ", height:" + height + ", label:" + label + ", prob:" + prob);
}
} else {
Log.e(Tag, "未检测到商品");
}
}
}
Yolov8Ncnn文件内容
package com.youlian.weight.yolov8;
import android.content.res.AssetManager;
import android.view.Surface;
public class Yolov8Ncnn
{
public native boolean loadModel(AssetManager mgr, int modelid, int cpugpu);
public native boolean openCamera(int facing);
public native boolean closeCamera();
public native boolean setOutputWindow(Surface surface, SurfaceCallBack nativeCallback);
static {
System.loadLibrary("yolov8ncnn");
}
}
8)在首页初始化YOLO和opencv
/**
* 初始化 OpenCV
*/
public void openCvInit(){
// System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
System.loadLibrary("opencv_java4");
}
private void yolov8Init(){
// getPermissions(); // 获取相机和录音权限
SurfaceCallBack surfaceCallBack = new SurfaceCallBack();
surfaceCallBack.setYolov8ncnn(yolov8ncnn);
cameraView = findViewById(R.id.cameraView);
// cameraView.setVisibility(View.GONE);
cameraView.getHolder().setFormat(PixelFormat.RGBA_8888);
cameraView.getHolder().addCallback(surfaceCallBack);
int current_model = 0; // 使用模型id
int current_cpugpu = 0; // 使用gpu的ID
boolean ret_init = yolov8ncnn.loadModel(getAssets(), current_model, current_cpugpu);
if (!ret_init)
{
Log.e(Tag, "yolov8ncnn 加载模型失败!");
}
Log.d(Tag,"加载模型成功");
yolov8ncnn.openCamera(1);
}
9)页面添加(用于显示内容,可自己修改页面)
<SurfaceView
android:id="@+id/cameraView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent">
</SurfaceView>
10)修改jni里面的yolov8ncnn.cpp文件
还有这几个方法
名称修改规则:Java_com_你的包名_Yolov8Ncnn_loadModel
例如:我的包名是package com.youlian.weight.yolov8,Java_com_youlian_weight_yolov8_Yolov8Ncnn_loadModel
注意Yolov8Ncnn_setOutputWindow方法需要添加这个
JNIEXPORT jboolean JNICALL Java_com_你的包名_Yolov8Ncnn_setOutputWindow(JNIEnv* env, jobject thiz, jobject surface
, jobject native_callback)
{
ANativeWindow* win = ANativeWindow_fromSurface(env, surface);
__android_log_print(ANDROID_LOG_DEBUG, "ncnn", "setOutputWindow %p", win);
g_camera->set_window(win);
g_yolo->initNativeCallback(javaVM, native_callback);
return JNI_TRUE;
}
11)修改jni里面的yolo.h文件
/**
* 全局引用
* */
JavaVM *javaVM;
//回调类
jobject j_callback;
void initNativeCallback(JavaVM *vm, jobject pJobject);
12)修改jni里面的yolo.cpp文件
实现initNativeCallback函数
void Yolo::initNativeCallback(JavaVM *vm, jobject pJobject) {
javaVM = vm;
/**
* JNIEnv不支持跨线程调用
* */
JNIEnv *env;
vm->AttachCurrentThread(&env, nullptr);
j_callback = env->NewGlobalRef(pJobject);
}
把class_nams放外面去
搜索Yolo::detect方法添加
/**
* 回调给Java/Kotlin层
* */
JNIEnv *env;
javaVM->AttachCurrentThread(&env, nullptr);
jclass callback_clazz = env->GetObjectClass(j_callback);
jmethodID j_method_id = env->GetMethodID(
callback_clazz, "onDetect", "(Ljava/util/ArrayList;)V"
);
//获取ArrayList类
jclass list_clazz = env->FindClass("java/util/ArrayList");
jmethodID arraylist_init = env->GetMethodID(list_clazz, "<init>", "()V");
jmethodID arraylist_add = env->GetMethodID(list_clazz, "add", "(Ljava/lang/Object;)Z");
//初始化ArrayList对象
jobject arraylist_obj = env->NewObject(list_clazz, arraylist_init);
for (const auto &item: objects) {
float array[6];
array[0] = item.rect.x;
array[1] = item.rect.y;
array[2] = item.rect.width;
array[3] = item.rect.height;
array[4] = (float) item.label;
array[5] = item.prob;
jfloatArray result_array = env->NewFloatArray(6);
env->SetFloatArrayRegion(result_array, 0, 6, array);
env->CallBooleanMethod(arraylist_obj, arraylist_add, result_array);
}
//回调
env->CallVoidMethod(j_callback, j_method_id, arraylist_obj);
OK!!!