项目的github地址:https://github.com/lijialinneu/MyApplication
这是在android上运行的一个小demo,使用真机调试,运行在红米Note3上。上面是原始图片,下面是边缘检测的结果图。这里的边缘检测使用OpenCV中的canny算法。
分为以下几步:
名词解释:
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)首先需要安装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 := on
OpenCV_CAMERA_MODULES := off
OPENCV_LIB_TYPE :=STATIC
ifeq ("$(wildcard $(OPENCV_MK_PATH))","")
#include ..\..\..\..\native\jni\OpenCV.mk
# 根据自己的路径修改
include C:\Users\lijialin\AndroidStudioProjects\MyApplication\native\jni\OpenCV.mk
else
include $(OPENCV_MK_PATH)
endif
LOCAL_MODULE := OpenCV
LOCAL_SRC_FILES := canny.cpp
#LOCAL_SRC_FILES += lsd.cpp #在这里可以添加其他cpp文件
#LOCAL_LDLIBS += -lm -llog
LOCAL_LDLIBS += -llog
include $(BUILD_SHARED_LIBRARY)
Application.mk的内容如下:
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi
APP_PLATFORM := android-8
APP_OPIM := debug
(6)在app\src\main目录下,新建一个libs目录,我们编译后生成的libOpenCV.so会生存储在这个目录下。
canny.h是canny.cpp的头文件,功能只有一个,就是声明了native函数:
Java_com_example_lijialin_myapplication_OpenCVCanny_canny
//
// Created by lijialin on 2017/10/20.
//
/* Header for class canny */
#include
#ifndef MYAPPLICATION_CANNY_H
#define MYAPPLICATION_CANNY_H
#ifdef __cplusplus
extern "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的界面。
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
编写完上述的代码后,可以进行真机调试了。如果程序没有其他报错,就能看到下面的运行结果。
总结:
如果我们要在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,实现地图聚合、选点,展示文字介绍(实际图片有出入)
4、分享图片至微信等
拜拜~