建立完使用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.執行結果。
調亮:
調暗:




























沒有留言:
張貼留言