# yolo安卓部署 **Repository Path**: htwin/ncnn-yolo-android ## Basic Information - **Project Name**: yolo安卓部署 - **Description**: yolo安卓部署 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-07-01 - **Last Updated**: 2026-07-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ncnn_yolov8(原生 C++ 相机 + YOLOv8 检测) 本文档面向开发者,完整说明本工程如何使用 Android NDK 的原生相机(Camera2 + AImageReader)在 C++ 中抓取图像、加载 ncnn YOLOv8 模型并进行实时检测与渲染。按照本文档,你可以从零搭建并运行项目。注意:本说明仅覆盖 C++ 相机管线,不涉及 CameraX。 ## 工程结构与文件作用 ``` app/ ├─ src/main/ │ ├─ cpp/ # 原生 C++ 源码 │ │ ├─ CMakeLists.txt # 原生库构建、链接 OpenCV 与 ncnn │ │ ├─ native-lib.cpp # JNI 桥;模型加载、相机打开/关闭、Surface 渲染入口 │ │ ├─ ndkcamera.h/.cpp # 原生相机封装:Camera2 + AImageReader,NV21→RGB、旋转、缩放、渲染 │ │ ├─ yolo.h/.cpp # YOLOv8 推理与后处理,detect/draw │ ├─ java/com/jianle/ncnn_yolov8/ │ │ ├─ Yolov8Ncnn.java # 调用 JNI:loadModel/openCamera/setOutputWindow/closeCamera │ │ └─ MainActivity.java # UI 入口;创建 SurfaceView、驱动 JNI 管线 │ ├─ res/layout/activity_main.xml # 包含 SurfaceView 的布局,用于原生渲染 │ └─ assets/ # 模型与标签资源 │ ├─ yolov8n.param/.bin # YOLOv8-nano 模型(ncnn 格式) │ ├─ yolov8s.param/.bin # YOLOv8-small 模型(ncnn 格式) ├─ build.gradle # App 构建配置(NDK、externalNativeBuild、ABI) └─ src/main/AndroidManifest.xml # 权限与 Activity 声明 ``` - `ndkcamera.*`:使用 `ACameraManager/Device` + `AImageReader` 接收 YUV_420_888 帧;拼装 NV21,做裁剪、旋转(结合传感器与相机朝向),再转 RGB 与渲染到 `ANativeWindow`。 - `yolo.*`:ncnn 模型加载、预处理(resize + pad + normalize)、推理、NMS、结果绘制。 - `native-lib.cpp`:JNI 入口,模型加载与相机驱动,并在渲染回调中调用 YOLO 检测。 - `Yolov8Ncnn.java` 与 `MainActivity.java`:Java 端持有 `SurfaceView` 并调用 JNI;Activity 生命周期内打开/关闭相机。 ## 环境准备 - Android Studio(含 NDK),Android API ≥ 24。 - CMake ≥ 3.22.1(Gradle 已配置)。 - 将 OpenCV Mobile 与 ncnn 的 Android 预编译包放入项目并设置路径: - OpenCV:`app/src/main/cpp/opencv-mobile-2.4.13.7-android/sdk/native/jni` - ncnn:`app/src/main/cpp/ncnn-20230223-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn` - 设备权限:Manifest 已声明 `android.permission.CAMERA`。 ## 构建配置(关键片段) `app/src/main/cpp/CMakeLists.txt` 部分: ```cmake find_package(OpenCV REQUIRED core imgproc) set(ncnn_DIR ${CMAKE_SOURCE_DIR}/ncnn-20230223-android-vulkan/${ANDROID_ABI}/lib/cmake/ncnn) find_package(ncnn REQUIRED) add_library(${CMAKE_PROJECT_NAME} SHARED native-lib.cpp yolo.cpp ndkcamera.cpp camerax_jni.cpp) target_link_libraries(${CMAKE_PROJECT_NAME} ncnn ${OpenCV_LIBS} camera2ndk mediandk) ``` `app/build.gradle`(NDK 与 CMake): ```gradle android { defaultConfig { ndk { abiFilters 'arm64-v8a', 'x86_64' } } externalNativeBuild { cmake { path file('src/main/cpp/CMakeLists.txt') version '3.22.1' } } } ``` 确保你放置的 OpenCV/ncnn 预编译包同时覆盖所选 ABI(例如 `arm64-v8a` 与 `x86_64`)。 ## 原生相机采集(核心流程) - 图像获取与 NV21 拼装(摘自 `ndkcamera.cpp`): ```cpp static void onImageAvailable(void* context, AImageReader* reader) { AImage* image = 0; if (AImageReader_acquireLatestImage(reader, &image) != AMEDIA_OK) return; // 读取 Y/U/V 分量与步距 // 若平面布局等于 NV21,直接传入;否则构造 NV21 缓冲区 if (u_data == v_data + 1 && /* 省略若干条件 */) { ((NdkCamera*)context)->on_image((unsigned char*)y_data, width, height); } else { unsigned char* nv21 = new unsigned char[width*height + width*height/2]; // 拷贝 Y 平面 + 交错 UV(V,U)到 nv21 ((NdkCamera*)context)->on_image(nv21, width, height); delete[] nv21; } AImage_delete(image); } ``` - NV21 旋转 + 转 RGB(`NdkCamera::on_image`): ```cpp cv::Mat nv21_rotated(h + h/2, w, CV_8UC1); ncnn::kanna_rotate_yuv420sp(nv21, nv21_width, nv21_height, nv21_rotated.data, w, h, rotate_type); cv::Mat rgb(h, w, CV_8UC3); ncnn::yuv420sp2rgb(nv21_rotated.data, w, h, rgb.data); on_image(rgb); // 回调到渲染管线 ``` - 渲染到 `ANativeWindow`(`NdkCameraWindow::on_image` 内部完成裁剪/旋转并写入窗口像素)。 ## YOLO 模型加载与推理(关键片段) - JNI 加载模型(`native-lib.cpp`): ```cpp extern "C" JNIEXPORT jboolean JNICALL Java_com_jianle_ncnn_1yolov8_Yolov8Ncnn_loadModel(JNIEnv* env, jobject thiz, jobject assetManager, jint modelid, jint cpugpu) { AAssetManager* mgr = AAssetManager_fromJava(env, assetManager); const char* modeltypes[] = {"n", "s"}; const int target_sizes[] = {320, 320}; const float mean_vals[][3] = {{103.53f,116.28f,123.675f},{103.53f,116.28f,123.675f}}; const float norm_vals[][3] = {{1/255.f,1/255.f,1/255.f},{1/255.f,1/255.f,1/255.f}}; const char* modeltype = modeltypes[modelid]; int target_size = target_sizes[modelid]; bool use_gpu = (cpugpu == 1); ncnn::MutexLockGuard g(lock); if (use_gpu && ncnn::get_gpu_count() == 0) { delete g_yolo; g_yolo = 0; } else { if (!g_yolo) g_yolo = new Yolo(); g_yolo->load(mgr, modeltype, target_size, mean_vals[modelid], norm_vals[modelid], use_gpu); } return JNI_TRUE; } ``` - 推理与绘制(`MyNdkCamera::on_image_render`): ```cpp void MyNdkCamera::on_image_render(cv::Mat& rgb) const { ncnn::MutexLockGuard g(lock); if (g_yolo) { std::vector objects; g_yolo->detect(rgb, objects); g_yolo->draw(rgb, objects); } else { // 在画面上提示未加载 } } ``` - 模型文件名(`yolo.cpp`): ```cpp sprintf(parampath, "yolov8%s.param", modeltype); sprintf(modelpath, "yolov8%s.bin", modeltype); yolo.load_param(mgr, parampath); yolo.load_model(mgr, modelpath); ``` 将匹配的 `.param/.bin` 放在 `app/src/main/assets/`。 ## Java 入口与 Surface 渲染 - `activity_main.xml`: ```xml ``` - `MainActivity.java`(核心调用): ```java public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback { private Yolov8Ncnn yolov8ncnn = new Yolov8Ncnn(); protected void onCreate(Bundle saved) { // 省略绑定 viewBinding... SurfaceHolder holder = binding.cameraview.getHolder(); holder.addCallback(this); yolov8ncnn.loadModel(getAssets(), /*modelid*/0, /*cpugpu*/0); // 建议先走CPU } public void surfaceChanged(SurfaceHolder h, int f, int w, int hgt) { yolov8ncnn.setOutputWindow(h.getSurface()); } protected void onResume() { super.onResume(); // 申请权限后: yolov8ncnn.openCamera(1); // 1=后摄,0=前摄 } protected void onPause() { super.onPause(); yolov8ncnn.closeCamera(); } } ``` - `Yolov8Ncnn.java`: ```java 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); static { System.loadLibrary("ncnn_yolov8"); } } ``` ## 从零运行步骤 1. 将 OpenCV 与 ncnn 预编译包按“环境准备”放置,确认 `CMakeLists.txt` 路径正确。 2. 确保 `assets/` 下有匹配的 `yolov8n.param/.bin` 或 `yolov8s.param/.bin`(与 `modelid` 一致)。 3. 打开 Android Studio,选择 `app` 模块,执行构建。 4. 连接设备/启动模拟器,安装并运行应用。通过 `MainActivity`(SurfaceView 原生渲染)。 5. 首次运行授予相机权限,即可看到检测框与类别。 - 切换 CPU/GPU:`loadModel(getAssets(), modelid, /*0=CPU,1=GPU*/)`;若设备 Vulkan 不可用,请传 `0`。 - 切换前后摄:`openCamera(0)` 前摄,`openCamera(1)` 后摄。