OpenCV移动端之android JNI
OpenCV强大的图像处理被广泛应用与各行各业,如何将其部署到不同的平台是每个OpenCV开发人员必须面对解决的。对于OpenCV开发,很多选择C++实现基本的算法,而如何将算法应用到IOS、Android或嵌入式设备上却 是一个问题,这节将讲解OpenCV 在Android上的部署,这里选择在Android Studio上开发Android应用程序。
1.首先在Opencv官网下载Android SDK,这里选中最新的opencv-3.4.1-android-sdk.zip下载 ,解压得到OpenCV-android-sdk。
2.启动Android-stiudio(本人安装的是AS 3.1版本),新建一个Android项目,Application name填OpencvJNI,Company domaint填xinyi61,Package name者为xinyi61.opencvjni,并且选上Include C++ support,最终效果如下:
3.选中app/src/main右键 New->Folder->JNI Folder,这将在main下面创建一个jni的目录,将1解压文件夹下OpenCV-android-sdk/sdk下的native目录整个拷贝到jni目录下,此时jni目录展开如下:
4. 选中app/src/main/java/xinyi61.opencvjni 右键New->Java Class,新建一个OpencvJNI的java类,OpencvJNI如下所示
public class OpencvJNI { static { System.loadLibrary("imgproc"); } public int api_method; public int img_width; public int img_height; public int retval; public int [] srcbuf; public int [] dstbuf; public int [] outmuf; public int [] inmask; public double clarityVal; public String version; public static native Object jniApiMethod(Object param);}
5.编写带有native声明的方法的java类,使用 javac 命令编译所编写的java类。点击Build->Make Project,完成后会在app/build/intermediates/classes/debug/xinyi61/opencvjni下生成一个OpencvJNI.class,如下所示
6.使用 “ javah -jni java类名” 生成扩展名为h的头文件,使用C/C++实现本地方法。打开Android Studio的Terminal,cd 到app/src/main/jni目录下,输入
javah -jni -classpath ../../../build/intermediates/classes/debug xinyi61.opencvjni.OpencvJNI
自动生成头文件,查看jni下生成的头文件xinyi61_opencvjni_OpencvJNI.h,
/* DO NOT EDIT THIS FILE - it is machine generated */#include /* Header for class xinyi61_opencvjni_OpencvJNI */#ifndef _Included_xinyi61_opencvjni_OpencvJNI#define _Included_xinyi61_opencvjni_OpencvJNI#ifdef __cplusplusextern "C" {#endif/* * Class: xinyi61_opencvjni_OpencvJNI * Method: jniMethod * Signature: (Ljava/lang/Object;)Ljava/lang/Object; */JNIEXPORT jobject JNICALL Java_xinyi61_opencvjni_OpencvJNI_jniMethod (JNIEnv *, jclass, jobject);#ifdef __cplusplus}#endif#endif
选中jni右键New->C/C++ Source FIle 命名为xinyi61_opencvjni_OpencvJNI,作为对应的java类的JNI实现源文件,其中需要访问java中定义的类对象的实例,则由以下几步完成
1.通过GetObjectClass()函数获得该对象的类class,返回值是一个jclass;
2.调用GetFieldID函数得到需要访问的实例域(变量)在该c类中id
3.调用Get【Type】Field()得到访问的变量值。其中【Type】对应变量的类型。
最终实现为:
//// Created by Administrator on 2018/7/3/003.//#include #include #include #include #include #include #include "opencv2/opencv.hpp"#include "xinyi61_opencvjni_OpencvJNI.h"#define LOG_TAG "MYJNI"#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)#ifdef __cplusplusextern "C" {#endifenum JNI_API__METHOD{ JNI_API_METHOD_GETVERSION=0, /*get build version and time*/ JNI_API_METHOD_IM2GRAY, /* convert image to gray*/ JNI_API_METHOD_INPAINT, /* image restore */ JNI_API_METHOD_AUTOCOMPLATE, /* image auto restore */ JNI_API_METHOD_AUTOWHITEBALANCE, /* image auto white balance */ JNI_API_METHOD_AUTOWHITEBACKGROUND, /* replace background with white */ JNI_API_METHOD_ADJUSTIMAGECLARITY, /* adjust image clarity*/ JNI_API_METHOD_INTERACTIVEMATTING, /* interactivate matting */ JNI_API_METHOD_END = JNI_API_METHOD_INTERACTIVEMATTING};JNIEXPORT jobject JNICALL Java_bc_imgproc_jniapi_jniApiMethod (JNIEnv *env, jclass jcls, jobject jobj){ jclass jclsinput = env->GetObjectClass(jobj); if (jclsinput == NULL) { return env->NewStringUTF("can not find class"); } /* get jobj property id */ jfieldID jfdmethod = env->GetFieldID(jclsinput, "api_method", "I"); jfieldID jfdimgwidth = env->GetFieldID(jclsinput, "img_width", "I"); jfieldID jfdimgheight = env->GetFieldID(jclsinput, "img_height", "I"); jfieldID jfdcallret = env->GetFieldID(jclsinput, "retval", "I"); jfieldID jfdsrc = env->GetFieldID(jclsinput, "srcbuf", "[I"); jfieldID jfddst = env->GetFieldID(jclsinput, "dstbuf", "[I"); jfieldID jfdinmask = env->GetFieldID(jclsinput, "inmask", "[I"); jfieldID jfdversion = env->GetFieldID(jclsinput, "version", "Ljava/lang/String;"); jfieldID jfdclarityVal = env->GetFieldID(jclsinput, "clarityVal", "D"); /* create new object for return */ jobject jobjret = env->AllocObject(jclsinput); /* get property value,get jni method */ int japi_call_method = env->GetIntField(jobj, jfdmethod); LOGI("japi_call_method:%d", japi_call_method); if ((japi_call_method < JNIAPI_METHOD_VERSION) || (japi_call_method > JNIAPI_METHOD_END)) { LOGE("RET_ERROR_JNI_METHOD_ISNOT_SURPPORT:%x", RET_ERROR_JNI_METHOD_ISNOT_SURPPORT); env->SetIntField(jobjret, jfdcallret, RET_ERROR_JNI_METHOD_ISNOT_SURPPORT); return jobjret; } if (japi_call_method == JNIAPI_METHOD_VERSION) { char buf[40]; sprintf(buf, "V:%s Build Time:%s-%s", CV_VERSION, __DATE__, __TIME__); std::string version = buf; jstring jstr = env->NewStringUTF(version.c_str()); if (jstr == NULL) { env->SetIntField(jobjret, jfdcallret, RET_FAILED); return jobjret; } env->SetObjectField(jobjret, jfdversion, jstr); env->SetIntField(jobjret, jfdcallret, RET_OK); return jobjret; } int ret = 0; /* get property value,img_width img_height */ int w = env->GetIntField(jobj, jfdimgwidth); int h = env->GetIntField(jobj, jfdimgheight); if ((w < MINIMUM_IMAGE_WIDTH) || (h < MINIMUM_IMAGE_HEIGHT)) { env->SetIntField(jobjret, jfdcallret, RET_ERROR_IAMGE_ISNOT_SURPPORT); return jobjret; } LOGI("00japi_call_method:%d", japi_call_method); jintArray jint_bufsrc = (jintArray)env->GetObjectField(jobj, jfdsrc); /* get srcbuf length */ jsize len = env->GetArrayLength(jint_bufsrc); if (len <= 0) { env->SetIntField(jobjret, jfdcallret, RET_ERROR_IAMGE_LEN_ERROR); return jobjret; } /* get point to array object */ jint *bufsrc = env->GetIntArrayElements(jint_bufsrc, NULL); cv::Mat srcimg(h, w, CV_8UC4, (unsigned char *)bufsrc); cv::cvtColor(srcimg, srcimg, cv::COLOR_RGBA2BGR);cv::Size sz = srcimg.size(); //cv::imwrite("/storage/emulated/0/imsrc.jpg", srcimg); LOGI("01srcimg size:%d len:%d w:%d h:%d", srcimg.size().area(), len, w, h); bc_jniapi_method method = (bc_jniapi_method)japi_call_method; bc_jniapi_in inparam; inparam.srcimg = srcimg; bc_jniapi_out outparam; switch (japi_call_method) { case JNIAPI_METHOD_WHITEBALANCE: break; case JNIAPI_METHOD_INPAINT: { jintArray jint_bufinmask = (jintArray)env->GetObjectField(jobj, jfdinmask); jint *bufinmask = env->GetIntArrayElements(jint_bufinmask, NULL); inparam.maskimg = cv::Mat(h, w, CV_8UC1, (unsigned char *)bufinmask); break; } } LOGI("02bc_algorithm_api call:%d", japi_call_method); ret = bc_algorithm_api(method, &inparam, &outparam); LOGI("111bc_algorithm_api call end ret:%d result mat:%zu %d", ret, outparam.dstimg.size(), outparam.dstimg[0].channels()); /* new an jintArray object */ jintArray retdst = env->NewIntArray(outparam.dstimg.size() * w * h); if (retdst == NULL) { env->SetIntField(jobjret, jfdcallret, RET_MALLOC_MEMORY_ERROR); return jobjret; } LOGI("03bc_algorithm_api call:%d", japi_call_method); jint *dbuf = env->GetIntArrayElements(retdst, NULL); for (int i = 0; i < outparam.dstimg.size(); ++i) { cv::cvtColor(outparam.dstimg[i], outparam.dstimg[i], cv::COLOR_BGR2RGBA);cv::resize(outparam.dstimg[i],outparam.dstimg[i], sz, cv::INTER_AREA); //char buf[60]; //sprintf(buf, "/storage/emulated/0/imresut_%d.jpg", i); //LOGI("return result mat rows:%d cols:%d channels:%d", // outparam.dstimg[i].rows, outparam.dstimg[i].cols, outparam.dstimg[i].channels()); //cv::imwrite(buf, outparam.dstimg[i]);env->SetIntArrayRegion(retdst, i * w * h, w * h, (int *)outparam.dstimg[i].data); } env->SetObjectField(jobjret, jfddst, retdst); env->SetIntField(jobjret, jfdcallret, ret); env->ReleaseIntArrayElements(jint_bufsrc, bufsrc, 0); env->ReleaseIntArrayElements(retdst, dbuf, 0); LOGI("0000000api_call_method call end with ret:%d", ret); return jobjret;}#ifdef __cplusplus}#endif
其中java数据类型和c数据类型关系如下表所示
7. 将C/C++编写的文件生成动态连接库。选中jni右键New->File 建立jni的Android.mk,
LOCAL_PATH:=$(call my-dir)include $(CLEAR_VARS)OpenCV_INSTALL_MODULES := onOpenCV_CAMERA_MODULES := offOPENCV_LIB_TYPE :=STATICifeq ("$(wildcard $(OPENCV_MK_PATH))","")include $(LOCAL_PATH)/native/jni/OpenCV.mkelseinclude $(OPENCV_MK_PATH)endifLOCAL_MODULE := opencv_worldLOCAL_SRC_FILES := xinyi61_opencvjni_OpencvJNI.cppLOCAL_LDLIBS += -lm -lloginclude $(BUILD_SHARED_LIBRARY)
选中jni右键New->File 建立jni的Application.mk,
APP_STL := gnustl_staticAPP_CPPFLAGS := -frtti -fexceptionsAPP_PLATFORM := android-14
直接到Terminal下的jni目录运行ndk-build,此时就会在app/src/main/libs下生成各个平台的libopencv_world.so 。
libopencv_world.so 就可以拿来使用了。
这里简单写个demo程序进行验证,最后结果图如下所示:
8.修改app/src/main/res/layout/activity_main.xml,修改后为下
<?xml version="1.0" encoding="utf-8"?>
9,选图按钮实现从手机中选中一副图片,这就需要给手机开权限,到app/src/main/res下修改AndroidManifest.xml,修改后的文件
如下,加粗体部分为添加的部分。
<?xml version="1.0" encoding="utf-8"?>
10.编辑app/src/main/java/xinyi61.opencvjni/MainActivity下实现整个处理,
package xinyi61.opencvjni;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.widget.TextView;import android.content.Context;import android.content.Intent;import android.database.Cursor;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Bitmap.Config;import android.net.Uri;import android.provider.MediaStore;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.ImageView;import android.widget.TextView;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); System.loadLibrary("opencv_world"); } private static final int IMAGE = 1; private TextView processTime; private TextView bccvVersion; private ImageView imageView; private Button btnChooseImg; private Bitmap yourSelectedImage = null; private Process myjni = new Process(); private Process res = new Process(); private static final String TAG = "bccv"; Bitmap bm; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); processTime = (TextView) findViewById(R.id.processTime); bccvVersion = (TextView) findViewById(R.id.bccvVersion); imageView = (ImageView) findViewById(R.id.imageView); btnChooseImg = (Button) findViewById(R.id.chooseImg); btnChooseImg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.chooseImg: { Intent intent = new Intent(Intent.ACTION_PICK, null); intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,"image/*"); startActivityForResult(intent, 0x1); break; } } } }); Button processImg = (Button) findViewById(R.id.processImg); processImg.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { switch (v.getId()) { case R.id.processImg: { imageView.setDrawingCacheEnabled(true); //Bitmap bitmap = imageView.getDrawingCache(); int w = bm.getWidth(); int h = bm.getHeight(); int[] pix = new int[w * h]; bm.getPixels(pix, 0, w, 0, 0, w, h); long start_time = System.currentTimeMillis(); /* call jni api */ myjni.api_method =7; myjni.srcbuf = new int[w * h]; bm.getPixels(myjni.srcbuf, 0, w, 0, 0, w, h); myjni.img_width = w; myjni.img_height = h; Log.e(TAG, "111111111111111111a:" + myjni.api_method + " w:" + w + " h:" + h); //res = (Process)myjni.jniApiMethod(myjni); res =(Process)com.example.imageproc.Process.jniApiMethod(myjni); if (myjni.api_method == 0) { bccvVersion.setText(res.version); break; } else { Bitmap bm = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); bm.setPixels(res.dstbuf, 0, w, 0, 0, w, h); long end_time = System.currentTimeMillis(); long procss_time = end_time - start_time; imageView.setImageBitmap(bm); imageView.setDrawingCacheEnabled(false); Log.e(TAG, "process time:" + procss_time); processTime.setText("w:" + w + " h:" + h + " ProcessTime:" + procss_time + "ms"); bccvVersion.setText(res.version); break; } } } } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); /* get image path */ if (requestCode == IMAGE && resultCode == RESULT_OK && data != null) { Uri selectedImage = data.getData(); String[] filePathColumns = {MediaStore.Images.Media.DATA}; Cursor c = getContentResolver().query(selectedImage, filePathColumns, null, null, null); c.moveToFirst(); int columnIndex = c.getColumnIndex(filePathColumns[0]); String imagePath = c.getString(columnIndex); showImage(imagePath); c.close(); } } /* load image */ private void showImage(String imaePath){ bm = BitmapFactory.decodeFile(imaePath); ((ImageView)findViewById(R.id.imageView)).setImageBitmap(bm); }}
最终运行效果图如下:
备注:第一次接触Android平台,都是各种baidu完成的,很多Java上的术语不知道怎么说,只能以记流水的形式记录下整个实现过程,以备后期参考,各位看官勿喷。
感谢以下小伙伴的精华帖,
点击打开链接Android JNI 传递对象
JNI层与Java层结构体传递
Java中JNI的使用详解第五篇:C/C++中操作Java中的数组
Java与c++通过JNI的完美结合
Jni函数调用
[JNI]开发之旅
Android JNI和NDK学习(02)--静态方式实现JNI
Android JNI和NDK学习(03)--动态方式实现JNI
JNI 实战全面解析
更多相关文章
- Android画圆角矩形图片,并在图片上写字
- Android ProgressBar自定义图片进度,自定义渐变色进度条
- Android中使用imageviewswitcher 实现图片切换轮播导航的方法
- Android 获取网络视频某一帧图片
- Android关于图片内存计算
- Eclipse项目导入Android Studio,.9图片报错解决办法
- Android /system 目录解析
- Android工程下面没有gen目录