Android 上使用jni和opencv 实现边缘检测和直线检测

项目的github地址:https://github.com/lijialinneu/MyApplication

首先来看实验效果

OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测_第1张图片

这是在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。

OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测_第2张图片

(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中。复制后的目录结构如下图所示:

OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测_第3张图片

(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。

OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测_第4张图片


如果make报错,需要修改好错误。修改make时的错误是一个痛苦的过程,我一般借助android studio上的message和Gradle Console工具,同时通过自己打Log来分析代码中的错误。

OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测_第5张图片


这样核心的部分就写好了,下面开始编写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、真机调试

编写完上述的代码后,可以进行真机调试了。如果程序没有其他报错,就能看到下面的运行结果。

OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测_第6张图片

总结:

如果我们要在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,实现地图聚合、选点,展示文字介绍(实际图片有出入)
OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测_第7张图片

2、调用系统相机,添加图层拍照
OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测_第8张图片

3、实现一个简单的水印叠加效果,包含简单的滤镜
OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测_第9张图片

4、分享图片至微信等

拜拜~

OpenCV自学笔记31. Android 上使用jni和opencv 实现边缘检测和直线检测_第10张图片

更多相关文章

  1. 关于android的9path图片处理
  2. 性能优化——Android图片压缩与优化的几种方式
  3. Android积木之图片的生成和保存
  4. IDEA和Android Studio设置自定义背景图片
  5. android图片压缩工具类分享
  6. 『ANDROID』Android实现圆形的图片边角
  7. PC客户端与Android实现图片传送
  8. android_常用UI控件_02_EditText_01添加图片到edittext中

随机推荐

  1. android:gravity和android:layout_gravit
  2. android layout,xml属性介绍
  3. Android笔试(一)
  4. Android开发入门教程1-初试Android
  5. Android解析服务器端发来的xml数据示例
  6. android面试题总结
  7. Android绘图之2D绘图基础
  8. Mac上如何使用adb命令进行操作?(Android(安
  9. selector 及 Shape 小结
  10. Android蓝牙开发浅析