OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测
Android 上使用jni和opencv 实现边缘检测和直线检测
项目的github地址:https://github.com/lijialinneu/MyApplication
首先来看实验效果
这是在android上运行的一个小demo,使用真机调试,运行在红米Note3上。上面是原始图片,下面是边缘检测的结果图。这里的边缘检测使用OpenCV中的canny算法。
下面说明如何创建这样一个项目
分为以下几步:
- 1、配置ndk开发环境
- 2、写好make文件:Android.mk和Application.mk
- 3、写好canny.h、canny.cpp(c++)和java封装类
- 4、写好界面,包括MainActivity,activity_main.xml
名词解释:
sdk,即software development kit,Android的软件开发工具包。
ndk,即native develop kit,是google为了方便在android上使用原生代码语言而推出的开发工具。简单地说,ndk作用之一就是帮助开发者在android上使用c++开发程序。
jni,即java native interface,提供了API实现java和C/C++的交互。可以看做java程序和C/C++程序间的桥梁。
1、配置ndk开发环境
**(1)首先需要安装AndroidStudio,并新建一个空项目:MyApplication。**这一步可以参考:http://blog.csdn.net/liranke/article/details/49636927/
**(2)然后下载android-ndk-r10并解压,搭建ndk环境。**在Project Structure设置项中,可以配置sdk和ndk的位置。这里面的ndk就是刚刚下载好的android-ndk-r10。
(3)修改app的build.gradle:
这部分代码,是根据网上资料修改而来,某些配置项可以按照开发时的实际情况修改。
apply plugin: 'com.android.application'android { compileSdkVersion 25 buildToolsVersion "23.0.3" defaultConfig { applicationId "com.example.lijialin.myapplication" minSdkVersion 15 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" ndk { moduleName "OpenCV" //生成的so名字 } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } /** * 禁止自带的ndk功能 * @author 10405 * add on 2017-10-20 */ sourceSets.main.jni.srcDirs = [] sourceSets.main.jniLibs.srcDirs = ['src/main/libs', 'src/main/jniLibs'] //重定向so目录为src/main/libs和src/main/jniLibs,原来为src/main/jniLibs 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'}dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.+' compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha9' testCompile 'junit:junit:4.12'}
(4)从opencv官网下载opencv为android平台准备的开发工具:OpenCV-3.1.0-android-sdk,当然使用其他版本也可以。下载后解压,在路径OpenCV-3.1.0-android-sdk\OpenCV-android-sdk\sdk\下,有一个native文件。将native文件复制到MyApplication中。复制后的目录结构如下图所示:
(5)在app\src\main目录下,新建一个jni目录,在jni目录中,添加三个文件:
Android.mk,Application.mk,canny.cpp
其中,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 ..\..\..\..\native\jni\OpenCV.mk# 根据自己的路径修改include C:\Users\lijialin\AndroidStudioProjects\MyApplication\native\jni\OpenCV.mkelseinclude $(OPENCV_MK_PATH)endifLOCAL_MODULE := OpenCVLOCAL_SRC_FILES := canny.cpp#LOCAL_SRC_FILES += lsd.cpp #在这里可以添加其他cpp文件#LOCAL_LDLIBS += -lm -llogLOCAL_LDLIBS += -lloginclude $(BUILD_SHARED_LIBRARY)
Application.mk的内容如下:
APP_STL := gnustl_staticAPP_CPPFLAGS := -frtti -fexceptionsAPP_ABI := armeabiAPP_PLATFORM := android-8APP_OPIM := debug
(6)在app\src\main目录下,新建一个libs目录,我们编译后生成的libOpenCV.so会生存储在这个目录下。
2、编写canny.h,canny.cpp和Java封装类
canny.h是canny.cpp的头文件,功能只有一个,就是声明了native函数:
Java_com_example_lijialin_myapplication_OpenCVCanny_canny
- com_example_lijialin是包名
- myapplication是项目名
- OpenCVCanny是java封装类的类名
- canny是函数名
//// Created by lijialin on 2017/10/20.///* Header for class canny */#include#ifndef MYAPPLICATION_CANNY_H#define MYAPPLICATION_CANNY_H#ifdef __cplusplusextern "C" {#endif/* * Class: canny * Method: canny * Signature: ([III)[I */JNIEXPORT jintArray JNICALL Java_com_example_lijialin_myapplication_OpenCVCanny_canny (JNIEnv *, jclass, jintArray, jint, jint);#ifdef __cplusplus}#endif#endif
canny.cpp是我们实现边缘检测的核心程序,在这个程序中,首先将图像转为灰度图,然后调用高斯滤波对图像进行平滑去噪,最后使用canny边缘检测算法,提取出图像中的边缘。
canny.cpp
//// Created by lijialin on 2017/10/20.//#include "canny.h"#include #include #include #include #include #include #define LOG_TAG "asdf"#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG, __VA_ARGS__) // 定义LOGD类型,便于调试using namespace cv;using namespace std;extern "C" {// JNIEnv 代表java环境,通过这个参数可以调用java中的方法JNIEXPORT jintArray JNICALL Java_com_example_lijialin_myapplication_OpenCVCanny_canny(JNIEnv *env, jclass obj, jintArray buf, int w, int h);JNIEXPORT jintArray JNICALL Java_com_example_lijialin_myapplication_OpenCVCanny_canny(JNIEnv *env, jclass obj, jintArray buf, int w, int h) { jint *cbuf; cbuf = env->GetIntArrayElements(buf, false); // 读取输入参数 if (cbuf == NULL) { return 0; } Mat image(h, w, CV_8UC4, (unsigned char*) cbuf); // 初始化一个矩阵(图像)4通道的图像 cvtColor(image, image, COLOR_BGR2GRAY); // 转为灰度图 GaussianBlur(image, image, Size(5,5), 0, 0); // 高斯滤波 Canny(image, image, 50, 150, 3); // 边缘检测 // LSD 直线检测 Mat image2(image.size(), image.type()); // 用于绘制直线 Ptr<LineSegmentDetector> ls = createLineSegmentDetector(LSD_REFINE_STD, 0.80); vector<Vec4f> lines_std; ls->detect(image, lines_std); // LOGD("channels = %d", image.channels()); ls->drawSegments(image2, lines_std); cvtColor(image2, image2, COLOR_BGR2GRAY); // 此处要转为灰度图 // TODO 这里还可以添加其他的功能 // 构造返回结果 int* outImage = new int[w * h]; int n = 0; for(int i = 0; i < h; i++) { uchar* data = image.ptr<uchar>(i); // uchar* data = image2.ptr(i); // 如果是直线检测,就用image2 for(int j = 0; j < w; j++) { if(data[j] == 255) { outImage[n++] = 0; }else { outImage[n++] = -1; } } } int size = w * h; jintArray result = env->NewIntArray(size); env->SetIntArrayRegion(result, 0, size, outImage); env->ReleaseIntArrayElements(buf, cbuf, 0); return result;}}
在写好canny.cpp后,还需要在外面封装一层java代码,定义native方法以便调用本地的C/C++代码。
OpenCVCanny.java:
package com.example.lijialin.myapplication;/** * Created by lijialin on 2017/10/20. */public class OpenCVCanny { static { System.loadLibrary("OpenCV"); // 加载编译好的.so动态库 } /** * 声明native方法,调用OpenCV的边缘检测 * * @param buf 图像 * @param w 宽 * @param h 高 * @return 边缘图 */ public static native int[] canny(int[] buf, int w, int h);}
如果我们此时编译,也就是make,可以看到在libs目录下生成了libOpenCV.so。
如果make报错,需要修改好错误。修改make时的错误是一个痛苦的过程,我一般借助android studio上的message和Gradle Console工具,同时通过自己打Log来分析代码中的错误。
这样核心的部分就写好了,下面开始编写android的界面。
3、编写android界面
app只有一个界面,只需把MainActivity写好就可以了。在MainActivity中,程序首先从资源文件中获取了building图像,然后调用上一步封装好的OpenCVCanny.canny()方法,并传递相应的参数进去。canny()方法会使用native方法,也就是canny.cpp中的Java_com_example_lijialin_myapplication_OpenCVCanny_canny函数,实现边缘检测的功能。
MainActivity.java
package com.example.lijialin.myapplication;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import android.widget.ImageView;public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 获取图片,使用边缘检测提取图像的边缘 Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.building); int width = bitmap.getWidth(); int height = bitmap.getHeight(); int[] pix = new int[width * height]; bitmap.getPixels(pix, 0, width, 0, 0, width, height); int[] resultPixels = OpenCVCanny.canny(pix, width, height); // 调用canny方法 Bitmap resultBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444); // 创建一个4通道的图像 resultBitmap.setPixels(resultPixels, 0, width, 0, 0, width, height); // 将边缘图显示出来 ImageView view = (ImageView) findViewById(R.id.resultView); view.setImageBitmap(resultBitmap); view.setVisibility(View.VISIBLE); }}
界面:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
4、真机调试
编写完上述的代码后,可以进行真机调试了。如果程序没有其他报错,就能看到下面的运行结果。
总结:
如果我们要在android上使用opencv,除了jni外,还可以直接使用opencv提供的java api,但是对于实时性要求较高的系统,不建议直接使用java api。
大致思路就是这样,项目的github地址:https://github.com/lijialinneu/MyApplication,欢迎下载。(因为android版本的关系,不保证适配所有机型)
补充:DreameraJni小项目
求学时期写过一个小项目,主要用到了上问提及的jni+opencv技术,不过功能更加强大,还可以实现图片的水印效果。代码年久失修,已经不能正常运行了,但是一些解决问题的思路,还是可以参考借鉴的。
项目地址:
https://gitee.com/lijialin/DreameraJni
主要有以下几个功能:
1、使用百度地图api,实现地图聚合、选点,展示文字介绍(实际图片有出入)
2、调用系统相机,添加图层拍照
3、实现一个简单的水印叠加效果,包含简单的滤镜
4、分享图片至微信等
拜拜~
更多相关文章
- 关于android的9path图片处理
- 性能优化——Android图片压缩与优化的几种方式
- Android积木之图片的生成和保存
- IDEA和Android Studio设置自定义背景图片
- android图片压缩工具类分享
- 『ANDROID』Android实现圆形的图片边角
- PC客户端与Android实现图片传送
- android_常用UI控件_02_EditText_01添加图片到edittext中