Android中使用OpenCV(实用教程)

利用jni将Eclipse与C++、Opencv无缝连接

博主:w风的季节

日期:2018/4/11

 

现在越来越多的大学生实验室或者创新项目需要用到将JAVA与C++之间的混合编程,因此本篇就来讲解一下Eclipse中用java写的APP如何将C++算法程序无缝融入进去。

本篇博客主要是针对初学Ecilpse的博友们,从配置JAVA环境变量开始讲起,最终实现可以开发一个将用C++编写的图像处理程序于java编写的程序链接起来,最终在Android上开发出APP。

写本篇文章的目的是主要是因为小编刚跟老师做完一个大学生创新创业项目,而且主要工作是将C++编写的图像处理程序一直到Android中,并根据这个来开发一个小型APP。经过大约不到一年的实践与学习,项目成果还不错,因此特来把我在做过程中遇到的问题以及Eclipse配置jni的方法详细给大家讲述一下,以免大家少走一些弯路(小编就在此之前被折磨惨了,整天看不完的资料,搜各种花式问题)。

本文写的图像处理函数比较简单,主要是为了给初学者留一点自己理解的时间,只用到Opencv函数库中的几个函数,后续等小编有时间,继续给大家编写更难一点,对工程项目有实际作用的C++程序嵌入。

本篇文章主要是借鉴这位博主的文章,下面是这位博主发表的文章链接,大家也可以去参考一下: https://blog.csdn.net/watkinsong/article/details/9849973

 

下面是本篇文章讲解所用的的全部资源包(本文全部都要用到),主要包括:

(百度网盘)

 

jdk-jre.zip :

https://pan.baidu.com/s/10sIvW-D1rxUnUmhdP1wR-Q

密码:ziu0

ADT-23.0.7.zip:

https://pan.baidu.com/s/1AJqzSW09O19snQhcJAQFZw

密码:atps

eclipse-jee-oxygen-3-win32-x86_64.zip:

https://pan.baidu.com/s/1EV6GAWfqptBayEEvOycYTA

密码:4bif

android-ndk64-r10-windows-x86_64.zip:

https://pan.baidu.com/s/1tgnIQGoQmpAu80l2p27wfw

密码:i9i2

android-sdk_r24.4.1-windows.zip

https://pan.baidu.com/s/1Rrv7HG-tHsMNcP85ROqTzA

密码:pph1

opencv-3.4.1-android-sdk.zip

https://pan.baidu.com/s/1NInNkBnqwtyqcVWA2nFcyw

密码:lgds

 

以下是本篇文章的大体方向:

1、             安装jdk-8u162(包含jre),配置java环境变量;

2、             安装Eclipse;

3、             在Eclipse中离线安装ADT;

4、             下载并安装SDK;

5、             新建Android工程;

6、             配置Android NDK环境;

7、             将C++编译为动态链接库(.so库);

8、             调用.so库函数,实现功能


一、配置java环境变量

1、安装jdkjre

1.1从上面提供的资源包中,解压jdk-jre压缩包到当前文件夹下:

 

1.2、从解压好的文件夹下找到jdk-8u162-windows-x64.exe双击运行,点击下一步如下图所示:注意红线区为保存的路径,可以更改。

 

  


 


  

2Java环境变量配置

 2.1、右击 计算机→属性→高级系统设置→高级→环境变量


 

2.2、系统变量→新建 JAVA_HOME 变量,变量值是你安装jdk时文件夹,与下有图的路径一致,不要配置错了,如下两图所示:






 

2.3、系统变量寻找 Path 变量编辑,新建两个分别输入:

%JAVA_HOME%\bin

%JAVA_HOME%\jre\bin



2.4、系统变量新建 CLASSPATH 变量

变量值填写  .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar(注意最前面有一点)

系统变量配置完毕,如下图所示:

 


2.5、检验是否配置成功运行cmd输入javac或者java –version(注意有空格)

若如图所示显示版本信息则说明安装和配置成功:


二、安装eclipse

2.1、如下图,解压到当前文件夹



2.2、打开解压后的文件夹,双击运行eclipse.exe应用程序



2.3、运行后,会出现一下界面,你需要找一个保存工程的文件夹(最好与应用程序在同一文件夹下),并勾选(以后默认工程在此文件夹下),如图所示:


三、离线安装ADT

3.1、打开Eclipse,点击Help ->Install New Software



3.2、之后到如下界面点击Add,按照下图所示操作,然后点击Archive,添加ADT压缩包ADT-23.0.7.zip(此压缩包均在本文开头所给的文件夹下,需要博友们先下载下来):

 

 



3.3、点击ok之后便可以到一下界面,然后经过Pending解析后,就可以看到对应的:Developer Tools了:

 


3.4等待下载完之后可以看到如下界面:




 

3.5不要急,等待一小会,中间会有安全提示,点击Installanyway即可:



3.6、点击Restart Now重启Eclipse



3.7重启Eclipse之后,可能会出现这个窗口,可以点击红框内,也可以点击Close,最好点击Close,因为这个安装SDK的步骤会在下文再解释:



3.8、至此,离线安装ADT就完成了。另外,你也可以在工具栏中看到对应的Android小图标,这个是安装SDK用的Android SDK Manager


四、Android SDK安装

4.1、打开资源包,解压图示所画的压缩包,(小编这里是已经解压好的)

 


4.2、打开解压后的文件夹,并双击运行SDKManager.exe

 

 

4.3Tools的勾选图示选项下载即可:



4.4API的选择三个,考虑向下兼容原则,可以下载最新的API,这里选用API27,API26,API22

Android 8.1.0 Android8.0.0


 

Android5.1.1



4.5Extras的选择这几个(有时间和内存的话,最好这个全部都下载)



4.6、点击红线区开始下载,时间比较慢,建议晚上下载:





注:无法下载的会出现以下提示



此时采用有Android SDK在线更新镜像服务器来下载安装:

北京化工大学镜像服务器地址:

IPv4: ubuntu.buct.edu.cn/ 端口:80

IPv4: ubuntu.buct.cn/ 端口:80

IPv6: ubuntu.buct6.edu.cn/ 端口:80

大连东软信息学院镜像服务器地址: //一般用这个服务器比较快

mirrors.neusoft.edu.cn 端口:80

郑州大学开源镜像站:

mirrors.zzu.edu.cn 端口:80

使用方法:

启动 Android SDK Manager,打开主界面,依次选择『Tools』、『Options...』,弹出『Android SDK Manager - Settings』窗口;

 

在『Android SDK Manager - Settings』窗口中,在『HTTP ProxyServer』和『HTTP Proxy Port』输入框内填入上面镜像服务器地址(不包含http://,如下图)和端口,并且选中『Force https://... sources to be fetched using http://...』复选框。设置完成后单击『Close』按钮关闭『AndroidSDK Manager - Settings』窗口返回到主界面;

依次选择『Packages』、『Reload



等待安装完成后,需要将SDK的文件安装路径配置到Eclipse上,单击“Browse…”按钮,选择你的SDK安装路径,添加好就行啦~如下图:

 


五、新建Android工程

5.1、打开eclipse,在Eclipse里,选择菜单项File—>New—>Project新建一个项目,我们就能看到建立Android项目的选项了:





5.2、接下来是选择API,重点,前往别选择错!!!!!

  

 






 


到此为止,新建Android工程,完毕。

 

六、配置AndroidNDK环境

6.1、在本文的资源包里面包含android-ndk64-r10-windows-x86_64.zip,解压到当前文件夹即可,如下图:



6.2、打开eclipse Window—>Prefernces—>Android—>NDK选择你NDKLocation的存放路径:


 

七、将该项目转换到C++项目

7.1、首先需要将该项目转换到C++项目,使得该项目具有C++代码属性,如下所述。
点击项目,右击,New -> Other -> C/C++ -> Convert to a C/C++ Project.





     

7.2、配置Eclipsecpp代码进行编译:
首先需要给当前项目添加一个编译环境变量
如下目录Eclipse ->Window -> Preferences -> C/C++ -> Build ->Environment,点击Add... 
添加一个NDKROOT,并且设置值为NDK的根目录,如下图:




7.3、在工程中设置编译的一些参数
右击你新建的工程,找到Properties,并按照顺序 Properties -> C/C++ Build, uncheck Use default build command,replace “Build command” text from "make" to
"${NDKROOT}/ndk-build.cmd" on Windows,
"${NDKROOT}/ndk-build" on Linux and MacOS.
如下图:




7.4、然后修改Behaviour选项卡,设置编译配置(配置在保存代码的时候进行自动编译)



7.5在此之后出现这种情况,不要着急,我们接着开始写C++程序,并编译成动态链接库;



八、在Android中使用Opencv

8.1、安卓java代码

8.1.1MainActivity函数代码如下:

 


     
     
  1. package com.Rng.freescale;
  2. import android.app.Activity;
  3. import android.graphics.Bitmap;
  4. import android.graphics.Bitmap.Config;
  5. importandroid.graphics.drawable.BitmapDrawable;
  6. import android.os.Bundle;
  7. import android.view.Menu;
  8. import android.view.MenuItem;
  9. import android.view.View;
  10. import android.widget.Button;
  11. import android.widget.ImageView;
  12. public class MainActivity extendsActivity {
  13. ImageView imgView;
  14. Button btnNDK, btnRestore;
  15. @Override
  16. protected voidonCreate(Bundle savedInstanceState) {
  17. super.onCreate(savedInstanceState);
  18. setContentView(R.layout.activity_main);
  19. this.setTitle( "使用NDK转换灰度图");
  20. btnRestore = (Button) this.findViewById(R.id.btnRestore);
  21. btnRestore.setOnClickListener(newClickEvent());
  22. btnNDK = (Button) this.findViewById(R.id.btnNDK);
  23. btnNDK.setOnClickListener(newClickEvent());
  24. imgView = (ImageView) this.findViewById(R.id.ImageView01);
  25. @SuppressWarnings( "deprecation")
  26. Bitmap img =((BitmapDrawable) getResources().getDrawable(
  27. R.drawable.we)).getBitmap();
  28. imgView.setImageBitmap(img);
  29. }
  30. class ClickEvent implementsView.OnClickListener {
  31. public void onClick(Viewv) {
  32. if (v == btnNDK) {
  33. long current = System.currentTimeMillis();
  34. @SuppressWarnings( "deprecation")
  35. Bitmapimg1 = ((BitmapDrawable) getResources().getDrawable(
  36. R.drawable.we)).getBitmap();
  37. int w = img1.getWidth(),h = img1.getHeight();
  38. int[] pix = new int[w* h];
  39. img1.getPixels(pix, 0, w, 0, 0,w, h);
  40. int[] resultInt =LibImgFun.ImgFun(pix, w, h);
  41. Bitmap resultImg = Bitmap.createBitmap(w,h, Config.RGB_565);
  42. resultImg.setPixels(resultInt, 0, w, 0, 0, w, h);
  43. long performance =System.currentTimeMillis() - current;
  44. imgView.setImageBitmap(resultImg);
  45. MainActivity. this.setTitle( "w:"+ String.valueOf(img1.getWidth())
  46. + ",h:" +String.valueOf(img1.getHeight()) + "NDK耗时"
  47. + String.valueOf(performance)+ " 毫秒");
  48. } else if (v ==btnRestore) {
  49. @SuppressWarnings( "deprecation")
  50. Bitmapimg2 = ((BitmapDrawable) getResources().getDrawable(
  51. R.drawable.we)).getBitmap();
  52. imgView.setImageBitmap(img2);
  53. MainActivity. this.setTitle( "使用OpenCV进行图像处理");
  54. }
  55. }
  56. }
  57. }



8.1.2、在文件夹src下的com.testopencv.haveimgfun包中新建一个类用于包装使用了opencv c++代码的动态库的导出函数,类名为LibImgFunEclipse会为你创建一个新的文件LibImgFun.java,我们将里面的内容改为:

 


    
    
  1. packagecom.Rng.freescale;
  2. publicclass LibImgFun {
  3. static {
  4. System.loadLibrary( "ImgFun");
  5. }
  6. publicstatic native int[] ImgFun( int[] buf, int w, int h);
  7. }

 

从上面的代码可以得知,我们的动态库名字应该为“libImgFun.so”,注意"public static native int[] ImgFun(int[] buf, int w, inth)"中的native关键字,表明这个函数来自native codestatic表示这是一个静态函数,这样就可以直接用类名去调用。


8.1.3、然后修改界面控制文件res->layout->activity_main.xml



    
    
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. xmlns:tools= "http://schemas.android.com/tools"
  3. android:orientation= "vertical"
  4. android:layout_width= "match_parent"
  5. android:layout_height= "match_parent"
  6. tools:context= "com.Rng.freescale.MainActivity" >
  7. <Button android:layout_height="wrap_content"
  8. android:layout_width= "fill_parent"
  9. android:id= "@+id/btnNDK"
  10. android:text= "使用C++ OpenCV进行处理" />
  11. <Button android:layout_height="wrap_content"
  12. android:layout_width= "fill_parent"
  13. android:id= "@+id/btnRestore"
  14. android:text= "还原" />
  15. <ImageView android:id="@+id/ImageView01"
  16. android:layout_width= "fill_parent"
  17. android:layout_height= "fill_parent" />
  18. </LinearLayout>

文件图如下:

 


8.2 C++代码

在项目中新建一个jni文件,用于放置该项目的所有cpp代码。在jni文件夹下建立一个"ImgFun.cpp"的文件,内容改为下面所示:



    
    
  1. #include <jni.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <opencv2/opencv.hpp>
  5. using namespace cv;
  6. IplImage * change4channelTo3InIplImage(IplImage * src);
  7. extern "C" {
  8. JNIEXPORT jintArray JNICALL Java_com_Rng.freescale_LibImgFun_ImgFun (
  9. JNIEnv* env, jobject obj, jintArray buf, int w, int h);
  10. JNIEXPORT jintArray JNICALL Java_com_Rng.freescale_LibImgFun_ImgFun (
  11. JNIEnv* env, jobject obj, jintArray buf, int w, int h) {
  12. jint *cbuf;
  13. cbuf = env->GetIntArrayElements(buf, false);
  14. if (cbuf == 0) {
  15. return 0;
  16. }
  17. Mat myimg(h, w, CV_8UC4, (unsigned char*) cbuf);
  18. IplImage image=IplImage(myimg);
  19. IplImage* image3channel = change4channelTo3InIplImage(&image);
  20. IplImage* pCannyImage=cvCreateImage(cvGetSize(image3channel),IPL_DEPTH_8U, 1);
  21. cvCanny(image3channel,pCannyImage, 50, 150, 3);
  22. int* outImage= new int[w*h];
  23. for( int i= 0;i<w*h;i++)
  24. {
  25. outImage[i]=( int)pCannyImage->imageData[i];
  26. }
  27. int size = w * h;
  28. jintArray result = env->NewIntArray(size);
  29. env->SetIntArrayRegion(result, 0, size, outImage);
  30. env->ReleaseIntArrayElements(buf, cbuf, 0);
  31. return result;
  32. }
  33. }
  34. IplImage * change4channelTo3InIplImage(IplImage * src) {
  35. if (src->nChannels != 4) {
  36. return 0;
  37. }
  38. IplImage * destImg = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 3);
  39. for ( int row = 0; row < src->height; row++) {
  40. for ( int col = 0; col < src->width; col++) {
  41. CvScalar s = cvGet2D(src, row, col);
  42. cvSet2D(destImg, row, col, s);
  43. }
  44. }
  45. return destImg;
  46. }

 

在上面的代码中,给出了简单的Canny算子检测边缘的代码,并且返回检测后的图像显示。
上面的代码中#include <jni.h>是必须要包含的头文件,#include <opencv2/opencv.hpp>opencv要包含的头文件。

 

 

说明:

 

这个函数名,必须与java代码中的包名以及类名,函数名完全一致,

 

分别表示了包,类,函数名,中间用_分开,这个是非常重要的,否则会提示找不到函数的异常错误。


8.3 配置文件

然后再在jni下新建两个文件"Android.mk"文件和"Application.mk"文件,这两个文件事实上就是简单的Makefile文件。使用NDK进行编译的时候,需要使用Android.mk和Application.mk两个文件。

 

8.3.1如果是只有一个.cpp文件可以采用下面的Android.mk
Android.mk:

 


    
    
  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. OPENCV_LIB_TYPE:=STATIC
  4. ifeq ( "$(wildcard $(OPENCV_MK_PATH))", "")
  5. # try to load OpenCV.mk from default install location
  6. include F:\Eclipse\OpenCV-android-sdk\sdk\native\jni\OpenCV.mk
  7. else
  8. include $(OPENCV_MK_PATH)
  9. endif
  10. LOCAL_MODULE := ImgFun
  11. LOCAL_SRC_FILES := ImgFun.cpp
  12. include $(BUILD_SHARED_LIBRARY)


 


8.3.2如果是有多个.cpp文件则要采用下面的Android.mk

Android.mk:


    
    
  1. LOCAL_PATH := $(call my-dir)
  2. include $(CLEAR_VARS)
  3. OpenCV_INSTALL_MODULES:=on
  4. OPENCV_CAMERA_MODULES:=off
  5. OPENCV_LIB_TYPE:=STATIC
  6. ifeq ( "$(wildcard $(OPENCV_MK_PATH))", "")
  7. # try to load OpenCV.mk from default install location
  8. include F:\Eclipse\OpenCV-android-sdk\sdk\native\jni\OpenCV.mk
  9. else
  10. include $(OPENCV_MK_PATH)
  11. endif
  12. LOCAL_MODULE := ImgFun
  13. LOCAL_SRC_FILES := ImgFun.cpp \
  14. cut.cpp \
  15. stdafx.cpp \
  16. lsd.cpp
  17. include $(BUILD_SHARED_LIBRARY)


 


8.3.3 Application.mk:

 


    
    
  1. APP_STL:=gnustl_static
  2. APP_CPPFLAGS:=-frtti -fexceptions
  3. APP_ABI:=armeabi armeabi-v7a



8.3.4Android.mk文件中,需要主要修改的代码是如下一行,这里要指定到opencv.mk这个文件,否则在NDK进行编译本地c/c++文件得时候会提示你找不到opencv.mk这个文件:



8.3.5这一行表示你的在Android中使用的Opencv.mk文件位置所在,在本文的资源包中包含了opencv-3.4.1-android-sdk.zip压缩包,解压到当前文件夹,便可以得到地址所在,将其改为你自己的解压后的文件位置,如下图:


 


8.3.6打开解压后的文件夹OpenCV-android-sdk -> sdk -> native ->jni ->Opencv.mk




8.4、编译本地C++代码

8.4.1、这里选择使用Eclipse自动进行编译,参考以下内容进行配置:

这个时候,会在C++代码中,看到非常多的错误提示,遍地都是错误提示,这里不要慌,这里只是假的错误提示,编译cpp代码能够编译通过,但是运行程序是不行的,会提示你代码有错误,需要解决这些问题。如下图所示: 



8.4.2、打开工程属性,Project Properties -> C/C++ General -> Paths and Symbols

AssemblyGNC C++编译器添加如下路径:(这里添加的路径就是NDK中的c/c++头文件的路径)










8.4.3、点击Apply and Close后就会看到所有的错误都消失了,这样重新编译本地cpp代码,然后就可以运行工程了.

 



8.4.4、终于可以运行程序了,可以看到本程序的截图如下(直接在真机上测试):



      


总述:天呐,终于写忘了,虽然累了两天,但还是挺开心的,毕竟是小编第一次写博客嘛!但我看这些上传照片不是很清楚,如果大家感兴趣的话,可以加小编的QQ,小编把电子版的发给你,如果是在配置或者学习的过程中有什么问题,也可以给小编留言,或者大家可以加小编QQ:1637850405!说实话,真的很开心,这是小编人生中发表的第一篇博客,在此立个Flag,希望可以与大家多多交流,扩大视野,随时留言哦!Come On !!!


小编QQ:1637850405

PDF版 (百度网盘):https://pan.baidu.com/s/1kbTmETLQT6GZnKJNw_jSpQ 

                               密码:c2k7

Word版 (百度网盘):https://pan.baidu.com/s/1GZGni_yRMZ72wM22YDf6BA
                                密码:4cvn




评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值