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

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.效果展示

在这里插入图片描述
在这里插入图片描述

5.源码地址

为了满足读者的需要,这里放出源码的仓库地址,仅供参考:
Android平台上实现全景图拼接

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

赈川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值