Showing posts with label 程式設計. Show all posts
Showing posts with label 程式設計. Show all posts

Tuesday, October 25, 2011

Porting OpenCV 2.3.1 to iPhone 3GS/4/4S - 如何將OpenCV中影像處理輸出資料結構Mat的data轉出成為UIimage

透過OpenCV處理完的影像資料可以透過如下的程序轉回UIImage,就可以在iOS中任意顯示或儲存:
1. 先將OpenCV處理後在Mat資料結構中的RGB資料(result.data)轉成RGBA資料(img_data

    unsigned char * img_data = (unsigned char *)malloc(img_width * img_height * 4);

// Convert RGB data in Mat (result) to RGBA raw data buffer (img_data)
    for(i=0;i<img_height;i++){
        for(j=0;j<img_width;j++){
            img_data[i*img_width*4+j*4]=result.data[i*result.step[0]+j*3*2];
            img_data[i*img_width*4+j*4+1]=result.data[i*result.step[0]+j*3*2+2];
            img_data[i*img_width*4+j*4+2]=result.data[i*result.step[0]+j*3*2+4];
            img_data[i*img_width*4+j*4+3]=0xFF;
        }
    }


2. 透過如下的程序將RGBA資料(img_data)在轉成UIImage

    int bitsPerComponent = 8;
    int bitsPerPixel = 32;
    int bytesPerRow = 4 * img_width;
    CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, img_data, img_width*img_height*4, NULL);
    CGColorSpaceRef colorSpaceRef = CGColorSpaceCreateDeviceRGB();
    CGBitmapInfo bitmapInfo = kCGImageAlphaPremultipliedFirst|kCGBitmapByteOrder32Little;
    CGColorRenderingIntent renderingIntent = kCGRenderingIntentDefault;
    CGImageRef imageRef = CGImageCreate(img_width, img_height, bitsPerComponent, bitsPerPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, NULL, NO, renderingIntent);
    UIImage *myImage = [UIImage imageWithCGImage:imageRef];

Porting OpenCV 2.3.1 to iPhone 3GS/4/4S - 如何將UIImage轉成OpenCV中資料結構Mat的data 當成影像處理之輸入

在Xcode iOS下要調用OpenCV的相關函數還有個難題就是如何將iOS中慣用來處來影像的Buffer - UIImage轉化成OpenCV內部使用Mat型態的資料結構內,下面這段程式紀錄如何達成此一目的的程序:
1. 先將UIImage中的影像資料轉出到資料型態為unsigned char *img中(img中為RGB raw data)

                UIImage *myImage1 = [UIImage imageNamed:@"test.jpg"];
                CGImageRef imageRef = [myImage1 CGImage];
                NSUInteger width = CGImageGetWidth(imageRef);
                NSUInteger height = CGImageGetHeight(imageRef);
                CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
                unsigned char *rawData = (unsigned char *)malloc(height * width * 4);
                NSUInteger bytesPerPixel = 4;
                NSUInteger bytesPerRow = bytesPerPixel * width;
                NSUInteger bitsPerComponent = 8;
                CGContextRef context = CGBitmapContextCreate(rawData, width, height,
                                                             bitsPerComponent, bytesPerRow, colorSpace,
                                                             kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little);
                CGColorSpaceRelease(colorSpace);
                
                CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
                CGContextRelease(context);
                unsigned char * img = (unsigned char *)malloc(height * width * 3);
                for(int i=0;i
                    for(int j=0;j
                        img[i*width*3+j*3]=rawData[i*width*4+j*4];
                        img[i*width*3+j*3+1]=rawData[i*width*4+j*4+1];
                        img[i*width*3+j*3+2]=rawData[i*width*4+j*4+2];
                    }
                }
                free(rawData);


2. 呼叫OpenCV函式庫前先製作符合需求的Mat型態的資料結構full_img如下
        int refcount=1;
        Mat full_img;
        full_img.dims=2;
        full_img.rows=img_height;
        full_img.cols=img_width;
        full_img.flags|=0x00000010;
        full_img.refcount=&refcount;
        full_img.data=img;
        full_img.datastart=full_img.data;
        full_img.dataend=full_img.data+full_img.rows*full_img.cols*3;
        full_img.datalimit=full_img.data+full_img.rows*full_img.cols*3;
        full_img.size[0] = full_img.rows;
        full_img.size[1] = full_img.cols;
        full_img.step[0] = full_img.cols*3;
        full_img.step[1] = 3;

3. 需要使用到Mat型態的資料結構的函數呼叫,就可以傳入full_img當引數了


Porting OpenCV 2.3.1 to iPhone 3GS/4/4S - 如何在Xcode中使用OpenCV 2.3.1 iOS函式庫

要在Xcode中使用已經編譯好的OpenCV函式庫基本上非常簡易,照如下步驟即可輕易使用:
1. 將要使用已經編譯好的函式庫只接拖拉進Xcode的專案下


2. 將頭檔(header files)所在資料夾加入Build Settings裡的User Header Search Paths中


3. 要使用函式庫的xxx.m或是xxx.mm檔中引入相關的頭檔,即可使用相關函數

p.s. 在Xcode專案交雜使用xxx.m/xxx.mm/xxx.c/xxx.cpp時,記得xxx.c中的函數,可以在引入頭檔後直接在xxx.m中調用;而在xxx.cpp中的函數,只能在xxx.mm中才能調用,xxx.m是不能調用xxx.cpp中的函數的!

Friday, October 21, 2011

Porting OpenCV 2.3.1 to iPhone 3GS/4/4S - 如何使用cmake+Xcode編譯OpenCV 2.3.1函式庫給iOS使用

要能在Xcode中使用OpenCV 2.3.1函式庫來實作自己的程式首要之務當然是要能建立出符合iOS使用的OpenCV 2.3.1的函式庫,如下的步驟紀錄了這個過程:
1. 下載並安裝CMakeMac OSX 64/32-bit Universal
2. 下載並解壓縮OpenCV-2.3.1a.tar.bz2
3. 執行CMake並指定Where is the source code(你的OpenCV資料夾位置)、Where to build the binaries(編譯後OpenCV函式庫要放的資料夾)以及CMAKE_INSTALL_PREFIX(安裝編譯的檔案夾),如下圖:
4. 在CMake裡面取消下列的選項:

  • BUILD_DOCS
  • BUILD_EXAMPLES
  • BUILD_NEW_PYTHON_SUPPORT,
  • BUILD_SHARED_LIBS
  • BUILD_TESTS
  • BUILD_WITH_DEBUG_INFO
  • ENABLE_SSE, WITH_EIGEN
  • WITH_OPENEXR
  • WITH_PVAPI
  • WITH_QT
  • WITH_QT_OPENGL
  • WITH_QUICKTIME

5. CMake裡面Configure選擇Xcode然後按下Generate,執行完後在你設定輸出函式庫的資料夾裡就會產生OpenCV.xcodeproj 。

6. 用Xcode打開OpenCV.xcodeproj,將Base SDK選成iOS(我用的是iOS5.0),如圖:

7. Xcode中OpenCV Build Settings的Architectures選Standard (armv7),如圖:

8. Xcode中OpenCV Build Settings的Compiler for C/C++/Objective-C選LLVM GCC4.2,如圖:

8. Xcode中OpenCV TARGETS將opencv_highgui、 opencv_traincascade、 opencv_createsamples、 opencv_haartraining、 opencv_performance、 opencv_stitching,如圖:

9. 接著就是選擇TARGETS中的ALL_BUILD來編譯;如果沒有意外,成功編譯後在你設定編譯後OpenCV函式庫要放的資料夾中的lib資料夾下就會有所有opencv的函式庫了;最後,你還可以選擇TARGETS中的install來編譯,成功的話,在你設定安裝編譯的檔案夾中就會產生集成後的標頭檔方便你使用。

Thursday, October 20, 2011

Porting OpenCV 2.3.1 to iPhone 3GS/4/4S - 在iOS中使用thread來實作耗時的程序

在使用OpenCV函式庫中必然有大量運算的需求,因此就需要用thread來分離大量運算的部份才不至於使UI卡住,下面就來實際操作如何在iOS中使用thread來實作耗時的程序:
1. 在xxxViewController.h的@interface xxxViewController中宣告
            NSThread * stitchThread;


2. 在xxxViewController.m中實作stitchThreadProc如下(將有大量需求的運算放在其中):

       - (void)stitchThreadProc:(id)info {
    
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
            //大量運算放於此
            //...

            [pool drain];
    
       }

3. 使用如下的方式呼叫並啟動thread:

        stitchThread = [[NSThread alloc] initWithTarget:self selector:@selector(stitchThreadProc:) object:nil];
        
        [stitchThread start];


完成了,是不是很簡單呢!

Wednesday, October 19, 2011

Porting OpenCV 2.3.1 to iPhone 3GS/4/4S - 在耗時的程序中使用UIActivityIndicatorView來通知使用者程式還在執行中

在使用OpenCV函式庫時,你會發現很多運算都是相當耗時,這時候我們就需要利用iOS中的UIActivityIndicatorView來通知使用者程式還在執行運算,請稍待。如何使用呢?精簡的步驟如下:

1. 在xxxViewController.h的@interface xxxViewController...中宣告
           UIActivityIndicatorView * activityIndicator;
         
           並加上如下的程式碼:
       @property (nonatomic, retain) IBOutlet UIActivityIndicatorView * activityIndicator;
       -(void)spinActivityIndicatorBegin;
       -(void)spinActivityIndicatorEnd;

2. 使用Interface Builder 擺一個UIActivityIndicatorView在你想要放置的位置上,並連接到上面這個 activityIndicator;設置屬性為 hidden when stopped.
3. 在xxxViewController.m實作spinActivityIndicatorBeginspinActivityIndicatorEnd如下:
       - (void) spinActivityIndicatorBegin {
            [activityIndicator startAnimating];
       }

       - (void) spinActivityIndicatorEnd {
            [activityIndicator stopAnimating];
       }
4. 在xxxViewController.m耗時運算的前後作如下的呼叫:
       [NSThread detachNewThreadSelector: @selector(spinActivityIndicatorBegin) toTarget:self withObject:nil];
       //此處為耗時運算

       [NSThread detachNewThreadSelector: @selector(spinActivityIndicatorEnd) toTarget:self withObject:nil];

完成後執行程式應該就可以看到小菊花在你設定的位置轉阿轉的囉!


Porting OpenCV 2.3.1 to iPhone 3GS/4/4S - 序曲

這陣子剛好需要熟悉Xcode iOS的程式設計,所以挑了個題目來實作操練一下,剛好看到OpenCV釋出2.3.1,因為剛好裡面有個新的Photo Stitch範例,想說就挑他來練靶,經歷了2個星期的摸索,終於成功的將OpenCV 2.3.1編譯成iOS上的library,並套用他來生成iPhone 3GS/4/4S可以執行Photo Stitch的小程式。下面的video是實際運行程式的過程演示:



範例中用來接合的兩張照片如下:



及接合後的結果:


這個demo是在iPhone 3GS上演示,接合效果還不錯,但是效能不是頂好,日後有精力再來針對演算法參數與程式最佳化進行較調;這邊陸續把一些移植過程中的稜稜角角給記錄下來,應該會包括下面幾項:

  1. 如何使用cmake+Xcode編譯OpenCV 2.3.1函式庫給iOS使用
  2. 如何在Xcode中使用OpenCV 2.3.1 iOS函式庫
  3. 如何將UIImage轉成OpenCV中資料結構Mat的data 當成影像處理之輸入
  4. 如何將OpenCV中影像處理輸出資料結構Mat的data轉出成為UIimage
  5. 在iOS中使用thread來實作耗時的程序
  6. 在耗時的程序中使用UIActivityIndicatorView來通知使用者程式還在執行中
待續

Friday, June 17, 2011

Android裡判讀網路連線狀態

在Android系統裡面,網路連線是很重要的一環,判讀連線狀態來協助目前執行中的application做出反應通常是必要的,下列的方法可以讓我們輕鬆的讀取android系統目前網路連的狀態

1. 先在AndroidManifest.xml裡面先加上,沒有這個,利用下面的步驟去取得網路狀態會導致application出現exception
2.透過下面的code可以取得NetworkInfo
    ConnectivityManager connManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo info=connManager.getActiveNetworkInfo();
3.可以透過NetworkInfo提供的methods來取得網路狀態
    info.isConnected()           //網路是否連線
    info.getTypeName()        //連線方式wifi/gprs
    info.isRoaming()              //是否漫遊

Wednesday, June 15, 2011

Android裡使用global variable

寫程式,難免要用到一些global variables,在Android裡面當然也不例外。我的實際遭遇是剛開始本來是想要在Activities間來傳遞variables,通常可以用如下的方式:

若要將"hello"由ActivityA傳給ActivityB的話首先要在ActivityA中作如下的動作
String text="hello";
Intent intentA = new Intent(ActivityA, ActivityB.class);
intentA.putExtra("variable", text);
startActivity(intentA);
接著可以到ActivityB中去接收就行了
Bundle bundle = getIntent().getExtras();
String text = bundle.getString("variable");

但是使用這個方法的限制就是傳遞的參數只能為String或是可以Serialize的物件,要是你要傳的不符合,那真的就頭大了,這時候就得要靠android.app.Application在Android裡建立一個全域物件來達成了,方法如下:

1.先建立自己的全域物件,裡面隨便你要擺什麼variable都行

        public class MyApp extends Application{
                   int            int_test;
                   boolean  boolean_test;
                   String      string_test;
                   ...
        }
2.在AndroidManifest.xml裡加上android:name="MyApp",如下
      
3.然後在你想要讀寫MyApp的Activity透過下面的方式就可以存取你放在MyApp中的任何variable了:
        MyApp myapp = ((MyApp)getApplicationContext());
        myapp.int_test=12;
        if (myapp.boolean_test==false)
           myapp.string_test="Error!";

這樣是不是就解決了這個讓人頭大的問題了呢!

Thursday, June 2, 2011

如何讓Focus在第一個EditText的Android Activity不要出現小鍵盤

其實我想這個問題很多人會遇到,就是當你寫了一個Activity然後EditText是在第一順位的時候,當這個Activity的UI被執行時,Focus會直接跳到這個EditText而且會飄出小鍵盤來等候你輸入,但是這個小鍵盤有時候很讓人討厭,如何在第一次進入的時候不讓小鍵盤飄出來呢,其實很簡單,只要先打開你的的AndroidManifest.xml然後作如下的步驟(參考附圖):

1. 選擇你要的Activity
2.將Window soft input mode選stateHidden
存檔後從新編譯就完成了,很簡單就可以把惱人的小鍵盤給變不見了呢。

Tuesday, April 26, 2011

如何在android app裡面使用tab以及新增activity


因為Project的需要,這幾天研究了一下要如何在Android APP中使用Tab元件,原本照著Android Developers(http://developer.android.com/resources/tutorials/views/hello-tabwidget.html)裡面的步驟依樣畫葫蘆,以為簡簡單單就可以搞定,沒想到踢到了鐵板@@...在一番的研究之後發現原來是我產生Activity的方式不對;我原本天真的以為直接在src folder裡面加上我需要的.java檔案就可以運作,結果發現build project都沒有問題,但是一旦deploy到simulator上怎樣都跑不出畫面,原來正確的作法是必須如下

1.在你自己的package上按右鍵選擇New->Class





2.在出現的對話框的Name欄位填入要產生Activity的Class名字,然後按下Superclass這個欄位的"Browse..."這個按鈕後會跳出另一個對話框,選擇Activity-android.app後按Finish按鈕完成。





3. 完成後會自動產生Settings.java檔案,我在裡面加上了onCreate method如下來輸出"This is the Settings tab":

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        TextView textview = new TextView(this);
        textview.setText("This is the Settings tab");
        setContentView(textview);
    }


完成了新增class的動作後再來就是處理AndroidManifest.xml,步驟如下:

1.在Project Explorer中點選並打開AndroidManifest.xml;
2.選擇Application這個tab
3.選擇Application nodes
4.點選"Add..."這個按鈕



5.在跳出的對話框中選擇Activity
6.選OK

 

7.此時會出現Attributes for Activity的表單,按下Name這個欄位的"Browse..."來展開對話框
8.在對話框會有剛剛新增的Settings這個Activity選項,選擇他
9.按OK後存檔




10.最後開啟AndroidManifest.xml確認有Settings這個Activity加入,也就是在檔案裡會有一行這個東西;這邊有個小地方要注意的事所有AndroidManifest.xml裡的activity tag需要連續,我在測是的時候就因為我在中途加入了一個service tag而導致程式無法運作...我猜應該是google的bug...(P.S.如果你覺得步驟1~9真的太麻煩,也可以直接打開AndroidManifest.xml然後在裡面加上這一行)。

 


重複如上的步驟在產生所有需要Tab的Activity,最後就是在你繼承TabActivity這個物件的onCreate這個method加入產生tab的程式碼如下例:

        TabHost tabHost = getTabHost();  // The activity TabHost

        tabHost.addTab(tabHost.newTabSpec("settings")
                .setIndicator("Settings")
                .setContent(new Intent(this, Settings.class)));
        tabHost.addTab(tabHost.newTabSpec("panel")
                .setIndicator("Panel")
                .setContent(new Intent(this, Panel.class)));
        tabHost.addTab(tabHost.newTabSpec("devices")
                .setIndicator("Devices")
                .setContent(new Intent(this, Devices.class)));
        tabHost.addTab(tabHost.newTabSpec("captureevents")
                .setIndicator("Capture Events")
                .setContent(new Intent(this, CaptureEvents.class)));
        tabHost.setCurrentTab(0);

最後,把模擬器跑起來就會看到如下 的Tab GUI產生:

 


其實只要是操作過一次,下次要作就很簡單,但是對剛入門的新手,要注意的小地方真是有點多,所以在此紀錄一下心得,也給有需要的人參考...