资讯动态

SuperResolution图像前后处理逻辑从C++迁移到Java的开发文档

发布时间:2026/10/6 17:40:26 来源:尧图企业网站定制
本项目基于ai-engine-direct-helper (QAI_AppBuilder)相关 GitHub 链接https://github.com/quic/ai-engine-direct-helper.git本文档旨在阐述在 SuperResolution Android 示例项目中将图像的预处理Pre-processing和后处理Post-processing逻辑从 C Native 层迁移到 Java 应用层的原因、实现方式以及相关的配置变更。1.1迁移原因分析将图像处理逻辑从 C 层迁移至 Java 层主要基于以下几点考虑旨在优化项目的开发效率、可维护性及整体架构的清晰度。1.1.1简化Native层职责让 C Native 层JNI的职责更单一、更纯粹。迁移后C 代码只负责核心的、计算密集型的推理任务buffer-to-buffer不再关心图像文件的读取、格式转换等 I/O 和图像处理操作。这使得 Native 层的代码更简洁易于维护和复用。1.1.2提升开发和调试效率在 Android Studio 中使用 Java/Kotlin 调试图像处理流程远比调试 C 代码方便。开发者可以轻松地设置断点、查看 Bitmap 对象、检查中间处理结果例如查看 Mat 对象的像素值从而快速定位问题。1.1.3更好地与Android UI框架集成图像数据源于 Android UI如图库选择最终也要显示在 UI 上如 ImageView。将处理逻辑放在 Java 层可以直接操作 Android 的 Bitmap 对象避免了在 C 和 Java 之间频繁传递文件路径或进行复杂的对象转换代码逻辑更顺畅。1.1.4减少Native编译依赖将 OpenCV 的使用完全移至 Java 层后C 的构建系统CMake不再需要链接庞大的 OpenCV 库。这简化了 CMakeLists.txt 的配置减少了 Native 库的体积和编译复杂度。1.2实现方法实现这一重构的核心思想是改变 JNI 接口的数据传递方式从传递文件路径String改为传递直接内存缓冲区java.nio.ByteBuffer。1.2.1修改C JNI接口我们将原有的 JNI 函数签名从接收文件路径改为接收 ByteBuffer。1.2.1.1修改前(Before)cpp Java_com_example_superresolution_MainActivity_SuperResolution(JNIEnv* env,jobject,jstring j_libsDir,jstring j_model_path,jstring j_input_img,jstring j_output_img)1.2.1.2修改后(After)cpp Java_com_example_superresolution_MainActivity_SuperResolution(JNIEnv* env,jobject,jstring j_libsDir,jstring j_model_path,jobject j_inputBuffer,jobject j_outputBuffer)在 C 实现中我们使用 env-GetDirectBufferAddress() 来获取 ByteBuffer 的内存地址从而直接读写由 Java 分配的内存。所有与 OpenCV 相关的代码如 cv::imread, cv::resize, cv::imwrite 等都已从 native-lib.cpp 中移除。1.2.2在Java中实现前后处理我们在 MainActivity.java 中增加了两个核心方法preprocess 和 postprocess。1.2.2.1预处理(preprocess)此方法负责将用户选择的 Bitmap 图像转换为模型推理所需的 ByteBuffer。private ByteBuffer preprocess(Bitmap bitmap) { // 1. 将 Bitmap 转换为 OpenCV Mat Mat mat new Mat(); Utils.bitmapToMat(bitmap, mat); // 2. 颜色空间转换 (Android Bitmap 是 RGBA, 模型可能需要 RGB) Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGBA2RGB); // 3. 归一化 (将像素值从 0-255 映射到 0.0-1.0) 并转为 32 位浮点型 mat.convertTo(mat, CvType.CV_32F, 1.0 / 255.0); // 4. 分配直接 ByteBuffer int bufferSize IMAGE_WIDTH * IMAGE_HEIGHT * 3 * 4; // 3通道, float32 ByteBuffer byteBuffer ByteBuffer.allocateDirect(bufferSize); byteBuffer.order(ByteOrder.nativeOrder()); // 5. 将 Mat 数据写入 ByteBuffer float[] data new float[IMAGE_WIDTH * IMAGE_HEIGHT * 3]; mat.get(0, 0, data); byteBuffer.asFloatBuffer().put(data); return byteBuffer; }1.2.2.2后处理(postprocess)此方法负责将 Native 层返回的 ByteBuffer 结果转换回可供显示的 Bitmap 图像。private Bitmap postprocess(ByteBuffer buffer, int width, int height) { // 1. 重置 buffer 指针并读取 float 数据 buffer.rewind(); FloatBuffer floatBuffer buffer.asFloatBuffer(); float[] data new float[width * height * 3]; floatBuffer.get(data); // 2. 反归一化 (将 0.0-1.0 的值映射回 0-255) 并进行 clip byte[] byteData new byte[data.length]; for (int i 0; i data.length; i) { byteData[i] (byte) Math.max(0, Math.min(255, data[i] * 255.0f)); } // 3. 将数据写入 Mat Mat mat new Mat(height, width, CvType.CV_8UC3); mat.put(0, 0, byteData); // 4. 颜色空间转换 (从 RGB 转回 RGBA 以正确生成 Bitmap) Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGB2RGBA); // 5. 将 Mat 转换回 Bitmap Bitmap bitmap Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Utils.matToBitmap(mat, bitmap); return bitmap; }1.2.3更新Java层的调用逻辑在 covertImageButton 的点击事件中我们更新了调用流程串联起预处理、Native 推理和后处理。// ... 在 covertImageButton.setOnClickListener 中 ... // 1. 加载和缩放原始图片 Bitmap bitmap BitmapFactory.decodeFile(input_img); Bitmap resizedBitmap Bitmap.createScaledBitmap(bitmap, IMAGE_WIDTH, IMAGE_HEIGHT, true); // 2. 预处理 ByteBuffer inputBuffer preprocess(resizedBitmap); // 3. 准备输出 Buffer int srWidth SCALE * IMAGE_WIDTH; int srHeight SCALE * IMAGE_HEIGHT; int outputBufferSize srWidth * srHeight * 3 * 4; // float32 ByteBuffer outputBuffer ByteBuffer.allocateDirect(outputBufferSize); outputBuffer.order(ByteOrder.nativeOrder()); // 4. 调用 Native 方法进行推理 SuperResolution(nativeLibPath, model_name, inputBuffer, outputBuffer); // 5. 后处理并显示结果 Bitmap outputBitmap postprocess(outputBuffer, srWidth, srHeight); outputImagePreview.setImageBitmap(outputBitmap);1.3依赖与配置变更为了支持 Java 层的 OpenCV 功能需要进行以下配置变更。1.3.1 app/build.gradle.kts在 dependencies 代码块中添加 OpenCV 的官方 Android 依赖。dependencies { // ... 其他依赖 implementation(org.opencv:opencv:4.9.0) }1.3.2 MainActivity.java为了让 App 在运行时能找到并加载 OpenCV 的 Native 库 (.so 文件)必须在调用任何 OpenCV API 之前对其进行初始化。这通常在 Activity 的 onCreate 方法的开头完成。Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 初始化 OpenCV if (!OpenCVLoader.initDebug()) { Log.e(TAG, OpenCV initialization failed!); } else { Log.d(TAG, OpenCV initialization successful!); } // ... 后续代码 }1.3.3 app/src/main/cpp/CMakeLists.txt由于 C 代码不再使用 OpenCV我们移除了相关的头文件包含路径和库链接配置以简化 Native 构建。移除 include_directories 中的 OpenCV 路径。移除 add_library(opencv ...) 的定义。移除 target_link_libraries 中的 opencv 链接。通过以上步骤我们成功地将前后处理逻辑迁移到了 Java 层实现了更清晰的架构和更高效的开发流程。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑