目录:

  1,过程感慨;

  2,运行环境;

  3,准备工作;

  4,编译 .so

  5,遇到的关键问题及其解决方法

  6,实现效果截图。

(原创:转载声明出处:http://www.cnblogs.com/linguanh/)

1,过程感慨(想直接看教程,请跳过此部分)

在写具体内容之前,我先说下我搞这个东西的过程,由于导师之前说过要搞个图像匹配的androi APP,具体就是匹配前后两张图片的相似度,类似 安卓5.0 引入的刷脸解锁。

当时觉得,要实现这样一个东西,肯定没现成的API 可供使用,第一时间想到的 无疑就是opencv,这个拥有一套强大的图像处理函数的库,它的开发语言主要是C++,但是,也有 jar 包可供android开发使用,如果单单是使用里面已经写好了的效果的话,肯定是不能完成图像匹配的。

也就是说,我必须要调用它里面的函数再结合自己算法重新去实现这样一个功能,再使用 ndk 环境去实现 jni 编程,把我自己写好的 c++ 代码,在生成 .so 动态库的基础上,引入并使用。

刚开始,思路很清晰,然后便着手百度 android studio(下面简称 as) 的 opencv jni编程使用教程,十分遗憾,所能搜到的,关于 as 和 opencv、jni 搭边的例子 几乎为0,很多的例子是 eclipse。没办法,只有自己亲手搞了。

刚动手的时候,很快地把所有装备工作都搞定了,.so 动态库文件(下面会介绍)也编译出来了,但是,就在此时,我遇到了一个 令我第一阶段切底放弃的 bug!!

这个 bug 是:(下面我会说明白,它的真实起因和解决方法)

fatal error: opencv2/opencv.hpp: No such file or directory, 意思是 我所要编译的 cpp文件中的 头文件 opencv2/opencv.hpp 找不到。当时,无论是自己请教别人、百度、google 还是查书,都无法解决,足足耗时 一星期!!

逐保留项目信息,放弃不搞。

直到 2 天前,开始决定重新尝试,并于今天正式解决后,现发表此文。

2,运行环境

win 7, 系统;

android studio 版本 0.8.0 beta,使用  build:gradle:0.12.+,tools版本:21.1.2,api 21;

opencv for android 包,我使用的版本是 OpenCV-3.0.0-android-sdk,2.4.9的也可以,可以到 opencv 官网下载,我这里提供个链接

http://downloads.sourceforge.net/project/opencvlibrary/opencv-android/3.0.0/OpenCV-3.0.0-android-sdk-1.zip?r=http%3A%2F%2Fopencv.org%2F&ts=1436167636&use_mirror=nchc

编译.so 动态库 使用 cygwin,安装了所有包,这里提示,不一定要用它,可以直接使用 cmd 进行编译;

ndk 为 android-ndk-r10d(强烈建议使用 r9 或 r10 系列,因为这两个能在 cmd 中编译出 .so),r10d 能够支持的 android api 最高到 21,如果你的是 22 的请修改,否则会有会编译不出 jni.h 头文件,或者其他的头文件,你会发现,别人的源码在你这编译不出了。

3,准备工作

,---ndk 的下载、安装和配置,此部分不说,网上教程很多,很多可行。

,---cygwin 的下载和安装, 参照 http://blog.csdn.net/asmcvc/article/details/9311573,我上面说了,不一定要用它,win 自带的 cmd 也可以编译。如果使用 cygwin,要做好心理准备,下载和安装它,非常非常的久,文件总体积 20 多G!!!!我是用了9个多小时。

,---opencv for android 的sdk 下载完成后。打开 该文件夹,sdk/native/libs,里面有很多平台的文件夹,能在里面出现的,证明你能够在下面的 Application.mk 中设置生成对应的架构的 .so文件,我举个例子,我的是:

在下面介绍的 Application.mk 文件中有一句话 ,它是用来设置生成 对应架构的 .so 文件,我这里是armeabi-7a,如果要生所有的,写出 :=all,注意,这样很可能会报错,错误信息是,某种架构找不到,所以,我要你看清楚,上面文件夹里面有哪些架构,这些 坑是网上找不到,如果你要生成两种,可以轮着来编译,第二次的编译,不同的架构是不会覆盖的。现在打开 sdk/native/jni,如无意外,里面肯定有个 文件叫做 OpenCV.mk,它就是我们在 android.mk 脚本文件中要引入 opencv C++库所要参照的文件。请用记事本 或者Notepad++ 打开。

,---了解 Android.mk 和 Application.mk 文件的基本内容信息:下面我使用默认的 Android.mk 来说明,和我的例子的 Application.mk 来说明。

它们都是脚本文件。

Android.mk 

Application.mk

4,编译 .so

使用你的 as 创建一个新项目,然后在你的 项目的 main 目录下创建一个一个 jni 文件夹,这样创建:

创建好了之后,是这样的:

   首先编译 项目的头文件 .h,一般编译出来后,它的名字结构是:包名_类名.h

  编译命令如下,请在你的 as 下面的 Terminal 里面输入:

    SourcePath:    D:\work\androidstudio\VisualRecognition\app\src\main\java (绝对路径)
    TargetPath:    D:\work\androidstudio\VisualRecognition\visual\src\main\jni (绝对路径)
    TargetClassName:    com.yf.visualrecognition.UnityPlayerActivity  (你的包名+类名)
    格式:  javah -d ${SourceFile} -classpath ${TargetPath} ${TargetClassName}
    控制台指令:javah -d D:\work\androidstudio\VisualRecognition\visual\src\main\jni  -classpath                  D:\work\androidstudio\VisualRecognition\app\src\main\java io.github.froger.jni.MyActivity
 

然后在你的jni 文件夹下面 分别创建 Android.mk 、Application.mk 和你要编译的 .cpp 或者.c 文件,前两个的 内容可以模仿我上面介绍的, .cpp 我这里提供一个。

  Android.mk 、Application.mk 、ImgFuncpp 分别如下,util.c 是空文件,之所以创建它是为了避免另外一个 bug,这不说:

Android.mk 文件如下

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
OPENCV_LIB_TYPE:=STATIC
ifeq ("$(wildcard $(OPENCV_MK_PATH))","") include E:\OpenCV-3.0.-android-sdk-\OpenCV-android-sdk\sdk\native\jni\OpenCV.mk
else
include $(OPENCV_MK_PATH)
endif LOCAL_MODULE := ImgFun
LOCAL_SRC_FILES := ImgFun.cpp
LOCAL_LDLIBS += -lm -llog
include $(BUILD_SHARED_LIBRARY)

Application.mk 文件如下

APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
APP_ABI := armeabi-v7a #这句是设置生成的cpu指令类型,提示,目前绝大部分安卓手机支持armeabi,libs下太多类型,编译进去 apk 包会过大
APP_PLATFORM := android- #这句是设置最低安卓平台,可以不弄

ImgFun.cpp 文件如下

 #include <io_github_froger_jni_MyActivity.h>
#include <stdio.h>
#include <stdlib.h>
#include <opencv2/opencv.hpp>
using namespace cv;
IplImage * change4channelTo3InIplImage(IplImage * src); extern "C" {
JNIEXPORT jintArray JNICALL Java_io_github_froger_jni_MyActivity_ImgFun(
JNIEnv* env, jobject obj, jintArray buf, int w, int h);
JNIEXPORT jintArray JNICALL Java_io_github_froger_jni_MyActivity_ImgFun(
JNIEnv* env, jobject obj, jintArray buf, int w, int h) { jint *cbuf;
cbuf = env->GetIntArrayElements(buf, false);
if (cbuf == NULL) {
return ;
} Mat myimg(h, w, CV_8UC4, (unsigned char*) cbuf);
IplImage image=IplImage(myimg);
IplImage* image3channel = change4channelTo3InIplImage(&image); IplImage* pCannyImage=cvCreateImage(cvGetSize(image3channel),IPL_DEPTH_8U,); cvCanny(image3channel,pCannyImage,,,); int* outImage=new int[w*h];
for(int i=;i<w*h;i++)
{
outImage[i]=(int)pCannyImage->imageData[i];
} int size = w * h;
jintArray result = env->NewIntArray(size);
env->SetIntArrayRegion(result, , size, outImage);
env->ReleaseIntArrayElements(buf, cbuf, );
return result;
}
} IplImage * change4channelTo3InIplImage(IplImage * src) {
if (src->nChannels != ) {
return NULL;
} IplImage * destImg = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, );
for (int row = ; row < src->height; row++) {
for (int col = ; col < src->width; col++) {
CvScalar s = cvGet2D(src, row, col);
cvSet2D(destImg, row, col, s);
}
} return destImg;
}

  上面 .cpp 文件的有几句话要说明下,注意 .c 文件和 .cpp 文件是不一样的:

  1,请用  extern "C" { } 包住 你要你的 c++ 函数体的定义和里面的变量,函数声明可以在外面。

  2,JNIEXPORT jintArray JNICALL Java_io_github_froger_jni_MyActivity_ImgFun(JNIEnv* env, jobject obj, jintArray buf, int w, int h);

  3,jintArray 是你定义的函数的返回值,我这里的是int数组,它在类型的前面有一个 j ,如果是字符串,那么就是 jstring,数组加上Array;

  4,JNICALL Java 这句不变,所有都一样,注意java的 j 是大写;

  5,io_github_froger_jni 这里是你的包名;

  6,MyActivity 你的类名,要引用这个这里C++函数的类名;

  7,ImgFun  是你要在java中调用的函数名字,哪些不用直接被调用的,不用写;

  8,JNIEnv* env, jobject obj, 这个固定不变,第一个的意思是虚拟机引用,第二个是项目;

  9,jintArray buf, int w, int h 函数的参数。

  好了,上面该介绍的已经介绍完了,接下来是编译 .so 的正式操作(我这里使用cmd做例子,因为它更简单操作,cygwin也可以)。

你可以在 as 的 cmd 中或者 系统的 cmd框中实现编译,首先使用命令进入到当前的 jni 文件夹的 目录,例如,我的是

D:asproject/JniDemo/app/main/jni,然后使用命令 ndk-build,(使用ndk-build命令这一步,需要你已经配置好了 ndk 环境,请参照百度上面的教程)然后回车,如无意外,将会生成如下文件:

其中的 .so 文件就是我们所需要的,现在打开你项目app下的  build.gradle 文件,在 android{} 里面加入:

sourceSets {
  main() {
    jniLibs.srcDirs = ['src/main/libs']
  }
}

这样是为了使用 .so文件,上面我们仅仅是生产!

  OK,到这里基本大功告成了,不过,笔者我就是在这一步之后,运行程序的时候,出现的简单的致命的 bug,导致我找了近2星期,现在想起来真是蠢..............

5,遇到的关键问题及其解决方法

  运行程序,出现,如下错误,这里声明下,不仅仅是 opencv2/opencv.hpp,还可能是其他的 hpp。

  出现的原因:

原来是这样的,android studio 在我们编译完 .so 文件后,我们在Android.mk 文件中设置引入的opencv 函数库,是已经被编译进去.so 动态库里面了的,而我们编译所需要的 cpp 文件,它在 jni 文件夹呢,自然就没有 opencv 库可依赖,所以。

  解决方法:

在你编译完.so 文件后,就可以把 cpp 或者 c 文件里面的内容 注释或者删除了,不然在你运行程序的时候就会抛出头文件找不到的错误,哎,真是辛酸泪,这样一个 bug 搞了我 那么多时间,不过还好,还是解决了。

6,实现效果截图

 package io.github.froger.jni;

 import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView; public class MyActivity extends Activity {
/** Called when the activity is first created. */
ImageView imgView;
Button btnNDK, btnRestore;
public static native int[] ImgFun(int[] buf, int w, int h);
static {
System.loadLibrary("ImgFun");
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my); this.setTitle("使用NDK转换灰度图");
btnRestore = (Button) this.findViewById(R.id.btnRestore);
//btnRestore.setText(ImgFun());
btnRestore.setOnClickListener(new ClickEvent());
btnNDK = (Button) this.findViewById(R.id.btnNDK);
btnNDK.setOnClickListener(new ClickEvent());
imgView = (ImageView) this.findViewById(R.id.ImageView01);
Bitmap img = ((BitmapDrawable) getResources().getDrawable(
R.drawable.ic_launcher)).getBitmap();
imgView.setImageBitmap(img);
} class ClickEvent implements View.OnClickListener {
public void onClick(View v) {
//btnRestore.setText(ImgFun());
if (v == btnNDK) {
long current = System.currentTimeMillis();
Bitmap img1 = ((BitmapDrawable) getResources().getDrawable(
R.drawable.ic_launcher)).getBitmap();
int w = img1.getWidth(), h = img1.getHeight();
int[] pix = new int[w * h];
img1.getPixels(pix, 0, w, 0, 0, w, h);
int[] resultInt = ImgFun(pix, w, h);
Bitmap resultImg = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565);
resultImg.setPixels(resultInt, 0, w, 0, 0, w, h);
long performance = System.currentTimeMillis() - current;
imgView.setImageBitmap(resultImg);
} else if (v == btnRestore) {
Bitmap img2 = ((BitmapDrawable) getResources().getDrawable(
R.drawable.ic_launcher)).getBitmap();
imgView.setImageBitmap(img2);
}
}
} }

 

android studio 使用 jni 编译 opencv 完整实例 之 图像边缘检测!从此在andrid中自由使用 图像匹配、识别、检测的更多相关文章

  1. android studio 使用 jni 编译 opencv 完整实例 之 图像边缘检测!

    目录: 1,过程感慨: 2,运行环境: 3,准备工作: 4,编译 .so 5,遇到的关键问题及其解决方法 6,实现效果截图. ------------------------------------- ...

  2. Android studio 下JNI编程实例并生成so库

    Android studio 下JNI编程实例并生成so库 因为公司需要为Android相机做美颜等图像后期处理,需要使用JNI编程,最近学了下JNI,并且在Android Studio下实现了一个小 ...

  3. JNI 在Android Studio利用NDK编译运行一个简单的c库

    NDK开发,其实是为了项目需要调用底层的一些C/C++的一些东西:另外就是为了效率更加高些.如果你在Eclipse+ADT下开发过NDK就能体会到要么是配置NDK还要下载Cygwin,配置Cygwin ...

  4. Android studio 配置JNI环境

    Android studio配置jni开发环境,主要配置是两个build文件,以及新建一个jni文件,放c代码. 代码如下1: apply plugin: 'com.android.model.app ...

  5. Android Studio下jni应用

    最近在将一个小应用从eclipse开发迁移到android studio,程序中有native代码实现,在eclipse是靠Android.mk这么个mk文件来组织编译的,但到android stud ...

  6. studio_ 优化Android Studio 启动、编译和运行速度?

    http://www.admin10000.com/document/6842.html: 作为一名 Android 程序员,选择一个好的 IDE 工具可以使开发变得非常高效,很多程序员喜欢使用 Go ...

  7. 如何优化 Android Studio 启动、编译和运行速度?

    作为一名 Android 程序员,选择一个好的 IDE 工具可以使开发变得非常高效,很多程序员喜欢使用 Google 的 Android Studio来进行开发,但使用起来有时会出现卡顿等问题.本文介 ...

  8. 【转】实践最有效的提高Android Studio运行、编译速度方案

    原文:https://blog.csdn.net/xwh_1230/article/details/60961723 实践最有效的提高Android Studio运行.编译速度方案 最有效提升Andr ...

  9. Android studio 下 JNI 开发实例

    在AS中进行 NDK 开发之前,我们先来简单的介绍几个大家都容易搞懵的概念: 到底什么是JNI,什么是NDK? 何为“交叉编译”? 先看什么是 JNI?JNI 的全称就是 Java Native In ...

随机推荐

  1. RPC 使用中的一些注意点

    最近线上碰到一点小问题,分析其原因发现是出在对 RPC 使用上的一些细节掌握不够清晰导致.很多时候我们做业务开发会把 RPC 当作黑盒机制来使用,但若不对黑盒的工作原理有个基本掌握,也容易犯一些误用的 ...

  2. C#与C++的发展历程第三 - C#5.0异步编程巅峰

    系列文章目录 1. C#与C++的发展历程第一 - 由C#3.0起 2. C#与C++的发展历程第二 - C#4.0再接再厉 3. C#与C++的发展历程第三 - C#5.0异步编程的巅峰 C#5.0 ...

  3. [版本控制之道] Git 常用的命令总结(欢迎收藏备用)

    坚持每天学习,坚持每天复习,技术永远学不完,自己永远要前进 总结日常开发生产中常用的Git版本控制命令 ------------------------------main-------------- ...

  4. Javascript实用方法二

    承接上一篇, Object keys object的keys方法能够获取一个给定对象的所有键(key/属性名)并以数组的形式返回.这个方法可以用于键的筛选.匹配等. var basket = { st ...

  5. OpenCV模板匹配算法详解

    1 理论介绍 模板匹配是在一幅图像中寻找一个特定目标的方法之一,这种方法的原理非常简单,遍历图像中的每一个可能的位置,比较各处与模板是否“相似”,当相似度足够高时,就认为找到了我们的目标.OpenCV ...

  6. 浅谈Web自适应

    前言 随着移动设备的普及,移动web在前端工程师们的工作中占有越来越重要的位置.移动设备更新速度频繁,手机厂商繁多,导致的问题是每一台机器的屏幕宽度和分辨率不一样.这给我们在编写前端界面时增加了困难, ...

  7. Javascript高级技巧

    上次整理了Ajax部分,这周看完了高级技巧部分,也整理下吧. 1.类型检测 使用Object.prototype.toString.call(obj)的方式. 因为无论typeof还是instance ...

  8. Lesson 18 He often does this!

    Text After I had had lunch at a village pub, I looked for my bag. I had left it on a chair beside th ...

  9. linux用户权限相关内容查看

    linux用户权限相关内容查看 1   用户信息 创建用户一个名为 webuser 的账号,并填写相应的信息: root@iZ94fabhqhuZ:~# adduser webuser Adding ...

  10. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...