2012年3月3日

C# 控制 Webcam 【using Emgu】

這篇似乎是我歷年來第一篇有關M$的筆記.........
真是寫得有點心不甘情不願XD
畢竟本人真的是沒有很喜歡M$的東西,
當然不是因為M$不好,M$也有它好的一面,
只能說或許是習慣問題....

OK,進入正題,
這篇主要是去說明如何利用C#去控制你的網路攝影機,
且是使用Emgu這套dll,
甚麼是Emgu呢?
他和OpenCV有點關聯,
OpenCV相信不管是Java, C, C++等language的開發者,都了解OpenCV是甚麼!?
簡單說,OpenCV是一套強大的影像處理library,由INTEL開發,
非常強大,甚至你可以利用OpenCV去做到OCR,很方便。
也由於OpenCV沒有支援C#,那C#要怎麼使用OpenCV呢?
就是靠EmguEmgu是一套允許OpenCV的function在C#等語言中被使用。

但我們這邊並不會使用到OpenCV的功能,
就是簡單的介紹Emgu很基本的功能,
就是存取攝影機,
首先先去下載Emgu,並且安裝
安裝完成以後就可以開始寫程式....

看你是要開啟一個WPF專案,還是一個Window Form專案,
建議你開啟Window Form,因為到時Webcam回傳回來的image型態,
可以直接在 Window Form中的pictureBox裡面使用。

開啟以後,就在你的專案中加入reference,
加入以下四個dll,dll的位置就是在你安裝Emgu位置的bin底下:

  1. Emgu.CV.dll
  2. Emgu.CV.ML.dll
  3. Emgu.CV.UI.dll
  4. Emgu.Util.dll

加入以後,請先儲存你的專案,
儲存以後請在你安裝Emgu位置的bin底下找到兩個dll,

  1. opencv_core231.dll
  2. opencv_highgui231.dll

把這兩個dll放置到你的專案的/bin/Debug/底下。
因為Emgu.CV.dll會使用到上述兩個dll。


完成上述動作以後就開始寫code,
先import會使用到的lib,如下:
using Emgu.CV;
using Emgu.CV.Structure;


先宣告一個Capture物件,如下:
private Capture cap = null;                 // Webcam物件
這個物件就是用來連結到你的webcam。


接著在Form1_Load event中,
連結到攝影機以及建立一個event用來抓取畫面,如下:
private void Form1_Load(object sender, EventArgs e)
{
     cap = new Capture(0); // 連結到攝影機0,如果你有兩台攝影機,第二台就是1
     Application.Idle += new EventHandler(Application_Idle); // 在Idle的event下,把畫面設定到pictureBox上(當然你也可以用timer事件)
}


接下來要寫抓取畫面event的code,
        void Application_Idle(object sender, EventArgs e)
        {
             Image<Bgr, Byte> frame = cap.QueryFrame(); // 去query該畫面
             pictureBox1.Image = frame.ToBitmap(); // 把畫面轉換成bitmap型態,在餵給pictureBox元件
        }


完成以後按下F5執行,應該就可以順利取得攝影機畫面瞜!



2012年2月19日

Android Out of Memory (OOM)

呼,這篇文章放在我的草稿已經有兩個月了,
一直遲遲沒有完成,
一來是家裡太忙,
一來也是因為最近都在搞Kinect和Nxt Robot的應用(千百個不願意)
今天決定花點時間把這篇文章修完,
其實有很多篇都在草稿,有十幾篇要寫完,但先挑選這篇,
畢竟OOM是個很嚴重的問題。

何謂OOM呢?
OOM (Out of Memory),就是溢存的意思,
白話點就是超出記憶體大小了,
每個APP,都會有配置一定大小的heap size,
但這個heap size在每支設備上都不同,
像在Nexus one就是24mb,HTC Sensation只有16mb,
大小都不一定的,所以我們在寫app時,
必須做好記憶體的控管,

有些人可能會納悶,不是用JAVA寫的嗎?
JAVA無法對記憶體有更進一步的操作(配置、釋放)阿,
怎麼做好記憶體的控管呢?
恩,沒有錯。

【那OOM的問題會發生在哪?】
就是在圖片的使用!!!!!!
你每調用一張圖片,在android上都會做到auto scale的動作,
在做auto scale的動作是用C去配置記憶體的,
因此就會占用記憶體,
如果沒有做好控管,就會發生溢存!
那怎麼做好控管呢?


你們有時會發現有些APP好大喔,
有部分原因可能是引用其他外部的lib,
很少是因為程式碼寫太多....
大部分的問題就是在圖片太多,且太大,
沒有好好善用draw9patch,何謂draw9patch可以來這看
當然draw9patch並不適用所有的案例,
甚至連根據dpi的不同,放圖片在不同的資料夾,
這方法我也覺得不適用所有案例,
我大多都還是去取得螢幕大小,自己去做到裁切圖片的動作,
個人覺得這樣是最精準的(畢竟android device實在太多..................)。


網路上有些解法是,
在做重新裁切或者取得圖片的動作時,
把圖片品質設小一點。
當然這也是一種解法啦。
但個人看法,認為這解法治標不治本,
怎麼說呢? 畢竟如果圖片一多,
OOM的問題還是會發生。


所以最根本的解法在於recycle!
但是recycle的時機很重要,
畢竟如果圖片還在使用中就進行recycle的話,
可是會發生crash的。

我的建議是在Android Life Cycle中的onDestroy中進行recycle,
此時最為保險。當然如果你能保證你的圖片用不到了,就可以進行recycle了。
不一定要照我的建議而做。


這裡有個情境,假設我有4個畫面,A,B,C,D。
A有按鈕跳到B
B有按鈕跳到C
C有按鈕跳到D
每個畫面都有一張不同的背景圖,
怎麼設背景圖呢?
大部分人會在layout.xml中去做設定,
或者會在程式中設定,
如下:
linearLayout.setBackgroundDrawable(getResources().getDrawable(R.drawable.bg_normal));
這樣看似沒有甚麼問題,
但如果ABCD畫面換來換去,經過多次換來換去以後,
這時候heapSize就會越來越小....
最後就會發生OOM!

所以較好的做法就是在lifecycle中的onCreate中,
去做init的動作,
先宣告二個全域的變數
一個是用來存圖片的BitmapDrawable
一個是放置圖片的LinearLayout,如下:
private LinearLayout llBackgroundPanel = null;
private BitmapDrawable bmpDrawImg  = null;


接著在onCreate中去呼叫一個自己寫好的函數(fnSetBackground)
這個函數就是用來設背景圖片的,如下:
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.a);
    fnSetBackground(); // 呼叫函數
}


而函數內容如下:
public void fnSetBackground(){
    // 先取得LinearLayout
    llBackgroundPanel = (LinearLayout) findViewById(R.id.llBackgroundPanelChangePwd);
    // 取得該張圖片,並放置在變數bmpDrawImg中
    bmpDrawImg = new BitmapDrawable(getResources().openRawResource(R.drawable.bg_normal));
    // 最後就是設定圖片
    llBackgroundPanel.setBackgroundDrawable(bmpDrawImg);
}

完成上面步驟,只是設置圖片而已,
還是無法解決OOM的問題,下面才是解決的方式,
在lifecycle中的onDestroy中去進行清空,
@Override
protected void onDestroy() {
    super.onDestroy();
    // 每個Drawable被加到VIEW上面都會產生一個callback,所以在recycle圖片之前,必須先把callback設成null
    // 設成null以後,背景圖片自然就會不見,就會變成黑的背景。bmpDrawImg的狀態就會是沒有被使用中。
    llBackgroundPanel.getBackground().setCallback(null); 
 
    // 先判斷bmpDrawImg 是否為null,如果不是null,且bmpDrawImg 還沒有被recycle的話就進行recycle
    if (null != bmpDrawImg && !bmpDrawImg.getBitmap().isRecycled()){
        bmpDrawImg.getBitmap().recycle();
    }
    System.gc();
}


照著上面的方法實作就能避免掉OOM的問題,
我寫在destroy的原因在於,
android lifecycle的特性,
當你A按下按鈕到B,
這時候在B畫面中按下返回按鈕(back),
是不是就回到A了呢?
第一次按下返回按鈕,B會去調用onDestroy這個method,
如果這時候沒有進行recycle,那麼那塊記憶體空間就不會被釋放,
就會被占用著....
如果沒有進行RECYCLE,
久而久之,你持續返回A,又跳到B,重複著這動作好幾次,
圖片就被產生好幾次,
很快就會發生OOM,至於多快,就視你的圖片大小瞜!






















2012年1月9日

Java 物件複製的問題

不知道大家有沒有發現,
把物件A,設值給B,
當B的值改,A也會改!?!?!?!?!?

如下:
ArrayList<String> A = new ArrayList<String>();
A.add("KenYang");
ArrayList<String> B= A;



這時候如果我們去印出B的長度,
System.out.println(B.size());
那會是甚麼?
答案會是1,這無庸置疑,很基本的常識。


可是如果我把B的第0項刪除,在印出B的長度呢?
B.remove(0);
不用想,一定變成0。


那這時候,A的長度呢? 一般人可能會以為是1,
在C++的觀念,的確如此,的確會是1,
但在java不是...
可以印出來看看!
System.out.println(A.size());
答案會是0!


這是為什麼呢? 為什麼只是修改了B的值,
A也變了??

這是因為,在java中,非primitive的資料型態,
都屬於物件,都是參考!!! (甚麼是參考? 可以去我先前PO的文章看看)
簡單的說就是別名。
但Java的參考概念就很像C++的pointer!!! (記錄著該物件的位址)


下面用圖片來解釋,上面code的行為,
下面圖一的行為就是,

先宣告A,再設值進去。
我們可以從圖發現,A是去參考記憶體位址為0000的物件!!!!

圖一


那麼這時候,把A的值丟給B呢?
在記憶體中的表現會是怎樣?
A=B;
如圖二。
會發現B,也是去參考記憶體位址為0000的物件!!!
A和B都是記錄著該物件的記憶體位址!!!

圖二


所以從圖中應該可以了解上面的行為了吧?
為什麼B把第0項刪除,
結果A也變了呢?
就是因為在JAVA非primitive都是參考的關係!!!

嗯! 那有人可能會疑問!!!
咦? String 也是非primitive的資料型態阿
可是為什麼下面的例子,B改,A卻沒改呢?


這就要用到先前某篇講解java拆箱(unboxing)、裝箱(boxing)的概念了!
那關java拆箱(unboxing)、裝箱(boxing)的甚麼事呢?
還是不了解為什麼兩個都是String的物件,怎麼B改,A卻沒改呢?
來看下面的小例子,
String A = "ken";
String B = A;
B = "yang";
System.out.println(A);
為什麼不是印出yang呢?
都是primitive阿,
原因就是自動裝箱(auto-boxing),
上面的行為,在Compiler中,
B="yang"
會被轉換成
B = new String("yang");
這時候他就是全新的物件了!
因此不會改到原本的值!!!!!



在Java中的複製,
想要做到真正的複製,
得靠clone了。
clone有機會再說吧!








2012年1月8日

Java 拆箱(unboxing)、裝箱(boxing)




這篇要講到java拆箱(unboxing)、裝箱(boxing)、自動裝箱(auto-boxing)、自動拆箱(auto-unboxing)的概念了,
我舉個小例子來說明何謂拆箱(unboxing)、裝箱(boxing)、自動裝箱(auto-boxing))、自動拆箱(auto-unboxing),
我用Integer和int來說明。
Integer A = new Integer(10);  // 裝箱
int B = A.intValue();   // 拆箱
B = A;    // 自動拆箱
A = 20;    // 自動裝箱
上面的行為在Compilr中,
A = 20; 會被自動轉換成 A = new Integer(20);
我們稱之為自動裝箱。

B=A;  會被自動轉換成 B = A.intValue();
稱之為自動拆箱


簡單的說,
把primitive轉成物件,稱之為裝箱,
把物件轉乘primitive,稱之為拆箱。




















Android:Facebook Login API




前面有介紹過怎麼在網站上,導入Facebook Login
這篇要介紹如何在Android 裝置上,實作Facebook Login!!

第一步】
首先先來到FB Developer後台,並點選Create New App(右上角),

【第二步】

【第三步】
這步驟主要是在你的project中,include Facebook的lib,這裡大概分兩種方式,
第一種,就是在你已經create好的project上,點右鍵→點properties→選擇Android,
然後最下方有一個Library,然後就可以點選add,再去第二步驟下載的Facebook SDK中找到lib即可。
第二種就是在第二步驟下載的SDK中,
找到  SDK\\facebook\src,
底下應該有一個com資料夾,把資料夾拉進去你的project即可,
(我比較喜歡第二種)
第二種的,你拉進去以後,會看到下面的畫面!(圖一)






【第四步】
先去下載OpenSSL,因為待會會用到它來encode hash key,
下載以後解壓縮出來。

【第五步】
記得之前有教過大家,怎麼用keytool產生key吧?(在教學map的時候有講到)
這裡一樣得利用keytool工具,
大家先打開cmd,
並且切換目錄至你的java sdk資料夾底下的bin!!! (是java sdk唷)
底下就會有一個keytool.exe


【第六步】
先看到下面的指令範例以及說明,

指令範例:
keytool -exportcert -alias your_key_alias -keystore path_of_keystore | path_of_OpenSSL sha1 -binary |  path_of_OpenSSL  base64

指令說明:
your_key_alias :是你key的alias
path_of_keystore :是你keystore的位置,如果這裡你是用.android底下預設的debug.keystore,那如果你Export Sign一個Android App時,是用另一把keystore,到時候如果上傳至market,你的FB登入可能就不能使用,所以必須用同一把,以Android App為主。
(但是!!!! 如果你是在本機開發測試,尚未上傳至market之前,大部分人應該都是直接點選eclipse上的綠色箭頭,然後部屬到Android裝置上吧? 如果是這樣,你的key,還是得用.android底下的預設keystore,因為按下Eclipse上的綠色箭頭,就是用預設的keystore打包的!!! 也就是說你得有兩把keysotre,一把是測試用,一把是正式上線用)
path_of_OpenSSL :剛剛下載的OpenSSL位置


真實指令(這是我的喔,你複製貼上是沒用的,要改成自己的)
keytool -exportcert -alias helloKen -keystore E:\myProject\M@C\key.keystore | C:\openssl-0.9.8h-1-bin\bin\openssl sha1 -binary | C:\openssl-0.9.8h-1-bin\bin\openssl base64

完成上面以後,你應該會得到2個key,一個是測試用,一個是正式上線時用的!




【第七步】
去Facebook Developer後台,輸入上面的2把key,
如下圖中的紅色框框,請於Native Android App中填入喔(圖二)




【第八步】
開始寫程式了!!
首先必須加入一個permission,
如下
<uses-permission android:name="android.permission.INTERNET"/>

接著宣告一個Facebook的物件!
且必須是global的喔!
如下:
Facebook facebook = new Facebook("YOUR_APP_ID");


下面就是登入的code,看你要寫在哪,例如:按下按鈕去登入(btn.setOnClickListener),或者一進畫面就登入(onCreate)
facebook.authorize(this, new DialogListener() {  
       @Override
       public void onComplete(Bundle values) {} //登入成功會返回至此method

       @Override
       public void onFacebookError(FacebookError error) {}

       @Override
       public void onError(DialogError e) {}

       @Override
       public void onCancel() {}
});





最後還有一個步驟!因為上面的code會去trigger一個視窗出來,也就是換畫面,
執行完,還會再返回,所以在原本的Actitivy中的onActivityResult中,必須加入下段code,

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        facebook.authorizeCallback(requestCode, resultCode, data);
    }





這樣就完成了一個在Android上實作Facebook Login!

















2012年1月6日

Android EditText remove focus

只要頁面中有用到EditText 的,
只要跑到那個頁面,一定會被自動focus,也就是說鍵盤會自動彈開!
要取消focus,只要在你layout中的root element加上下面兩個屬性,
(root element就是最外圍的那個element,預設都是LinearLayout)


android:focusable="true"
android:focusableInTouchMode="true"





2012年1月5日

實作Facebook Login 【新版】

今天發現前陣子有使用到Facebook登入的案子,
突然掛了! 不能使用Facebook登入了....
去查了一下,原來Facebook API改版....
老實說,我覺得這樣機制很差....應該也保留舊有機制,而不是連通知都沒有就改版了!

Ok, fine! 其實也沒有太大差別!
主要是改一些參數名稱!
至於前面的註冊手續都相同! 可以來前一篇看!


至於coding實作如下,
一樣得嵌入js檔,
<script src="http://connect.facebook.net/en_US/all.js"></script>

接著一樣要init,但是多了一個參數,就是oauth,以及從以前的response.session改成response.authResponse
<script>
 //先做init的動作,輸入自己的app id
 FB.init({ 
    appId:'你自己的app id', 
    cookie:true, 
    status:true, 
    xfbml:true,
    oauth : true // 多了這個參數
 });
 



 //下面是實作一個登入的function
function fnLoginFb(){
    FB.login(function(response) {
        //先判斷是否已經登入了,如果是,就直接
        if (response.authResponse){  
            FB.api('/me', function(response) {
                alert(response.email);
            });
        //下面是沒有登入時才會做的,會去subscribe一個event,就是去監聽一個login event(也就是說login成功以後,會接收到訊息)
        }else{
            FB.Event.subscribe('auth.login', function(response) {
                if (response.authResponse) {
                    FB.api('/me', function(response) {
                        alert(response.email);
                    });          
                }
            }); 
        }

    }, {scope:'email'});

} 



</script>

然後下面就是實作FB登入的按鈕了!