开发项目中遇到一个需求 就是用手机按照顺序拍几张图片 然后将图片拼接成一张全景的照片 百度了一下 看到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
保证AndroidStudio安装了ndk
在AndroidStudio中新建一个空的android项目
下载Opencv-android-sdk:可以在opencv官网下载 不过下载速度是龟速 所以我建议去github上面下载:https://github.com/opencv/opencv/releases 最好下载3.2版本 因为我是在3.2版本上撸出来的 下载完成后放进项目中 如图:
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
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'
}
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);
}
}
#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的文件 如图所示:
大功告成 !!! 是不是很简单
另外如果你觉得拼接时间过长可以调整native-lib.cpp中的该部位:
具体调整方法 支持球面全景图片拼接:
//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;
}
看到这里了给个赞鼓励鼓励吧 或者关注一波 我会陆续将我劳动成果分享给大家!
感谢拨冗翻阅拙作,敬请斧正.