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