欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OpenCV学习记录——2.使用OpenCV的Stitcher类来实现全景图拼接

程序员文章站 2022-07-15 08:04:28
...

1.前言

现在回看起来,距离之前的那篇博客,竟然已经整整隔了一个月!在这一个月里,为了编写这个完全没有接触过的跨平台程序,作者踩了许多(超级多)的坑,这里就简单记录一下,希望能帮助到读者

2.常见的坑

  1. 安装OpenCV建议直接引入第三方.so,若想引入.a文件的话极其麻烦,集成的教程建议以下博客:
    https://www.jianshu.com/p/697def71d779

  2. 若build中出现错误,基本上都是CMakeList(在Android Studio现在用的是CMakeList而不是之前的Android.mk和Applicaiotn.mk)中的引用路径写错了,这里建议在写好路径时使用Ctrl+鼠标左键点击那个文件,查看是否可用进入到那个文件中,可用参考以下博客:
    https://blog.csdn.net/qq_34950682/article/details/95538383

  3. 其他的常见错误,参考以下博客:
    https://cloud.tencent.com/developer/article/1381003
    https://blog.csdn.net/qq_35366269/article/details/83275471

3.代码实现

  1. MainActivity.java:
package com.example.finalopencvproject;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.annotation.TargetApi;
import android.content.ContentUris;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import java.io.File;

public class MainActivity extends AppCompatActivity {

    /**
     * “选择图片”时的标识位
     */
    private static final int CHOOSE_PHOTO = 1;

    /**
     * 显示图片位置的标识位
     */
    private int DISPLAY_IMAGE = 1;

    /**
     * 图片拼接成功的标识位
     */
    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;

    /**
     * “选择图片1”的按钮实例
     */
    private Button mBtnSelect;

    /**
     * “选择图片2”的按钮实例
     */
    private Button mBtnSelect2;

    /**
     * “拼接图像”的按钮实例
     */
    private Button mMerge;

    /**
     * 显示图像1的实例
     */
    private ImageView mImageView;

    /**
     * 显示图像2的实例
     */
    private ImageView mImageView2;

    /**
     * 图像1的实例
     */
    private Bitmap mBitmap;

    /**
     * 图像2的实例
     */
    private Bitmap mBitmap2;

    /**
     * 存储待拼接的图像集合
     */
    private String[] mImagePath = new String[]{"abc","abc"};

    /**
     * 存储待拼接的图像集合的索引
     */
    private static int i = 0;

    // 引用native方法
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 初始化布局
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化控件实例
        mBtnSelect = findViewById(R.id.btn_select);
        mBtnSelect2 = findViewById(R.id.btn_select2);
        mMerge = findViewById(R.id.btn_merge);
        mImageView = findViewById(R.id.imageView);
        mImageView2 = findViewById(R.id.imageView2);

        // “选择图片1”的点击方法
        mBtnSelect.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
                }else{
                    DISPLAY_IMAGE = 1;
                    openAlbum();
                }
            }
        });

        // “选择图片2”的点击方法
        mBtnSelect2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
                    ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
                }else{
                    DISPLAY_IMAGE = 2;
                    openAlbum();
                }
            }
        });

        // “拼接图像”的点击事件
        mMerge.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mergeBitmap(mImagePath,new onStitchResultListener(){

                    @Override
                    public void onSuccess(Bitmap bitmap) {
                        Toast.makeText(MainActivity.this,"图片拼接成功!",Toast.LENGTH_LONG).show();
                        replaceImage(bitmap);
                    }

                    @Override
                    public void onError(String errorMsg) {
                        Toast.makeText(MainActivity.this,"图片拼接失败!",Toast.LENGTH_LONG).show();
                        System.out.println(errorMsg);
                    }
                });
            }
        });

        // 调用Native程序的示例
        /*
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
         */
    }

    /**
     * 动态申请权限的处理方法
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){
        switch (requestCode){
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
                    openAlbum();
                }else {
                    Toast.makeText(this,"拒绝授权将无法使用程序",Toast.LENGTH_SHORT).show();
                    finish();
                }
                break;
            default:
                break;
        }
    }

    /**
     * 打开相册
     */
    private void openAlbum(){
        Intent intent = new Intent("android.intent.action.GET_CONTENT");
        intent.setType("image/*");
        startActivityForResult(intent,CHOOSE_PHOTO);
    }

    /* OpenCV的测试方法
    private Bitmap hivePic() {
        Log.e(TAG, "hivePic: HHHA:0====>");
        Mat des = new Mat();
        Mat src = new Mat();
        Bitmap srcBit = BitmapFactory.decodeResource(getResources(), R.drawable.test2);
        Utils.bitmapToMat(srcBit, src);
        Bitmap grayBit = Bitmap.createBitmap(src.cols(), src.rows(), Bitmap.Config.ARGB_8888);
        Imgproc.cvtColor(src, des, Imgproc.COLOR_BGR2GRAY);
        Utils.matToBitmap(des, grayBit);
        return grayBit;
    }
     */

    /**
     * Activity的回调处理
     * @param requestCode 请求参数
     * @param resultCode 结果参数
     * @param data 数据
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case CHOOSE_PHOTO:
                if (resultCode  == RESULT_OK) {
                    if (Build.VERSION.SDK_INT >= 19) {
                        handleImageOnKitKat(data); /* 4.4及以上系统使用这个方法处理图片 */
                    } else {
                        handleImageBeforeKitKat(data); /* 4.4及以下系统使用这个方法处理图片 */
                    }
                }
                break;
            default:
                break;
        }
    }

    /**
     * 4.4及以上系统处理图片的方法
     * @param data 数据
     */
    @TargetApi(19)
    private void handleImageOnKitKat(Intent data){
        String imagePath = null;
        Uri uri = data.getData();
        if(DocumentsContract.isDocumentUri(this,uri)){
            String docId = DocumentsContract.getDocumentId(uri);
            if("com.android.providers.media.documents".equals(uri.getAuthority())){
                String id = docId.split(":")[1];
                String selection = MediaStore.Images.Media._ID + "=" + id;
                imagePath = getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
            }else if ("com.android.provideres.downloads.documents".equals(uri.getAuthority())){
                Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
                imagePath = getImagePath(contentUri,null);
            }
        }else if ("content".equalsIgnoreCase(uri.getScheme())){
            imagePath = getImagePath(uri,null);
        }else if ("file".equalsIgnoreCase(uri.getScheme())){
            imagePath = uri.getPath();
        }
        displayImage(imagePath);
    }

    /**
     * 4.4以下系统处理图片的方法
     * @param data 数据
     */
    private void handleImageBeforeKitKat(Intent data){
        Uri uri = data.getData();
        String imagePath = getImagePath(uri,null);
        displayImage(imagePath);
    }

    /**
     * 获取图片路径
     * @param uri 图片Url
     * @param selection 默认为空值
     * @return
     */
    private String getImagePath(Uri uri,String selection){
        String path = null;
        Cursor cursor = getContentResolver().query(uri,null,selection,null,null);
        if(cursor != null){
            if(cursor.moveToFirst()){
                path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
            }
            cursor.close();
        }
        mImagePath[i++] = path;
        return path;
    }

    /**
     * 图片显示的方法
     * @param imagePath 图片路径
     */
    private void displayImage(String imagePath){
        if(imagePath != null){
            Bitmap bitmap = BitmapFactory.decodeFile(imagePath);
            if (DISPLAY_IMAGE == 1){
                mImageView.setImageBitmap(bitmap);
                mBitmap = bitmap;
            }
            else {
                mImageView2.setImageBitmap(bitmap);
                mBitmap2 = bitmap;
            }
        }else{
            Toast.makeText(this,"获取图片失败",Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 拼接图片的方法
     * @param paths 图像URL的集合
     * @param listener 监听器回调
     * @return
     */
    private void mergeBitmap(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;
            }
        }
    }

    /**
     * 拼接监听回调的接口
     */
    public interface onStitchResultListener {

        void onSuccess(Bitmap bitmap);

        void onError(String errorMsg);
    }

    /**
     * 替换图片的方法
     * @param bitmap 拼接后的图像
     */
    private void replaceImage(Bitmap bitmap) {
        mImageView.setImageBitmap(bitmap);
        mImageView2.setVisibility(View.GONE);
        mBtnSelect.setVisibility(View.GONE);
        mBtnSelect2.setVisibility(View.GONE);
        mMerge.setVisibility(View.GONE);
    }

    /**
     * 调用底层的JNI方法(示例)
     * @return
     */
    // public native String stringFromJNI();
    private native static int[] stitchImages(String path[]);

    private native static void getMat(long mat);

    private native static int getBitmap(Bitmap bitmap);
}

  1. CMakeList.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# 包含opencv的头文件
include_directories(../../../opencv/jni/include)

# 静态方式导入库
#add_library(opencv_calib3d STATIC IMPORTED)
#add_library(opencv_core STATIC IMPORTED)
#add_library(opencv_features2d STATIC IMPORTED)
#add_library(opencv_flann STATIC IMPORTED)
#add_library(opencv_imgcodecs STATIC IMPORTED)
#add_library(opencv_imgproc STATIC IMPORTED)
#add_library(opencv_stitching STATIC IMPORTED)
#
#add_library(IlmImf STATIC IMPORTED)
#add_library(libjasper STATIC IMPORTED)
#add_library(libjpeg STATIC IMPORTED)
#add_library(libpng STATIC IMPORTED)
##add_library(libprotobuf STATIC IMPORTED)
#add_library(libtiff STATIC IMPORTED)
#add_library(libwebp STATIC IMPORTED)
#add_library(tbb STATIC IMPORTED)
#add_library(tegra_hal STATIC IMPORTED)
#
## 设置库路径
#set_target_properties(opencv_calib3d PROPERTIES IMPORTED_LOCATION ../../../libs/armeabi-v7a/libopencv_calib3d.a)
#set_target_properties(opencv_core PROPERTIES IMPORTED_LOCATION ../../../libs/armeabi-v7a/libopencv_core.a)
#set_target_properties(opencv_features2d PROPERTIES IMPORTED_LOCATION ../../../libs/armeabi-v7a/libopencv_features2d.a)
#set_target_properties(opencv_flann PROPERTIES IMPORTED_LOCATION ../../../libs/armeabi-v7a/libopencv_flann.a)
#set_target_properties(opencv_imgcodecs PROPERTIES IMPORTED_LOCATION ../../../libs/armeabi-v7a/libopencv_imgcodecs.a)
#set_target_properties(opencv_imgproc PROPERTIES IMPORTED_LOCATION ../../../libs/armeabi-v7a/libopencv_imgproc.a)
#set_target_properties(opencv_stitching PROPERTIES IMPORTED_LOCATION ../../../libs/armeabi-v7a/libopencv_stitching.a)
#
#
#set_target_properties(IlmImf PROPERTIES IMPORTED_LOCATION ../../../opencv/3rdparty/libs/armeabi-v7a/libIlmImf.a)
#set_target_properties(libjasper PROPERTIES IMPORTED_LOCATION ../../../opencv/3rdparty/libs/armeabi-v7a/liblibjasper.a)
#set_target_properties(libjpeg PROPERTIES IMPORTED_LOCATION ../../../opencv/3rdparty/libs/armeabi-v7a/liblibjpeg.a)
#set_target_properties(libpng PROPERTIES IMPORTED_LOCATION ../../../opencv/3rdparty/libs/armeabi-v7a/liblibpng.a)
##set_target_properties(libprotobuf PROPERTIES IMPORTED_LOCATION ${PROJECT_SOURCE_DIR}/opencv/3rdparty/libs/${ANDROID_ABI}/liblibprotobuf.a)
#set_target_properties(libtiff PROPERTIES IMPORTED_LOCATION ../../../opencv/3rdparty/libs/armeabi-v7a/liblibtiff.a)
#set_target_properties(libwebp PROPERTIES IMPORTED_LOCATION ../../../opencv/3rdparty/libs/armeabi-v7a/liblibwebp.a)
#set_target_properties(tbb PROPERTIES IMPORTED_LOCATION ../../../opencv/3rdparty/libs/armeabi-v7a/libtbb.a)
#set_target_properties(tegra_hal PROPERTIES IMPORTED_LOCATION ../../../opencv/3rdparty/libs/armeabi-v7a/libtegra_hal.a)
#
#set(SRC_DIR ../../../src/main/cpp)
#
#file(GLOB_RECURSE CPP_SRCS "${SRC_DIR}/*.cpp")  #指定当前目录下的所有.cpp文件(包括子目录)


# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        native-lib.cpp)


add_library(
        opencvc7 #最终在build中生成的so名字
        SHARED
        IMPORTED)
set_target_properties(
        opencvc7 #最终在build中生成的so名字
        PROPERTIES IMPORTED_LOCATION
        ${CMAKE_CURRENT_SOURCE_DIR}../../../../libs/${ANDROID_ABI}/libopencv_java3.so)
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        native-lib
        opencvc7

#        opencv_stitching
#        opencv_features2d
#        opencv_flann
#        opencv_imgcodecs
#        opencv_imgproc
#        opencv_core
#        opencv_calib3d
#
#        IlmImf
#        libjasper
#        libjpeg
#        libpng
#        #libprotobuf
#        libtiff
#        libwebp
#        tbb
#        tegra_hal

        # Links the target library to the log library
        # included in the NDK.
        jnigraphics
        ${log-lib})
  1. native-lib.cpp,注意修改相应的方法名,不然MainActivity无法调用
//#include <jni.h>
//#include <string>
//
//extern "C" JNIEXPORT jstring JNICALL
//Java_com_example_finalopencvproject_MainActivity_stringFromJNI(
//        JNIEnv *env,
//        jobject /* this */) {
//    std::string hello = "Hello from C++";
//    return env->NewStringUTF(hello.c_str());
//}
#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    "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_example_finalopencvproject_MainActivity_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_com_example_finalopencvproject_MainActivity_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_example_finalopencvproject_MainActivity_getBitmap(JNIEnv *env, jclass type, jobject bitmap) {
    if (finalMat.dims != 2){
        return -1;
    }
    MatToBitmap(env,finalMat,bitmap,false);
    return 0;
}
  1. activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_select"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="选择图片1"/>

    <Button
        android:id="@+id/btn_select2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="选择图片2"/>

    <Button
        android:id="@+id/btn_merge"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="拼接图像"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <ImageView
            android:id="@+id/imageView"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />

        <ImageView
            android:id="@+id/imageView2"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"/>
    </LinearLayout>

</LinearLayout>
  1. build.gradle
apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"
    defaultConfig {
        applicationId "com.example.finalopencvproject"
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++11 -frtti -fexceptions -lz"
                //cppFlags "-fexceptions"
                abiFilters 'armeabi-v7a','x86'
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            ndk{
                abiFilters "armeabi-v7a",'x86'
            }
        }
        debug{
            ndk{
                abiFilters "armeabi-v7a",'x86'
            }
        }
    }
    sourceSets{
        main{
            jniLibs.srcDirs=['libs']
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

4.效果展示

OpenCV学习记录——2.使用OpenCV的Stitcher类来实现全景图拼接
OpenCV学习记录——2.使用OpenCV的Stitcher类来实现全景图拼接