帮别人解决了一点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
解压后
显而易见,分别是简单的demo,sdk,许可和说明。打开readme真是傲娇,只有一句话
See http://opencv.org/platforms/android,
看说明很有比要,一般都是讲一下是什么怎么用。
这一堆大致看了下就是说这里有两种不同是适用建议....还是直接看谷歌翻译吧
3.android项目 导入
有两种方式1.直接使用,导入java sdk但是可能致使体积偏大。
所以还是使用2.使用opencv sdk 提供的 C++ 头文件与 .so动态库 与 .a静态库,自己封装jni。
把sdk下的native文件夹复制到android项目,参考别人的做法有的和app一级,有的放倒了src/main/jni下面,这个分析了并无要求分别,在后续的配置中指明位置就好。
在src/main目录下创建jni文件夹(cpp或者都是可以的,只要在配置中写对就好了)
配置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将导致错误。