建立完使用OpenCV的App之後,緊接著來介紹如何藉由JNI以及OpenCV,並使用C/C++來建立函式庫的方式,來進行Java與OpenCV之間的呼叫。
以修圖軟體來說,有時候需要直接將影像亮度進行調整,或者是將影像從RGB的色彩空間轉成HSV(Hue ,Saturation ,Value)的色彩空間,進行色相、飽和度、色相的調整,可是在OpenCV上並沒有實作相關的方法可以進行呼叫,而且在Java裡的Mat物件無法很直接地對影像上每一個Pixel進行存取,所以在這樣的情況下,可能會想要直接使用C/C++來進行撰寫這類的程式碼,然而這篇就是要來介紹如何使用C/C++並搭配OpenCV來建立函式庫,接著讓Java端呼叫建立出來C/C++函式庫。
(四)建立C/C++函式庫並搭配OpenCV (調整灰階影像的亮度)
1.如果要在Windows平台上編譯Linux的C/C++程式,必須先下載並安裝Cygwin來模擬Linux的編譯環境。
(1)下載並安裝Cygwin。
以修圖軟體來說,有時候需要直接將影像亮度進行調整,或者是將影像從RGB的色彩空間轉成HSV(Hue ,Saturation ,Value)的色彩空間,進行色相、飽和度、色相的調整,可是在OpenCV上並沒有實作相關的方法可以進行呼叫,而且在Java裡的Mat物件無法很直接地對影像上每一個Pixel進行存取,所以在這樣的情況下,可能會想要直接使用C/C++來進行撰寫這類的程式碼,然而這篇就是要來介紹如何使用C/C++並搭配OpenCV來建立函式庫,接著讓Java端呼叫建立出來C/C++函式庫。
(四)建立C/C++函式庫並搭配OpenCV (調整灰階影像的亮度)
1.如果要在Windows平台上編譯Linux的C/C++程式,必須先下載並安裝Cygwin來模擬Linux的編譯環境。
(1)下載並安裝Cygwin。
Step1:連結Cygwin的官方網站並下載,下載連結:http://www.cygwin.com/。
Step2:安裝Cygwin,下載完成Cygwin的安裝檔之後,滑鼠雙點擊安裝檔。
Step3:選擇要安裝的目錄(硬碟大小至少要6G)。
Step4:選擇一個下載網站,拉下去可以找到ntu的下載點,速度還蠻快的。
Step5:選擇要安裝的Package,選擇Devel,將Devel的選項從Default改成Install。
Step6:接下來就是下一步到底,等下載程式下載完成即可。
2.在Eclipse上安裝ADT與Android NDK套件。
(1)安裝ADT套件。
Step7:開啟Eclipse,點選Help->Install New Software。
Step8:跳出安裝套件的視窗之後,新增軟體來源,點選"Add"。
Step9:跳出新增來源的視窗後,輸入以下參數。
軟體名稱(Name):ADT
下載位置(Location):http://download.eclipse.org/tools/cdt/releases/indigo
Step10:等待一會之後,Eclipse會跳出安裝的資訊,將"CDT Main Features"以及"CDT Optional Features"勾選。
Step11:接下來就下一步到底。
Step12:下載並安裝完成後,Eclipse會要求你重開Eclipse,重開完Eclipse後,ADT就灣裝完成。
(2)安裝與設定Android NDK套件。
Step14:安裝過程與ADT套件一樣,只是下載位置改成:https://dl-ssl.google.com/android/eclipse/,在這邊就不再贅述。
Step15:在安裝完Android NDK套件之後,接下來在Eclipse上設定NDK的根路徑位置,點選Window->Preferences。
Step16:在Preferences的頁面上,選擇"Android"->"NDK",並輸入電腦上的NDK位置。
3.新增C/C++函式庫並設定編譯參數。
(1)新增C/C++函式庫。
Step17:對專案使用滑鼠右鍵->"Android Tools"->"Add Native Support"。
Step18:填入函式庫名稱:HelloOpenCVNDK。
Step19:新增完成後就會在專案底下建立jni資料夾,裡面包含原始碼(*.cpp)以及編譯設定檔(*.mk)。
(2)設定編譯參數。
Step20:對專案使用滑鼠右鍵->"Properties"。
Step21:選擇C/C++ Build頁籤,並設定建立命令(Build Command):${NDKROOT}/ndk-build.cmd。
Step22:設定編譯行為,設定方式如下圖。
Step23:換到"C/C++ General"頁籤->"Paths and Symbols"->"Add…",新增OpenCV函式庫標頭檔(Header)的位置。
Step24:新增OpenCV標頭檔的位置(OpenCV標頭檔會在"OpenCV根目錄\sdk\native\jni\include")。
Step25:將OpenCV編譯的參數檔(點我下載)放入專案\jni資料夾底下,比較常會修改的參數是:APP_ABI(目標機器的CPU架構,建議設定是all,eclipse就會幫你編譯OpenCV目前支援的CPU架構)、APP_PLATFORM(目標機器的android系統的版本)
APP_ABI := all
APP_PLATFORM := android-14
Step26:接下來在Eclipse的jni資料夾點選右鍵->"Refresh"。
Step27:在jni/Android.mk之中,新增引用OpenCV的編譯參數檔(檔案會在安裝OpenCV4Android的目錄之下/sdk/native/jni/OpenCV.mk,例如:”F:/ OpenCV-2.4.9-android-sdk/sdk/native/jni/OpenCV.mk”)。
4.新增控制元件以及C/C++的程式碼與對應的Java程式碼。
(1)新增控制元件。
可以讓使用者在App上面調整影像增加亮度的量,以及顯示調整亮度的量。
Step28:修改 專案/res/layout/activity_main.xml的程式碼為:
(2)C/C++程式碼。
Step29:修改 專案/jni/HelloOpenCVNDK.cpp的程式碼成為:
#include#include using namespace cv ; extern "C" { JNIEXPORT void Java_com_example_helloopencv4android_MainActivity_adjustBrightness(JNIEnv* env, jobject thiz , jlong addrGray , jlong addrDest , jint add ) { Mat& mGrey = *(Mat*)addrGray ; Mat& mDest = *(Mat*)addrDest ; uchar *ptr = NULL , *ptrD = NULL ; int i , j , mAdd = (int)add ; if ( mGrey.channels() != 1 || mGrey.depth() != CV_8U ) return ; mDest.release() ; mDest.create(mGrey.size() , mGrey.type()) ; for ( i = 0 ; i < mGrey.rows ; ++i ) { ptr = mGrey.ptr (i) ; ptrD = mDest.ptr (i) ; for ( j = 0 ; j < mGrey.cols ; ++j) { ptrD[j] = saturate_cast ( ptr[j] + mAdd) ; } } } } ;
要在Java端可以呼叫C/C++的函式,函式的介面必須是C的形式(extern "C"),函式的名稱必須根據以下規則進行:
JNIEXPORT 回傳型態 Java_套件的路徑(以_隔開下一層的目錄,因為必須使用’_’隔開資料夾的目錄,所以建議不要在套件名稱上面使用’_’)_類別名稱(想要在哪一個類別下呼叫)_函式名稱(JNIEnv* env, jobject thiz, 其他傳入參數)
Step30:以上面的範例,在Java呼叫端(com.example. helloopencv4android.MainActivity.java)的程式碼就是:
//C/C++函式庫的函式呼叫介面 static native void adjustBrightness(long grey, long dst, int add) ;
完整的程式碼範例(MainActivity.java):
package com.example.helloopencv4android; import java.io.InputStream; import org.opencv.android.BaseLoaderCallback; import org.opencv.android.LoaderCallbackInterface; import org.opencv.android.OpenCVLoader; import org.opencv.android.Utils; import org.opencv.core.Mat; import org.opencv.imgproc.Imgproc; import android.support.v7.app.ActionBarActivity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; //實作OnSeekBarChangeListener來取得SeekBar被使用者拖曳的數值 public class MainActivity extends ActionBarActivity implements OnSeekBarChangeListener{ //C/C++函式庫的函式呼叫介面 static native void adjustBrightness(long grey, long dst, int add) ; //lena的灰階影像 private Mat mGrey ; //使用者拖曳SeekBar所顯示的數值大小 private TextView mTextView ; //顯示處理的影像結果 private ImageView mImageview ; //將影像由OpenCV Mat物件轉成Java的Bitmap物件並顯示在ImageView1上 private void SetImageMat(Mat m) { Bitmap bmp = Bitmap.createBitmap(m.cols(), m.rows(),Bitmap.Config.ARGB_8888); switch(m.channels()) { case 1: case 3: int transCode = m.channels() == 1 ? Imgproc.COLOR_GRAY2BGRA : Imgproc.COLOR_mRGBA2RGBA ; Mat mRGBA = new Mat() ; //因為Android顯示的時候需要使用BGRA的格式 所以必須將灰階(channel = 1)擴展成BGRA(channel = 4) Imgproc.cvtColor(m, mRGBA, transCode); //將OpenCV的Mat型態轉成Bitmap型態 Utils.matToBitmap(mRGBA, bmp) ; mRGBA.release() ; break ; case 4: Utils.matToBitmap(m, bmp) ; break ; default: return ; } this.mImageview.setImageBitmap(bmp) ; } //非同步載入OpenCV函式庫 private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) { @Override public void onManagerConnected(int status) { switch (status) { case LoaderCallbackInterface.SUCCESS: { System.out.println("load opencv lib SUCCESS!!"); //從resource讀取影像 InputStream is = getResources().openRawResource(R.drawable.lena); Bitmap img = BitmapFactory.decodeStream(is); if (img != null) { Mat m = new Mat() ; mGrey = new Mat() ; //將bitmap轉成OpenCV的Mat型態 Utils.bitmapToMat(img, m); //將影像轉換成灰階 Imgproc.cvtColor(m, mGrey, Imgproc.COLOR_BGR2GRAY); SetImageMat(mGrey) ; } //初始化HelloOpenCVNDK的函式庫 System.loadLibrary("HelloOpenCVNDK") ; } break; default: { super.onManagerConnected(status); } break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //非同步初始化OpenCV OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_9 , this , mLoaderCallback ) ; SeekBar mSeekBar = (SeekBar) findViewById(R.id.seekBar1) ; //設定SeekBar不能點選 mSeekBar.setClickable(false) ; //設定SeekBar的最大值是254 mSeekBar.setMax(254) ; //設定初始的直為127 mSeekBar.setProgress(127) ; //設定SeekBar的OnSeekBarChangeListener事件來監聽SeekBar被拖曳的結果 mSeekBar.setOnSeekBarChangeListener(this) ; this.mTextView = (TextView) findViewById(R.id.textView1) ; //設定TextView初始字串為0 this.mTextView.setText("0") ; this.mImageview = (ImageView) findViewById(R.id.imageView1) ; } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.main, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } //當SeekBar的數值被使用者拖曳的時候所觸發的事件 @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // TODO Auto-generated method stub this.mTextView.setText(String.valueOf(progress - 127)) ; } @Override public void onStartTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub } //當使用者停止拖曳所觸發的事件 @Override public void onStopTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub int progress = seekBar.getProgress() ; Mat dst = new Mat() ; // adjustBrightness( this.mGrey.nativeObj , dst.nativeObj , progress - 127) ; SetImageMat(dst) ; dst.release() ; } }
5.執行結果。
調亮:
調暗:
沒有留言:
張貼留言