Android 利用OpenCV 的Stitcher做全景图片拼接(支持平面和球面)

开发项目中遇到一个需求 就是用手机按照顺序拍几张图片 然后将图片拼接成一张全景的照片 百度了一下 看到OpenCV 的Stitcher工具支持全景图片拼接 于是研究了一下OpenCV  花了差不多一周时间才研究出来 呕心沥血的成果分享给大家 本篇文章将带给大家的知识点:

(1).Windows上利用AndroidStudio进行ndk编译

(2).使用静态库的方式导入OpenCV(生成的包更小,传统的导入方式需要java的moudle和so或者opencvmanager,生成的包非常臃肿)

(3).在 C代码里将OpenCV的mat转换成bitmap 上传给java层

废话不多说 开始撸代码

GitHub 地址:https://github.com/yu12mang/OpenCvStitcher

1.准备工作

保证AndroidStudio安装了ndk

在AndroidStudio中新建一个空的android项目

下载Opencv-android-sdk:可以在opencv官网下载 不过下载速度是龟速 所以我建议去github上面下载:https://github.com/opencv/opencv/releases    最好下载3.2版本 因为我是在3.2版本上撸出来的 下载完成后放进项目中 如图:

Android 利用OpenCV 的Stitcher做全景图片拼接(支持平面和球面)_第1张图片

 

2.在src/main目录下创建jni文件夹(可命名为cpp或者其他文件件名 jni是通用文件名),接下来编写ndk编译的必要文件Android.mk和Application.mk

Android 利用OpenCV 的Stitcher做全景图片拼接(支持平面和球面)_第2张图片

Android 利用OpenCV 的Stitcher做全景图片拼接(支持平面和球面)_第3张图片

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
#需要编译的平台 我只需要arm64-v8a和armeabi-v7a 你可以自行设置
APP_ABI := arm64-v8a armeabi-v7a
APP_PLATFORM := android-8

 

3.编写app目录下的build.gradle 这是我的build.gradle 你需要将包名改成自己的 其他的你自己看着改:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion '25.0.0'
    defaultConfig {
        applicationId "com.logan.opencvforandroid"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    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'
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.2.0'
    compile 'com.android.support:design:24.2.0'
    compile 'com.android.support:cardview-v7:24.2.0'
}

 

4.编写java层的jni接口 直接调用StitchImages函数即可

 Android 利用OpenCV 的Stitcher做全景图片拼接(支持平面和球面)_第4张图片

package com.fimi.gh2.tracker;

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);
    }


}

 

5.最后一步编写cpp文件 也是最重要的一步 这是我代码 你也可以根据需求自己进行修改 native-lib.cpp:

Android 利用OpenCV 的Stitcher做全景图片拼接(支持平面和球面)_第5张图片

#include 
#include 
#include 
#import "opencv2/stitching.hpp"
#import "opencv2/imgcodecs.hpp"

#define BORDER_GRAY_LEVEL 0

#include 
#include 

#define LOG_TAG    "DDLog-jni"
#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/panorama_stitched.jpg";


cv::Mat finalMat;

extern "C"
JNIEXPORT jintArray JNICALL
Java_com_fimi_gh2_tracker_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_com_fimi_gh2_tracker_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_com_fimi_gh2_tracker_ImagesStitchUtil_getBitmap(JNIEnv *env, jclass type, jobject bitmap) {

    if (finalMat.dims != 2){
        return -1;
    }

    MatToBitmap(env,finalMat,bitmap,false);

    return 0;

}

好了 点击编译 不出意外的话会生成一个Stitcher.so的文件 如图所示:

Android 利用OpenCV 的Stitcher做全景图片拼接(支持平面和球面)_第6张图片

 

大功告成 !!! 是不是很简单 

另外如果你觉得拼接时间过长可以调整native-lib.cpp中的该部位:

Android 利用OpenCV 的Stitcher做全景图片拼接(支持平面和球面)_第7张图片

具体调整方法 支持球面全景图片拼接

    //false是禁止gpu加速 可改成true
    Stitcher stitcher = Stitcher::createDefault(false);
    stitcher.setRegistrationResol(0.6);//为了加速,我选0.1,默认是0.6,最大值1最慢,此方法用于特征点检测阶段,如果找不到特征点,调高吧
    //stitcher.setSeamEstimationResol(0.1);//默认是0.1
    //stitcher.setCompositingResol(-1);//默认是-1,用于特征点检测阶段,找不到特征点的话,改-1
    stitcher.setPanoConfidenceThresh(1);//默认是1,见过有设0.6和0.4的
    stitcher.setWaveCorrection(false);//默认是true,为加速选false,表示跳过WaveCorrection步骤
    //stitcher.setWaveCorrectKind(detail::WAVE_CORRECT_HORIZ);//还可以选detail::WAVE_CORRECT_VERT ,波段修正(wave correction)功能(水平方向/垂直方向修正)。因为setWaveCorrection设的false,此语句没用

    //找特征点surf算法,此算法计算量大,但对刚体运动、缩放、环境影响等情况下较为稳定
    detail::SurfFeaturesFinder *featureFinder = new detail::SurfFeaturesFinder();
    stitcher.setFeaturesFinder(featureFinder);

    //找特征点ORB算法,但是发现草地这组图,这个算法不能完成拼接
    //detail::OrbFeaturesFinder *featureFinder = new detail::OrbFeaturesFinder();
    //stitcher.setFeaturesFinder(featureFinder);

    //Features matcher which finds two best matches for each feature and leaves the best one only if the ratio between descriptor distances is greater than the threshold match_conf.
    detail::BestOf2NearestMatcher *matcher = new detail::BestOf2NearestMatcher(false, 0.5f/*=match_conf默认是0.65,我选0.8,选太大了就没特征点啦,0.8都失败了*/);
    stitcher.setFeaturesMatcher(matcher);

    // Rotation Estimation,It takes features of all images, pairwise matches between all images and estimates rotations of all cameras.
    //Implementation of the camera parameters refinement algorithm which minimizes sum of the distances between the rays passing through the camera center and a feature,这个耗时短
    stitcher.setBundleAdjuster(new detail::BundleAdjusterRay());
    //Implementation of the camera parameters refinement algorithm which minimizes sum of the reprojection error squares.
    //stitcher.setBundleAdjuster(new detail::BundleAdjusterReproj());

    //Seam Estimation
    //Minimum graph cut-based seam estimator
    //stitcher.setSeamFinder(new detail::GraphCutSeamFinder(detail::GraphCutSeamFinderBase::COST_COLOR));//默认就是这个
    //stitcher.setSeamFinder(new detail::GraphCutSeamFinder(detail::GraphCutSeamFinderBase::COST_COLOR_GRAD));//GraphCutSeamFinder的第二种形式
    //啥SeamFinder也不用,Stub seam estimator which does nothing.
    stitcher.setSeamFinder(new detail::NoSeamFinder);
    //Voronoi diagram-based seam estimator.
    //stitcher.setSeamFinder(new detail::VoronoiSeamFinder);

    //exposure compensators曝光补偿
    //stitcher.setExposureCompensator(new detail::BlocksGainCompensator());//默认的就是这个
    //不要曝光补偿
    stitcher.setExposureCompensator(new detail::NoExposureCompensator());
    //Exposure compensator which tries to remove exposure related artifacts by adjusting image intensities
    //stitcher.setExposureCompensator(new detail::detail::GainCompensator());
    //Exposure compensator which tries to remove exposure related artifacts by adjusting image block intensities  
    //stitcher.setExposureCompensator(new detail::detail::BlocksGainCompensator()); 

    //Image Blenders
    //Blender which uses multi-band blending algorithm 
    stitcher.setBlender(new detail::MultiBandBlender(try_use_gpu));//默认的是这个
    //Simple blender which mixes images at its borders
    //stitcher.setBlender(new detail::FeatherBlender());//这个简单,耗时少

    //柱面?球面OR平面?默认为球面
    //PlaneWarper*  cw = new PlaneWarper();
    //SphericalWarper*  cw = new SphericalWarper();
    //CylindricalWarper*  cw = new CylindricalWarper();
    //stitcher.setWarper(cw);

    Stitcher::Status status = stitcher.estimateTransform(imgs);
    if (status != Stitcher::OK)
    {
        cout << "Can't stitch images, error code = " << int(status) << endl;
        return -1;
    }
    status = stitcher.composePanorama(pano);
    if (status != Stitcher::OK)
    {
        cout << "Can't stitch images, error code = " << int(status) << endl;
        return -1;
    }

看到这里了给个赞鼓励鼓励吧 或者关注一波 我会陆续将我劳动成果分享给大家!

感谢拨冗翻阅拙作,敬请斧正.

 

 

 

你可能感兴趣的:(opencv,全景图片拼接,android,ndk,stitcher)