[1]OpenCV4Android機器視覺應用入門-JNI編寫HelloWorld
OpenCV是開源的C++機器視覺框架。OpenCV整體模塊分明,演算法優秀且速度很快,是很優秀的機器視覺框架。
Android業務程序一般採用Java編寫,但是在以下情境下需要C++的配合:
- 公司的C++代碼遺產,希望在移動平台繼續使用;
- IOS和Android的跨平台需求;
- 安全性考慮,C++編譯出的so庫很難被反編譯;
- 速度考慮,C++的運行速度可能比Java在JVM上快(以前的說法了,現在JVM優化的很好);
- 突破系統的限制,例如 如何突破24M內存的限制,為Android程序分配到更多內存
- 與底層硬體交互,下面是android中Camera一些代碼片段
android.hardware.Camera;nn//自動對焦功能npublic final void autoFocus(AutoFocusCallback cb)n {n synchronized (mAutoFocusCallbackLock) {n mAutoFocusCallback = cb;n }n native_autoFocus(); //調用本地方法,實現攝像機自動對焦功能n }n private native final void native_autoFocus(); //本地方法,用C/C++實現n
1. Android的java代碼
我們實現最基本的功能,點一下按鈕顯示HelloWorld,再點一下復原,HelloWorld的文字從C++代碼中獲取。阿軍就利用Android Studio開發,基本都是默認設置給大家演示一下啦~
頁面布局:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>n<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"n xmlns:tools="http://schemas.android.com/tools"n android:layout_width_="match_parent"n android:layout_height="match_parent"n android:paddingBottom="@dimen/activity_vertical_margin"n android:paddingLeft="@dimen/activity_horizontal_margin"n android:paddingRight="@dimen/activity_horizontal_margin"n android:paddingTop="@dimen/activity_vertical_margin"n android:orientation="vertical"n tools:context="demo.hello.MainActivity">nn <Buttonn android:id="@+id/send_hello"n android:layout_width_="wrap_content"n android:layout_height="wrap_content"n android:text="Hello"/>nn <TextViewn android:id="@+id/say_hello"n android:layout_width_="wrap_content"n android:layout_height="wrap_content"n android:layout_marginTop="10dp" />nn</LinearLayout>n
MainActivity.java
package demo.hello;nnimport android.support.v7.app.AppCompatActivity;nimport android.os.Bundle;nimport android.view.View;nimport android.widget.Button;nimport android.widget.TextView;nnpublic class MainActivity extends AppCompatActivity {nn private Button bnSendHello;n private TextView tvSayHello;n private boolean isSayHello = false;nn @Overriden protected void onCreate(Bundle savedInstanceState) {n super.onCreate(savedInstanceState);n setContentView(R.layout.activity_main);nn bnSendHello = (Button)findViewById(R.id.send_hello);n tvSayHello = (TextView) findViewById(R.id.say_hello);n final HelloWorld helloWorld = new HelloWorld();nn bnSendHello.setOnClickListener(new View.OnClickListener() {n @Overriden public void onClick(View v) {n if(!isSayHello) {n //從本地方法中獲取字元串n tvSayHello.setText(helloWorld.getString());n bnSendHello.setText("RETURN");n isSayHello = true;n }else{n tvSayHello.setText(" ");n bnSendHello.setText("HELLO");n isSayHello = false;n }n }n });n }n}n
HelloWorld.java
package demo.hello;nn/**n * Created by wcjzj on 2016/6/10.n */npublic class HelloWorld {nnstatic {ntry {n//載入編譯好的動態庫n System.loadLibrary("HelloWorld");n }catch (Exception e){n e.printStackTrace();n }n }nn//本地方法,獲取字元串n public native String getString();n}n
以上完成了Java層代碼的編寫,HelloWorld.java里完成動態庫的載入和本地方法的聲明。
2. HelloWorld.java中本地方法頭文件生成
- 在工程local.properties文件中設定ndk地址(需要自己提前下載解壓),目前我的ndk和sdk位於同一目錄下
ndk.dir=C:UserswcjzjAppDataLocalAndroidNdkn
在gradle.properties中輸入
android.useDeprecatedNdk=truen
這樣就可以使用ndk了;
- make一下現在的代碼,讓其生成build文件;
- 編譯好的HelloWorld.class文件位置如下:
- 利用javah工具生成頭文件,在cmd窗口輸入javah可以看到如下用法說明
在Android Studio的termial窗口輸入
cd app/build/intermediates/classes/debugn
按enter,接著輸入
javah -jni demo.hello.HelloWorldn
然後就生成了jni介面頭文件,demo_hello_HelloWorld.h,這裡有兩點需要注意的:
set classpath=C:UserswcjzjDocumentsworkandroidHelloappsrcmainjavan
然後再生成h文件
javah -classpath . -jni demo.hello.HelloWorldn
否則,會提示無法載入類之類的錯誤
二是:如果提示沒有找到javah之類的,需要把jdk的bin地址添加到環境路徑path中
3. 建立jni文件夾存放c++/c文件
- 默認情況下jni應該放到[module]/src/main/java/jni中,也可以自定義文件地址,build.gradle在android節點下添加如下代碼
sourceSets.main {n jni.srcDirs src/main/java/myjnin }n
或者點擊右鍵new->Folder->JNI Folder會自動在build.gradle添加如上代碼,這裡我們使用默認的配置,在java下建立jni文件夾,把上一步生成的demo_hello_HelloWorld.h剪切到jni文件夾中。
- 接下來我們看一下demo_hello_HelloWorld.h中都有哪些內容
/* DO NOT EDIT THIS FILE - it is machine generated */n#include <jni.h>n/* Header for class demo_hello_HelloWorld */nn#ifndef _Included_demo_hello_HelloWorldn#define _Included_demo_hello_HelloWorldn#ifdef __cplusplusnextern "C" {n#endifn/*n * Class: demo_hello_HelloWorldn * Method: getStringn * Signature: ()Ljava/lang/String;n */nJNIEXPORT jstring JNICALL Java_demo_hello_HelloWorld_getStringn (JNIEnv *, jobject);nn#ifdef __cplusplusn}n#endifn#endifn
我們不要編輯這個文件,在Cpp中編寫實現代碼
//n// Created by wcjzj on 2016/6/10.n//n#include "demo_hello_HelloWorld.h"n/*n * Class: demo_hello_HelloWorldn * Method: getStringn * Signature: ()Ljava/lang/String;n */nJNIEXPORT jstring JNICALL Java_demo_hello_HelloWorld_getStringn(JNIEnv *env, jobject obj){n return env->NewStringUTF("HelloWorld!");n}n
由於java和C++的數據通信需要JVM作為中介,所以這裡涉及到Java和C++的數據轉換,這裡面的內容非一時半會可以說完,想仔細研究的童鞋可以閱讀我上文推薦的那本書。
- 接著就是需要編譯這些代碼,android studio的gradle初步支持jni的編譯,不需要再自己編寫Android.mk和Application.mk文件了,由於這裡的編譯比較簡單,我們直接利用gradle進行編譯就好了,在build.gradle的android節點下defaultConfig子節點添加如下代碼
ndk {n moduleName "HelloWorld" // 這個就是上面java中載入的動態鏈接庫的名稱 }n
或者可以進行更為詳細的配置(xi:
ndk { n moduleName "myEpicGameCode"n cFlags "-DANDROID_NDK -D_DEBUG DNULL=0 " // Define some macrosn ldLibs "EGL", "GLESv3", "dl", "log" // Link with these libraries! 在這裡添加你原先在makefile里ldlibs所鏈接的庫n stl "stlport_shared" // Use shared stlport libraryn}n
- 然後build(如果出現clean失敗的提示,那麼進terminal窗口輸入exit;結束其佔用),如下圖可以看到生成了各平台的libHelloWorld.so動態鏈接庫
4. 編譯運行效果如下
---------------------昏割線--------------------阿軍終於寫完了,原來寫新手教程這麼累的啊....
這個是入門篇哦,如果對jni感興趣最好把我推薦的那本書看了哦~我今天辛苦一下,還能再更一篇呢~^ ^
[2]OpenCV4Android機器視覺應用入門-不使用OpenCV Manager的三種方法-法1:純java層開發 - 王傳軍的文章 - 知乎專欄
[3]OpenCV4Android機器視覺應用入門-不使用OpenCV Manager的三種方法-法2:java和native混編 - 行深般若 - 知乎專欄
[4]OpenCV4Android機器視覺應用入門-不使用OpenCV Manager的三種方法-法3:純native方法 - 王傳軍的文章 - 知乎專欄
[5]OpenCV4Android機器視覺應用入門-拍照預覽界面動態繪圖
[6]OpenCV4IOS靜態庫製作
推薦閱讀:
※ImagePy 簡介
※1.5【OpenCV圖像處理】讀寫像素
※在 MFC 框架中,有什麼方法能直接將 OpenCV 2.0 庫中 Mat 格式的圖片傳遞到 Picture Control(圖片控制項)顯示?
※想用OpenCV做AR該如何入手?