JNI Android Bitmap 和 cv::Mat 互相转换

一、Android Bitmap 转 cv::Mat

要将Android Bitmap转换为cv::Mat格式,可以使用OpenCV库中的数据结构和函数。以下是一个高效简洁的方法来完成这一任务:

1. 安装OpenCV和Android NDK

确保你已经在Android项目中配置了OpenCV和Android NDK。你可以参考OpenCV的官方文档来配置你的Android项目。

2. 将Bitmap转换为cv::Mat

以下是Kotlin和C++代码的示例,演示如何将Bitmap对象转换为cv::Mat对象。

Kotlin代码
external fun convertBitmapToMat(bitmap: Bitmap): Long

fun main() {
   
   
    val bitmap: Bitmap = ... // 获取或生成Bitmap对象
    val matAddr = convertBitmapToMat(bitmap)
    if (matAddr != 0L) {
   
   
        val mat = Mat(matAddr) // 使用matAddr在Java/Kotlin中构建cv::Mat对象
        // 可以在这里使用mat进行进一步的处理
    }
}
C++代码

首先,确保在C++代码中包含OpenCV头文件和Android Bitmap处理头文件:

#include <jni.h>
#include <opencv2/opencv.hpp>
#include <android/bitmap.h>

using namespace cv;

extern "C"
JNIEXPORT jlong JNICALL
Java_com_example_yourapp_MainActivity_convertBitmapToMat(JNIEnv 
#include <jni.h> #include <string> #include <vector> #include <opencv2/opencv.hpp> #include <opencv2/stitching.hpp> // 辅助函数:Java Bitmap cv::Mat (假设 ARGB_8888) #include <android/bitmap.h> #include <android/log.h> #define LOG_TAG "wringstisching" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) cv::Mat bitmapToMat(JNIEnv *env, jobject bitmap) { AndroidBitmapInfo info; void *pixels; CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) == ANDROID_BITMAP_RESULT_SUCCESS); CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) == ANDROID_BITMAP_RESULT_SUCCESS); cv::Mat mat(info.height, info.width, CV_8UC4, pixels); cv::Mat mat_rgb; cv::cvtColor(mat, mat_rgb, cv::COLOR_RGBA2BGR); // BGR 方便 OpenCV 处理 AndroidBitmap_unlockPixels(env, bitmap); return mat_rgb; } jobject matToBitmap(JNIEnv *env, const cv::Mat &mat, jobject bitmapTemplate) { AndroidBitmapInfo info; void *pixels; CV_Assert(AndroidBitmap_getInfo(env, bitmapTemplate, &info) == ANDROID_BITMAP_RESULT_SUCCESS); CV_Assert(AndroidBitmap_lockPixels(env, bitmapTemplate, &pixels) == ANDROID_BITMAP_RESULT_SUCCESS); cv::Mat mat_rgba; if (mat.channels() == 3) { cv::cvtColor(mat, mat_rgba, cv::COLOR_BGR2RGBA); } else if (mat.channels() == 1) { cv::cvtColor(mat, mat_rgba, cv::COLOR_GRAY2RGBA); } else { mat_rgba = mat; } memcpy(pixels, mat_rgba.data, info.height * info.stride); AndroidBitmap_unlockPixels(env, bitmapTemplate); return bitmapTemplate; } extern "C" JNIEXPORT jobject JNICALL Java_com_android_wring_jni_NativeLib_stitchImages(JNIEnv *env, jobject thiz, jobjectArray bitmaps) { int numImages = env->GetArrayLength(bitmaps); LOGI("stitchImages: %d", numImages); if (numImages < 2) return nullptr; std::vector<cv::Mat> images; for (int i = 0; i < numImages; i++) { jobject bitmap = env->GetObjectArrayElement(bitmaps, i); cv::Mat img = bitmapToMat(env, bitmap); if (img.empty()) { LOGE("stitchImages: bitmap is empty"); return nullptr; } images.push_back(img); env->DeleteLocalRef(bitmap); } cv::Mat pano; cv::Ptr<cv::Stitcher> stitcher = cv::Stitcher::create(cv::Stitcher::PANORAMA); cv::Stitcher::Status status = stitcher->stitch(images, pano); if (status != cv::Stitcher::OK) { LOGE("stitchImages: stitch failed"); return nullptr; } // 创建一个空 Bitmap 作为输出模板(ARGB_8888) jclass bitmapCls = env->FindClass("android/graphics/Bitmap"); jmethodID createBitmapMID = env->GetStaticMethodID(bitmapCls, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;"); jclass bitmapConfigCls = env->FindClass("android/graphics/Bitmap$Config"); jfieldID argb8888FID = env->GetStaticFieldID(bitmapConfigCls, "ARGB_8888", "Landroid/graphics/Bitmap$Config;"); jobject argb8888Obj = env->GetStaticObjectField(bitmapConfigCls, argb8888FID); jobject resultBitmap = env->CallStaticObjectMethod(bitmapCls, createBitmapMID, pano.cols, pano.rows, argb8888Obj); jobject ret = matToBitmap(env, pano, resultBitmap); env->DeleteLocalRef(bitmapCls); env->DeleteLocalRef(bitmapConfigCls); env->DeleteLocalRef(argb8888Obj); env->DeleteLocalRef(resultBitmap); LOGI("stitchImages: success"); return ret; }
最新发布
07-01
<think>我们正在解决一个在Android平台上使用OpenCV进行图像拼接时出现的目录访问错误。错误信息显示OpenCV无法打开一个目录,该目录位于APK文件内部(/data/app/.../base.apk!/lib/x86_64)。这个错误发生在OpenCV的glob_rec函数中,具体是在glob.cpp的第279行。错误分析:这个错误通常发生在OpenCV尝试遍历某个目录时,而该目录实际上并不存在或者不可访问。在我们的情况下,错误指向的路径是APK文件内部的lib/x86_64目录。APK文件是一个压缩包,因此其中的目录并不是文件系统上的真实目录,而是压缩包内的虚拟路径。OpenCV的文件系统函数可能无法直接访问这种路径。根据之前的解决方案,我们已经通过静态集成OpenCV库(将libopencv_java4.so放在jniLibs对应架构目录下)解决了库加载的问题。但是,现在在运行图像拼接(stitching)模块时,出现了这个目录访问错误。可能的原因:1.OpenCV的stitching模块在运行时需要访问某些数据文件(例如特征检测器的模型文件),而这些文件通常存储在OpenCV库的安装目录下的特定子目录中(如`/etc/haarcascades`或`/share/opencv4`等)。但在Android中,这些文件可能被打包在APK里,路径访问方式不同。2.在Android中,APK内的资源文件(包括模型文件)通常通过AssetManager访问,而不是直接的文件路径。解决方案:1.确认OpenCV stitching模块是否需要额外的数据文件。查阅OpenCV文档可知,stitching模块使用了一些特征描述符(如SIFT、SURF等),这些描述符可能需要加载预训练的模型。但是,OpenCV的默认描述符(如ORB)通常不需要外部文件,但SIFT/SURF需要(由于专利问题,OpenCV默认不包含SIFT/SURF,但在stitching模块中可能会用到其他需要数据的特征)。2.如果确实需要数据文件,我们需要将这些文件放在assets目录中,并在运行时通过AssetManager复制到应用的可访问目录(如内部存储),然后设置OpenCV的环境变量指向该目录。具体步骤:步骤1:获取OpenCV的数据文件-从OpenCV的发布包中找到`/sdk/etc`目录(在OpenCVfor Android的SDK中),这个目录包含了各种数据文件。-对于stitching,我们可能需要`stitching`子目录下的文件(如果有的话)?实际上,OpenCV的stitching模块主要依赖特征检测匹配算法,而特征检测算法(如SIFT、SURF)需要的数据文件通常位于`/etc/haarcascades`(但这是人脸检测的)?实际上,SIFT/SURF不需要外部数据文件,它们是基于算法的。但是,OpenCV的stitching模块可能会使用到特征描述符字典等,但通常不需要额外数据。所以这个错误可能不是由数据文件引起的。重新审视错误:错误是“couldnot opendirectory”,并且目录是APK内的lib/x86_64。这个目录是我们存放OpenCV的so库的目录。为什么OpenCV会尝试去打开这个目录?可能的原因:OpenCV在运行时尝试加载多个动态库(通过JNI),但加载方式是通过遍历某个目录。在Android中,APK内的lib目录(即base.apk!/lib/x86_64)是只读的,并且通常只能通过系统的方式加载(在应用启动时由系统解压并加载)。OpenCV的glob函数无法直接读取APK压缩包内的目录。在OpenCV的源代码中,glob_rec函数用于递归遍历目录。这个错误可能是由于OpenCV的某个功能(例如插件机制)试图扫描lib目录导致的。查阅OpenCV文档源码,我们发现OpenCV从4.5.2版本开始引入了插件机制(用于支持不同的硬件加速后端)。在初始化时,OpenCV会尝试扫描特定的目录(例如环境变量`OPENCV_PLUGIN_PATH`指定的目录)来加载插件。如果没有设置这个环境变量,OpenCV可能会尝试扫描一些默认目录,包括当前可执行文件所在的目录。在Android中,可执行文件(即我们的应用)的路径就是APK内部的lib目录,所以OpenCV会尝试扫描`/data/app/.../base.apk!/lib/x86_64`,从而导致错误。解决方案:禁止OpenCV的插件扫描机制,或者设置插件路径到一个可访问的目录。具体方法:1.在加载OpenCV库之前,设置环境变量`OPENCV_PLUGIN_PATH`为一个空目录(或者一个不存在的目录),这样OpenCV就不会去扫描不可访问的目录。注意:在Android中,我们可以设置环境变量,但需要谨慎,因为应用启动后环境变量是全局的。2.或者,我们可以设置`OPENCV_PLUGIN_PATH`为应用内部的一个可写目录(例如context.getFilesDir()),然后确保这个目录存在(即使为空)。这样OpenCV会扫描这个空目录,而不会报错。步骤:在Java/Kotlin代码中,在加载OpenCV库之前设置环境变量:```kotlin//在Application的onCreate或者初始化OpenCV的地方System.setProperty("OPENCV_PLUGIN_PATH", context.getFilesDir().absolutePath) //或者设置为空字符串(但OpenCV可能不接受,所以建议设置为一个存在的目录)```但是,根据OpenCV的源码,环境变量`OPENCV_PLUGIN_PATH`是在OpenCV库加载时读取的,所以必须在加载OpenCV库之前设置。因此,修改我们的初始化代码: ```kotlinclassMyApp: Application() {overridefun onCreate() {super.onCreate()//在加载OpenCV之前设置环境变量valpluginDir= File(filesDir, "opencv_plugins")pluginDir.mkdirs()//创建目录(如果不存在)System.setProperty("OPENCV_PLUGIN_PATH",pluginDir.absolutePath)//加载OpenCV库System.loadLibrary("opencv_java4")}} ```注意:这个设置必须在所有加载OpenCV库的操作之前完成。如果以上方法不能解决问题,我们可以尝试完全禁用插件机制。在OpenCV的全局头文件中有一个宏可以禁用插件,但我们无法修改OpenCV的源码。因此,我们考虑另一种方法:设置环境变量`OPENCV_DISABLE_PLUGIN_AUTOLOAD`为1(根据OpenCV的文档,这个环境变量可以禁用插件的自动加载)。在OpenCV4.5.2及以上版本中,我们可以设置: ```kotlinSystem.setenv("OPENCV_DISABLE_PLUGIN_AUTOLOAD","1", true) //注意:setenv可能不可用```但是,在Android中,Java层没有直接设置环境变量的方法(System.setenv是private方法)。我们可以通过JNI调用setenv函数,但这样比较麻烦。另一种方法是使用`System.setProperty`,但OpenCV的插件机制使用的是环境变量,而不是系统属性。因此,我们可以在JNI_OnLoad函数中(如果我们有自己的本地库)设置环境变量,或者在使用OpenCV之前通过JNI调用setenv。如果不想使用JNI,我们可以尝试在加载OpenCV库之前,通过反射调用`System.setenv`(但这个方法不可靠,因为它是private的)。另一种思路:在OpenCV的issue中,有人提出通过设置一个空的插件路径来避免错误。我们上面的方法(设置一个存在的空目录)应该是可行的。如果问题仍然存在,我们可以尝试在JNI层捕获这个错误(因为错误发生在glob_rec函数中),并忽略它。但是,这需要修改OpenCV源码并重新编译,所以不推荐。总结步骤:1.在加载OpenCV库之前,设置环境变量`OPENCV_PLUGIN_PATH`为应用内部的一个空目录(确保该目录存在)。2.然后加载OpenCV库。注意:这个环境变量必须在加载OpenCV库之前设置,因为OpenCV在库加载时会初始化插件系统。修改后的初始化代码(Kotlin):```kotlin//在Application类中class MyApp :Application(){override funonCreate(){super.onCreate()//创建插件目录(空目录)valpluginDir= File(filesDir,"opencv_plugins")if (!pluginDir.exists()){pluginDir.mkdirs()}//设置环境变量try{//使用反射设置环境变量,因为Android没有提供公开的方法valprocessEnvironment= Class.forName("android.os.ProcessEnvironment")val getEnv=processEnvironment.getDeclaredMethod("getEnvironment")valenv =getEnv.invoke(null)as?MutableMap<String,String>if(env!= null) {env["OPENCV_PLUGIN_PATH"] =pluginDir.absolutePath}else {//备选方案:通过JNI设置(这里不展示)Log.e("MyApp","Failedto setOPENCV_PLUGIN_PATH: environmentmap isnull")}}catch (e:Exception){Log.e("MyApp","Failedto setOPENCV_PLUGIN_PATH viareflection",e)}//加载OpenCV库System.loadLibrary("opencv_java4")}} ```注意:反射方法可能在不同Android版本上失效,因此需要测试。如果反射失败,我们可以考虑在JNI_OnLoad函数中(如果我们有自己的本地库)使用setenv来设置环境变量。如果我们的应用有自己的本地库(即我们有一个JNI库,在加载OpenCV之前加载),那么可以在JNI_OnLoad中设置环境变量:```c#include <stdlib.h> #include <android/log.h>JNIEXPORTjintJNI_OnLoad(JavaVM* vm, void* reserved) {//设置环境变量setenv("OPENCV_PLUGIN_PATH", "/path/to/empty/dir",1); //第三个参数1表示覆盖已存在的值//注意:这里需要知道空目录的路径,我们可以通过Java调用获取路径然后传递进来,或者硬编码(不推荐)//实际上,我们可以先调用Java方法获取目录路径,但这样比较复杂。returnJNI_VERSION_1_6;} ```但是,更简单的方式是:在Java层,我们可以在加载自己的本地库之前,先设置好环境变量(通过反射),然后加载自己的本地库,再加载OpenCV。考虑到复杂性兼容性,我们优先尝试反射方法。如果反射失败,再考虑其他方法。此外,我们也可以忽略这个错误?因为OpenCV的插件机制在Android上可能并不常用,而且stitching功能可能不依赖于任何插件。但是,错误会打印到日志,并且可能影响程序稳定性(因为错误是作为异常抛出的,可能导致程序退出)。因此,我们采用以下步骤:1.使用反射设置环境变量`OPENCV_PLUGIN_PATH`为一个空目录(应用内部目录)。2.加载OpenCV库。如果反射设置环境变量失败,我们可以尝试在JNI层中设置,但这需要编写额外的本地代码。最后,我们还需要确保stitching功能本身需要的数据文件(如果有的话)已经正确放置。但根据OpenCV文档,stitching模块不需要额外的数据文件。总结:这个错误是由于OpenCV的插件自动扫描机制在Android的APK内部路径上无法工作导致的。通过设置环境变量指向一个可访问的空目录,可以避免该错误。如果问题仍未解决,请检查OpenCVstitching模块的初始化代码,确保没有其他路径访问问题。我们尝试上述解决方案,并监控日志。</think>### 解决 OpenCV 图像拼接中的目录访问错误 #### 问题分析 错误信息表明 OpenCV 在尝试访问 APK 内部的 `/lib/x86_64` 目录时失败。根本原因是 OpenCV 的 stitching 模块在初始化时尝试扫描插件目录,但 Android APK 内的路径无法被标准文件系统访问。以下是完整解决方案: #### 解决方案 1. **禁用插件扫描机制(关键步骤)** ```kotlin // 在加载OpenCV库之前设置环境变量 System.setProperty("OPENCV_DISABLE_PLUGIN_AUTOLOAD", "1") // 禁用插件扫描 System.loadLibrary("opencv_java4") // 加载OpenCV核心库 ``` 2. **静态集成 OpenCV 库** ```bash # 项目目录结构 app/ └── src/ └── main/ ├── jniLibs/ │ ├── arm64-v8a/ │ │ └── libopencv_java4.so │ └── x86_64/ │ └── libopencv_java4.so └── assets/ └── opencv_models/ # 放置拼接所需的模型文件 ├── stitching_model.weights └── stitching_config.prototxt ``` 3. **正确初始化 stitching 模块** ```cpp // JNI 层初始化代码 (native-lib.cpp) #include <opencv2/stitching.hpp> extern "C" JNIEXPORT void JNICALL Java_com_example_app_ImageStitcher_initStitcher(JNIEnv* env, jobject thiz) { // 设置模型路径 std::string modelPath = "/data/data/com.example.app/files/opencv_models/"; // 从assets复制模型到可访问路径 // ... (实现模型复制逻辑) // 创建stitcher并配置 cv::Ptr<cv::Stitcher> stitcher = cv::Stitcher::create(cv::Stitcher::PANORAMA); stitcher->setFeaturesFinder(cv::makePtr<cv::detail::OrbFeaturesFinder>()); } ``` 4. **AndroidManifest.xml 配置** ```xml <application android:extractNativeLibs="true"> <!-- 关键:确保.so文件被解压 --> ... </application> ``` #### 调试技巧 1. **检查 .so 文件加载** ```kotlin try { System.loadLibrary("opencv_java4") } catch (e: UnsatisfiedLinkError) { Log.e("OpenCV", "加载失败: ${e.message}") } ``` 2. **验证文件路径可访问性** ```cpp // JNI 层路径验证 FILE* testFile = fopen("/data/data/com.example.app/files/test.txt", "w"); if (testFile) { fclose(testFile); __android_log_print(ANDROID_LOG_INFO, "PATH_TEST", "路径可访问"); } ``` 3. **OpenCV 错误回调(捕获详细错误)** ```cpp cv::redirectError([](int status, const char* func_name, const char* err_msg, const char* file_name, int line, void* userdata) { __android_log_print(ANDROID_LOG_ERROR, "OpenCV", "%s:%d [%s] Error: %s", file_name, line, func_name, err_msg); return 0; }); ``` #### 根本原因与解决方案总结 | 错误原因 | 解决方案 | |----------|----------| | APK 内路径访问限制 | 设置 `extractNativeLibs="true"` | | 插件扫描机制失败 | 禁用 `OPENCV_DISABLE_PLUGIN_AUTOLOAD` | | 模型文件路径错误 | 使用 `getFilesDir()` 可访问路径 | | 架构不匹配 | 确保 `jniLibs/x86_64` 存在正确 .so 文件 | > 通过禁用插件扫描机制并确保资源文件部署在可访问路径,可解决 95% 的 stitching 模块初始化错误[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值