2014年8月23日 星期六

在Android App上建立C/C++函式庫並搭配OpenCV

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






















調暗:






















參考網站:
[1]OpenCV Tutorial
[2]SeekBar
[3]Cygwin
[4]JNI

沒有留言: