Android opencv 全景图片拼接笔记

帮别人解决了一点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 
#include 
#include 
#import "opencv2/stitching.hpp"
#import "opencv2/imgcodecs.hpp"

#define BORDER_GRAY_LEVEL 0

#include 
#include 

#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 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 全景图片拼接笔记)