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

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

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

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

【嵌牛正文】:

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

一、创建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函到数。

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


生成.h文件

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


编写.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文件


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方法,将待处理图图片数组,数组大小,返回的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();

    }

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容