安卓上實現(xiàn)圖像拼接(JNI調用NATIVE方法)

【嵌牛導讀】:安卓上使用攝像頭獲取圖片,使用NATIVE的OPENCV方法進行圖像拼接。文中的幾個知識點:使用Intent調用系統(tǒng)默認相機拍攝照片;讀取圖片文件流轉化為Bitmap;JNI中獲取JAVA類,使用JAVA方法;使用OPENCV的Stitcher.stitch(Vector<Mat>,Mat)方法進行圖像拼接。

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

【嵌牛提問】:如何在安卓上調用C語言實現(xiàn)實現(xiàn)圖像拼接

【嵌牛正文】:

廢話不多說,直接上步驟。

一、創(chuàng)建android工程,調入opencv4android的Java sdk。

將原生庫復制到工程目錄下,與APP同級。

完成上述操作,你的工程目錄應該是這樣的:

二、配置NDK

修改工程目錄下的gradle.properties

修改工程目錄下的local.properties,添加上你下載的NDK路徑


修改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文件夾。

新建OpenCVCPP類,類里面聲明一個native函到數(shù)。

使用終端跳轉到app\build\intermediates\classes\debug目錄


生成.h文件

將生成的.h文件拷貝到jni文件夾下面,同時創(chuàng)建同名的.cpp文件。


編寫.cpp文件代碼,該代碼中首先獲取java類,然后調用java類的方法將獲取傳入的Mat數(shù)組的地址,然后將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;

? }

創(chuàng)建兩個mk文件


android.mk的代碼為


application.mk的代碼為


點右邊Gradle里面的ndkBuild,生成.so文件


四、編寫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方法,將待處理圖圖片數(shù)組,數(shù)組大小,返回的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");//創(chuàng)建一個文件

? ? ? ? ? ? ? ? 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 {

? ? ? ? ? ? ? ? ? ? //執(zhí)行拼接操作

? ? ? ? ? ? ? ? ? ? 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();

? ? }

}

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容