美文网首页
Android opencv 全景图片拼接笔记

Android opencv 全景图片拼接笔记

作者: silencefun | 来源:发表于2019-05-14 18:31 被阅读0次

    帮别人解决了一点opencv 实现图片拼接的小问题,采用的OpenCV 的Stitcher工具,感觉有必要整理一下。

    1.opencv 是什么

    OpenCV是一个基于BSD许可(开源)发行的跨平台计算机视觉库,主要是C/C++写的处理实现。简单的说,执行效率更高,内存开销更低。
    官网https://opencv.org
    github:https://github.com/opencv/opencv

    2.下载

    在页面https://opencv.org/releases/
    下载android 的sdk 目前(2019.5.14)最新是2019.4.8 release的OpenCV – 4.1.0
    或者在githu 下载:https://github.com/opencv/opencv/releases
    已上传到百度云盘:链接: https://pan.baidu.com/s/1EyA3N2sP_U6VeESMkdbY3w 提取码: 9zcw
    解压后

    image.png

    显而易见,分别是简单的demo,sdk,许可和说明。打开readme真是傲娇,只有一句话

    image.png
    See http://opencv.org/platforms/android
    看说明很有比要,一般都是讲一下是什么怎么用。 image.png

    这一堆大致看了下就是说这里有两种不同是适用建议....还是直接看谷歌翻译吧


    image.png

    3.android项目 导入

    有两种方式1.直接使用,导入java sdk但是可能致使体积偏大。
    所以还是使用2.使用opencv sdk 提供的 C++ 头文件与 .so动态库 与 .a静态库,自己封装jni。

    image.png

    把sdk下的native文件夹复制到android项目,参考别人的做法有的和app一级,有的放倒了src/main/jni下面,这个分析了并无要求分别,在后续的配置中指明位置就好。


    image.png

    在src/main目录下创建jni文件夹(cpp或者都是可以的,只要在配置中写对就好了)

    image.png
    配置android.mk和application.mk

    android.mk是用来描述要编译某个具体的模块,所需要的一些资源,包括要编译的源码、要链接的库等等

    application.mk:描述你的程序所需要的模块,即静态库或者共享库
    android.mk 代码内容

     LOCAL_PATH := $(call my-dir)
    
    include $(CLEAR_VARS)
    
    
    OpenCV_INSTALL_MODULES := on
    OpenCV_CAMERA_MODULES := off
    
    #SHARED使用动态库 STATIC使用静态库
    OPENCV_LIB_TYPE :=STATIC
    
    ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
    include ../../../../native/jni/OpenCV.mk
    else
    include $(OPENCV_MK_PATH)
    endif
    
    #生成的动态库的名称
    LOCAL_MODULE := Stitcher
    
    #需要链接的cpp文件
    LOCAL_SRC_FILES := native-lib.cpp
    
    
    LOCAL_LDLIBS    += -lm -llog -landroid
    LOCAL_LDFLAGS += -ljnigraphics
    
    include $(BUILD_SHARED_LIBRARY)
    

    application.mk 代码内容

    APP_STL := gnustl_static
    APP_CPPFLAGS := -frtti -fexceptions
    #需要编译的平台  
    APP_ABI := arm64-v8a armeabi-v7a
    

    需要注意的是,创建文件后在最好AS中进行,否则可能出现乱码,gbk在utf-8下显示乱码。

    声明java层的调用方法
      package test.com.opencv.utils;
    
      import android.graphics.Bitmap;
      import android.support.annotation.NonNull;
    
      import java.io.File;
    
    
      public class ImagesStitchUtil {
       public final static int OK = 0;
       public final static int ERR_NEED_MORE_IMGS = 1;
       public final static int ERR_HOMOGRAPHY_EST_FAIL = 2;
       public final static int ERR_CAMERA_PARAMS_ADJUST_FAIL = 3;
    
       static {
           System.loadLibrary("Stitcher");//对应好定义的名字
       }
    
    
    public static void StitchImages(String paths[], @NonNull onStitchResultListener listener) {
        for (String path : paths) {
            if (!new File(path).exists()) {
                listener.onError("无法读取文件或文件不存在:" + path);
                return;
            }
        }
        int wh[] = stitchImages(paths);
        switch (wh[0]) {
            case OK: {
                Bitmap bitmap = Bitmap.createBitmap(wh[1], wh[2], Bitmap.Config.ARGB_8888);
                int result = getBitmap(bitmap);
                if (result == OK && bitmap != null) {
                    listener.onSuccess(bitmap);
                } else {
                    listener.onError("图片合成失败");
                }
            }
            break;
            case ERR_NEED_MORE_IMGS: {
                listener.onError("需要更多图片");
                return;
            }
            case ERR_HOMOGRAPHY_EST_FAIL: {
                listener.onError("图片对应不上");
                return;
            }
            case ERR_CAMERA_PARAMS_ADJUST_FAIL: {
                listener.onError("图片参数处理失败");
                return;
            }
        }
    }
    
    
    private native static int[] stitchImages(String path[]);
    
    private native static void getMat(long mat);
    
    private native static int getBitmap(Bitmap bitmap);
    
    
    public interface onStitchResultListener {
    
        void onSuccess(Bitmap bitmap);
    
        void onError(String errorMsg);
    }
    
    
    }
    
    编写cpp的,实现java声明的方法

    Android.mk中定义了需要链接的cpp文件 native-lib.cpp,在jni文件夹下创建native-lib.cpp,编写Cpp实现代码内容

    #include <jni.h>
    #include <opencv2/opencv.hpp>
    #include <opencv2/core/base.hpp>
    #import "opencv2/stitching.hpp"
    #import "opencv2/imgcodecs.hpp"
    
    #define BORDER_GRAY_LEVEL 0
    
    #include <android/log.h>
    #include <android/bitmap.h>
    
    #define LOG_TAG    "opencv_test"
    #define LOGI(...)  __android_log_print(ANDROID_LOG_INFO,LOG_TAG, __VA_ARGS__)
    #define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__)
    #define LOGW(...)  __android_log_print(ANDROID_LOG_WARN,LOG_TAG, __VA_ARGS__)
    #define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR,LOG_TAG, __VA_ARGS__)
    #define LOGF(...)  __android_log_print(ANDROID_LOG_FATAL,LOG_TAG, __VA_ARGS__)
    using namespace cv;
    using namespace std;
    char filepath1[100] = "/storage/emulated/0/1.jpg";
    
    
    cv::Mat finalMat;
    
    extern "C"
    JNIEXPORT jintArray JNICALL
    Java_test_com_opencv_utils_ImagesStitchUtil_stitchImages(JNIEnv *env, jclass type,
                                                        jobjectArray paths) {
    
    jstring jstr;
    jsize len = env->GetArrayLength(paths);
    std::vector<cv::Mat> mats;
    for (int i = 0; i < len; i++) {
        jstr = (jstring) env->GetObjectArrayElement(paths, i);
        const char *path = (char *) env->GetStringUTFChars(jstr, 0);
        LOGI("path %s", path);
        cv::Mat mat = cv::imread(path);
    //        cvtColor(mat, mat, CV_RGBA2RGB);
        mats.push_back(mat);
    }
    
    LOGI("开始拼接......");
    cv::Stitcher stitcher = cv::Stitcher::createDefault(false);
    
    //stitcher.setRegistrationResol(0.6);
       // stitcher.setWaveCorrection(false);
    /*=match_conf默认是0.65,我选0.8,选太大了就没特征点啦,0.8都失败了*/
    detail::BestOf2NearestMatcher *matcher = new detail::BestOf2NearestMatcher(false, 0.5f);
    stitcher.setFeaturesMatcher(matcher);
    stitcher.setBundleAdjuster(new detail::BundleAdjusterRay());
    stitcher.setSeamFinder(new detail::NoSeamFinder);
    stitcher.setExposureCompensator(new detail::NoExposureCompensator());//曝光补偿
    stitcher.setBlender(new detail::FeatherBlender());
    
    Stitcher::Status state = stitcher.stitch(mats, finalMat);
    
    //此时finalMat是bgr类型
    
    LOGI("拼接结果: %d", state);
    //        finalMat = clipping(finalMat);
    jintArray jint_arr = env->NewIntArray(3);
    jint *elems = env->GetIntArrayElements(jint_arr, NULL);
    elems[0] = state;//状态码
    elems[1] = finalMat.cols;//宽
    elems[2] = finalMat.rows;//高
    
    if (state == cv::Stitcher::OK){
        LOGI("拼接成功: OK");
    }else{
        LOGI("拼接失败:fail code %d",state);
    }
    //同步
    env->ReleaseIntArrayElements(jint_arr, elems, 0);
    //    bool isSave  = cv::imwrite(filepath1, finalMat);
    //    LOGI("是否存储成功:%d",isSave);
    return jint_arr;
    
    
    }extern "C"
    JNIEXPORT void JNICALL
    Java_test_com_opencv_utils_ImagesStitchUtil_getMat(JNIEnv *env, jclass type, jlong mat) {
    
    LOGI("开始获取mat...");
    Mat *res = (Mat *) mat;
    res->create(finalMat.rows, finalMat.cols, finalMat.type());
    memcpy(res->data, finalMat.data, finalMat.rows * finalMat.step);
    LOGI("获取成功");
    
    }
    
    //将mat转化成bitmap
    void MatToBitmap(JNIEnv *env, Mat &mat, jobject &bitmap, jboolean needPremultiplyAlpha) {
    AndroidBitmapInfo info;
    void *pixels = 0;
    Mat &src = mat;
    
    
    try {
    
        LOGD("nMatToBitmap");
        CV_Assert(AndroidBitmap_getInfo(env, bitmap, &info) >= 0);
        LOGD("nMatToBitmap1");
    
        CV_Assert(info.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ||
                  info.format == ANDROID_BITMAP_FORMAT_RGB_565);
        LOGD("nMatToBitmap2 :%d  : %d  :%d", src.dims, src.rows, src.cols);
    
        CV_Assert(src.dims == 2 && info.height == (uint32_t) src.rows &&
                  info.width == (uint32_t) src.cols);
        LOGD("nMatToBitmap3");
        CV_Assert(src.type() == CV_8UC1 || src.type() == CV_8UC3 || src.type() == CV_8UC4);
        LOGD("nMatToBitmap4");
        CV_Assert(AndroidBitmap_lockPixels(env, bitmap, &pixels) >= 0);
        LOGD("nMatToBitmap5");
        CV_Assert(pixels);
        LOGD("nMatToBitmap6");
    
    
        if (info.format == ANDROID_BITMAP_FORMAT_RGBA_8888) {
            Mat tmp(info.height, info.width, CV_8UC4, pixels);
    //            Mat tmp(info.height, info.width, CV_8UC3, pixels);
            if (src.type() == CV_8UC1) {
                LOGD("nMatToBitmap: CV_8UC1 -> RGBA_8888");
                cvtColor(src, tmp, COLOR_GRAY2RGBA);
            } else if (src.type() == CV_8UC3) {
                LOGD("nMatToBitmap: CV_8UC3 -> RGBA_8888");
    //                cvtColor(src, tmp, COLOR_RGB2RGBA);
    //                cvtColor(src, tmp, COLOR_RGB2RGBA);
                cvtColor(src, tmp, COLOR_BGR2RGBA);
    //                src.copyTo(tmp);
            } else if (src.type() == CV_8UC4) {
                LOGD("nMatToBitmap: CV_8UC4 -> RGBA_8888");
                if (needPremultiplyAlpha)
                    cvtColor(src, tmp, COLOR_RGBA2mRGBA);
                else
                    src.copyTo(tmp);
            }
        } else {
            // info.format == ANDROID_BITMAP_FORMAT_RGB_565
            Mat tmp(info.height, info.width, CV_8UC2, pixels);
            if (src.type() == CV_8UC1) {
                LOGD("nMatToBitmap: CV_8UC1 -> RGB_565");
                cvtColor(src, tmp, COLOR_GRAY2BGR565);
            } else if (src.type() == CV_8UC3) {
                LOGD("nMatToBitmap: CV_8UC3 -> RGB_565");
    //                src.copyTo(tmp);
                cvtColor(src, tmp, COLOR_RGB2BGR565);
            } else if (src.type() == CV_8UC4) {
                LOGD("nMatToBitmap: CV_8UC4 -> RGB_565");
                cvtColor(src, tmp, COLOR_RGBA2BGR565);
            }
        }
        AndroidBitmap_unlockPixels(env, bitmap);
        return;
    } catch (const cv::Exception &e) {
        AndroidBitmap_unlockPixels(env, bitmap);
        LOGE("nMatToBitmap catched cv::Exception: %s", e.what());
        jclass je = env->FindClass("org/opencv/core/CvException");
        if (!je) je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, e.what());
        return;
    } catch (...) {
        AndroidBitmap_unlockPixels(env, bitmap);
        LOGE("nMatToBitmap catched unknown exception (...)");
        jclass je = env->FindClass("java/lang/Exception");
        env->ThrowNew(je, "Unknown exception in JNI code {nMatToBitmap}");
        return;
    }
    }
    
    extern "C"
    JNIEXPORT jint JNICALL
    Java_test_com_opencv_utils_ImagesStitchUtil_getBitmap(JNIEnv *env, jclass type, jobject bitmap) {
    
    if (finalMat.dims != 2){
        return -1;
    }
    
    MatToBitmap(env,finalMat,bitmap,false);
    
    return 0;
    
    }
    

    注要包名下划线类名下划线方法名去对应java中声明的方法。

    配置app的build.gradle

    在Android 节点中国 添加

     sourceSets { main { jni.srcDirs = ['src/main/jni', 'src/main/jni/'] } }
    
    sourceSets.main.jni.srcDirs = []
    //禁止自带的ndk功能
    sourceSets.main.jniLibs.srcDirs = ['src/main/libs', 'src/main/jniLibs']
    //重定向so目录为src/main/libs和src/main/jniLibs,原来为src/main/jniLibs
    
    task ndkBuild(type: Exec, description: 'Compile JNI source with NDK') {
        Properties properties = new Properties()
        properties.load(project.rootProject.file('local.properties').newDataInputStream())
        def ndkDir = properties.getProperty('ndk.dir')
    
        if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) {
            commandLine "$ndkDir/ndk-build.cmd", '-C', file('src/main/jni').absolutePath
        } else {
            commandLine "$ndkDir/ndk-build", '-C', file('src/main/jni').absolutePath
        }
    }
    
    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn ndkBuild
    }
    
    task ndkClean(type: Exec, description: 'Clean NDK Binaries') {
        Properties properties = new Properties()
        properties.load(project.rootProject.file('local.properties').newDataInputStream())
        def ndkDir = properties.getProperty('ndk.dir')
    
        if (org.apache.tools.ant.taskdefs.condition.Os.isFamily(org.apache.tools.ant.taskdefs.condition.Os.FAMILY_WINDOWS)) {
            commandLine "$ndkDir/ndk-build.cmd", 'clean', '-C', file('src/main/jni').absolutePath
        } else {
            commandLine "$ndkDir/ndk-build", 'clean', '-C', file('src/main/jni').absolutePath
        }
    }
    
    clean.dependsOn 'ndkClean'
    

    }

    点击同步然后重现构建项目

    出现问题

     Process 'command 'E:\AndroidSDK\ndk-bundle/ndk-build.cmd'' finished with non-zero exit value 2
    

    解决办法:更新ndk 百度知道R16
    没有问题
    https://dl.google.com/android/repository/android-ndk-r16-windows-x86.zip

    https://dl.google.com/android/repository/android-ndk-r16-windows-x86_64.zip

    https://dl.google.com/android/repository/android-ndk-r16-darwin-x86_64.zip

    https://dl.google.com/android/repository/android-ndk-r16-linux-x86_64.zip

    然后在Android.defaultConfig.externalNativeBuild 的节点内增加一行过滤器,如下:

    abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'mips', 'mips64'

    注:在NDK r17发行中说明:“已删除对ARMv5(armeabi),MIPS和MIPS64的支持。尝试构建任何这些ABI将导致错误。

    相关文章

      网友评论

          本文标题:Android opencv 全景图片拼接笔记

          本文链接:https://www.haomeiwen.com/subject/hwlraqtx.html