图像拼接c语言,安卓上实现图像拼接(JNI调用NATIVE方法)

【嵌牛导读】:安卓上使用摄像头获取图片,使用NATIVE的OPENCV方法进行图像拼接。文中的几个知识点:使用Intent调用系统默认相机拍摄照片;读取图片文件流转化为Bitmap;JNI中获取JAVA类,使用JAVA方法;使用OPENCV的Stitcher.stitch(Vector,Mat)方法进行图像拼接。

【嵌牛鼻子】:opencv4android;JNI;图像拼接

【嵌牛提问】:如何在安卓上调用C语言实现实现图像拼接

【嵌牛正文】:

废话不多说,直接上步骤。

一、创建android工程,调入opencv4android的Java sdk。

b84428f3068f

将原生库复制到工程目录下,与APP同级。

b84428f3068f

完成上述操作,你的工程目录应该是这样的:

b84428f3068f

二、配置NDK

修改工程目录下的gradle.properties

b84428f3068f

修改工程目录下的local.properties,添加上你下载的NDK路径

b84428f3068f

修改APP下的build.gradle,在android里添加

sourceSets.main.jni.srcDirs= []

sourceSets.main.jniLibs.srcDirs= ['src/main/libs','src/main/jniLibs']

//禁止自带的ndk功能

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'

这样,NDK就配置完成了。

三、编写C++文件,生成.so库

1.main目录下新建jni文件夹。

b84428f3068f

新建OpenCVCPP类,类里面声明一个native函到数。

b84428f3068f

使用终端跳转到app\build\intermediates\classes\debug目录

b84428f3068f

生成.h文件

b84428f3068f

将生成的.h文件拷贝到jni文件夹下面,同时创建同名的.cpp文件。

b84428f3068f

编写.cpp文件代码,该代码中首先获取java类,然后调用java类的方法将获取传入的Mat数组的地址,然后将java地址转化为c++地址,最后生成c++的Mat类,然后使用opencv的stitcher.stitch(clickedImages,output_stitched);方法进行图像拼接,最后将拼接好的图像写到指定地址处,

#include "com_tinymonster_opencvpicpaste_OpenCVCPP.h"

#include

#include

#include

#include

using namespace cv;

using namespace std;

char FILEPATH[100]="/storage/emulated/0/panorama_stitched.jpg";

JNIEXPORT jint JNICALL Java_com_tinymonster_opencvpicpaste_OpenCVCPP_StitchPanorama

(JNIEnv* env, jclass obj, jobjectArray images, jint size, jlong resultMatAddr){

jint resultReturn=0;

vector clickedImages=vector();

Mat output_stitched=Mat();

Mat& srcRes=*(Mat*)resultMatAddr, img;

jclass clazz=(env)->FindClass("org/opencv/core/Mat");//调用java的Mat类

jmethodID getNativeObjAddr=(env)->GetMethodID(clazz,"getNativeObjAddr","()J");//调用java的Mat类的方法

for(int i=0;i

jobject obj=(env->GetObjectArrayElement(images,i));//获取图片对象

jlong result=(env)->CallLongMethod(obj,getNativeObjAddr,NULL);//调用java方法,返回MAT的nativeAddr

img=*(Mat*) result;

resize(img,img,Size(img.rows/10,img.cols/10));

clickedImages.push_back(img);

env->DeleteLocalRef(obj);//清除对象

}

//env->DeleteLocalRef(images);//清除对象

Stitcher stitcher=Stitcher::createDefault();

Stitcher::Status status=stitcher.stitch(clickedImages,output_stitched);

output_stitched.copyTo(srcRes);

if(status==Stitcher::OK){

resultReturn=1;

}else{

resultReturn=0;

}

return resultReturn;

}

创建两个mk文件

b84428f3068f

android.mk的代码为

b84428f3068f

application.mk的代码为

b84428f3068f

点右边Gradle里面的ndkBuild,生成.so文件

b84428f3068f

四、编写java代码

1.首先写布局。两个按钮,一个imageView

xmlns:app="http://schemas.android.com/apk/res-auto"

xmlns:tools="http://schemas.android.com/tools"

android:layout_width="match_parent"

android:layout_height="match_parent"

tools:context="com.tinymonster.opencvpicpaste.MainActivity">

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="vertical"

>

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="horizontal"

>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:id="@+id/bClickImage"

android:text="Click more images"

/>

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:id="@+id/bDone"

android:text="Done"

/>

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:id="@+id/ivImage"

/>

2.写java代码,代码中多次通过Intent调用摄像头拍照,将照片保存在list中,然后使用NATIVE方法,将待处理图图片数组,数组大小,返回的MAT的地址传入。最后将处理结果显示即可。

package com.tinymonster.opencvpicpaste;

import android.Manifest;

import android.content.Intent;

import android.content.pm.PackageManager;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.net.Uri;

import android.os.AsyncTask;

import android.os.Environment;

import android.os.StrictMode;

import android.provider.MediaStore;

import android.support.annotation.NonNull;

import android.support.v4.app.ActivityCompat;

import android.support.v4.content.ContextCompat;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.ImageView;

import android.widget.Toast;

import com.orhanobut.logger.Logger;

import org.opencv.android.BaseLoaderCallback;

import org.opencv.android.LoaderCallbackInterface;

import org.opencv.android.OpenCVLoader;

import org.opencv.android.Utils;

import org.opencv.core.CvType;

import org.opencv.core.Mat;

import org.opencv.core.Size;

import org.opencv.imgproc.Imgproc;

import java.io.File;

import java.io.FileNotFoundException;

import java.io.InputStream;

import java.util.ArrayList;

import java.util.List;

public class MainActivity extends AppCompatActivity {

private static final String TAG="MainActivity";

private ImageView ivImage;

private Button bClickImage;

private Button bDone;

private Uri fileUri;

private String FILE_LOCATION= Environment.getExternalStorageDirectory().getAbsolutePath()+"/OpencvStudy1/";//文件夹路径

private static final int  CLICK_PHOTO=1;

private Bitmap image;

private List clickedImages=new ArrayList<>();

Mat src;//用于保存最新的一副照片

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();

StrictMode.setVmPolicy(builder.build());

builder.detectFileUriExposure();

if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED||

ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED||

ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA)!= PackageManager.PERMISSION_GRANTED||

ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.RECORD_AUDIO)!=PackageManager.PERMISSION_GRANTED){

Log.e("MainActivity,请求权限"," ");

ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO},2);

}else {

Log.e("MainActivity,跳转到相机"," ");

initView();

Log.e("MainActivity","3");

//            if (!OpenCVLoader.initDebug()) {

//                Log.d(TAG, "Internal OpenCV library not found. Using OpenCV Manager for initialization");

//                OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_11, MainActivity.this, mLoaderCallback);

//            } else {

//                Log.d(TAG, "OpenCV library found inside package. Using it!");

//                mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS);

//            }

System.loadLibrary("opencv_java");

System.loadLibrary("stitcher");

}

}

private void initView(){

ivImage=(ImageView)findViewById(R.id.ivImage);

bClickImage=(Button)findViewById(R.id.bClickImage);

bDone=(Button)findViewById(R.id.bDone);

bClickImage.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

Intent intent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

File imagesFolder=new File(FILE_LOCATION);

imagesFolder.mkdirs();

File image=new File(imagesFolder,"panorama"+System.currentTimeMillis()+".jpg");//创建一个文件

fileUri=Uri.fromFile(image);//获取文件URI

Logger.d("获取的文件URI="+fileUri.toString());

intent.putExtra(MediaStore.EXTRA_OUTPUT,fileUri);//设置图像文件名

startActivityForResult(intent,CLICK_PHOTO);

}

});

bDone.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

if(clickedImages.size()==0){

Toast.makeText(getApplicationContext(),"没有拍摄任何图像",Toast.LENGTH_SHORT).show();

}else if(clickedImages.size()==1){

Toast.makeText(getApplicationContext(),"只拍摄到一幅图像",Toast.LENGTH_SHORT).show();

image=Bitmap.createBitmap(src.cols(),src.rows(),Bitmap.Config.ARGB_8888);

Utils.matToBitmap(src,image);

ivImage.setImageBitmap(image);

}else {

//执行拼接操作

craetePanorama();

}

}

});

}

@Override

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

switch (requestCode){

case 2:

if(ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE)== PackageManager.PERMISSION_GRANTED||ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)== PackageManager.PERMISSION_GRANTED||ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA)== PackageManager.PERMISSION_GRANTED

||ContextCompat.checkSelfPermission(MainActivity.this,Manifest.permission.RECORD_AUDIO)!=PackageManager.PERMISSION_GRANTED){

Log.e("请求权限完成,跳转"," ");

initView();

System.loadLibrary("opencv_java");

System.loadLibrary("stitcher");

}else {

ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.CAMERA,Manifest.permission.RECORD_AUDIO},2);

Log.e("再次请求权限"," ");

}

break;

}

}

private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {

@Override

public void onManagerConnected(int status) {

switch (status) {

case LoaderCallbackInterface.SUCCESS: {

Log.i("MainActivity", "OpenCV loaded successfully");

System.loadLibrary("stitcher");

}

break;

default: {

super.onManagerConnected(status);

}

break;

}

}

};

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

super.onActivityResult(requestCode, resultCode, data);

switch (requestCode){

case CLICK_PHOTO:

try{

Logger.d("接收到一副照片");

Log.e(TAG,"接收到一张照片");

final InputStream inputStream=getContentResolver().openInputStream(fileUri);

final Bitmap selectedImage= BitmapFactory.decodeStream(inputStream);//InputStream->Bitmap

src =new Mat(selectedImage.getHeight(),selectedImage.getWidth(), CvType.CV_8UC4);

Imgproc.resize(src,src,new Size(src.rows()/4,src.cols()/4));//修改图像尺寸

Utils.bitmapToMat(selectedImage,src);

Imgproc.cvtColor(src,src,Imgproc.COLOR_BGR2RGB);

clickedImages.add(src);

}catch (FileNotFoundException e){

e.printStackTrace();

}

break;

}

}

private void craetePanorama(){

new AsyncTask(){

@Override

protected void onPreExecute() {

super.onPreExecute();

}

@Override

protected Bitmap doInBackground(Void...voids) {

Mat srcRes=new Mat();

Log.e(TAG,"clickedImages大小:"+clickedImages.size());

int success=OpenCVCPP.StitchPanorama(clickedImages.toArray(),clickedImages.size(),srcRes.getNativeObjAddr());

clickedImages.clear();

Log.e(TAG,"native返回结果:"+success);

if(success==0){

runOnUiThread(new Runnable() {

@Override

public void run() {

Toast.makeText(MainActivity.this,"合成失败",Toast.LENGTH_SHORT).show();

}

});

return null;

}else {

Bitmap bitmap1=Bitmap.createBitmap(srcRes.cols(),srcRes.rows(),Bitmap.Config.ARGB_8888);

Utils.matToBitmap(srcRes,bitmap1);

runOnUiThread(new Runnable() {

@Override

public void run() {

Toast.makeText(MainActivity.this,"合成成功",Toast.LENGTH_SHORT).show();

}

});

return bitmap1;

}

}

@Override

protected void onPostExecute(Bitmap bitmap) {

super.onPostExecute(bitmap);

ivImage.setImageBitmap(bitmap);

}

}.execute();

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值