Pages

2010年5月31日 星期一

自動膚色偵測

  之前介紹過幾篇與膚色模型相關的文章,這次,我打算即時地去找符合當下的膚色分佈,為了因應在不同外在環境對膚色造成的影響,例如:白光、黃光等。

  採取的手段是分析手部區域的直方圖分佈,這個想法源自於:背景(人工)的分佈會比較均勻(狹長);而手掌(自然)的分佈會比較廣泛(常態)。例如下圖所示:這是在辦公室、日光燈光線充足的環境下,而手的分佈就落在右半邊。

  
  至於選擇的 color space 是 YCbCr 的 Cr channel,因為膚色比較偏黃、紅。下圖是整個演算法的流程圖,當中做了以下幾件事情:
  1. Histogram Statistics & Smooth。
  2. Find Global Maximum。
  3. Find Local Maximum。
  4. Find Valley (Local Minimum)。
  5. Set-up Threshold & Create Skin Model。

  以上所提的五個項目,當中參數的設置、做法,已經寫在投影片,在此不一一贅述,不過,由於測試的樣本數太少,會不會白光、黃光的分佈都這麼一致,還是個問號;另外「Set-up Threshold」的公式目前是:
  
  比較合理應該是除以 225 取平均,只是,我為了將指尖(比較亮、比較黃、數量比較少)也拉進來,才除上 1000,這是有待商榷的。最後,補上兩張測試結果:(因為是統計像素,所以最好是框得剛剛好的)

黃光(光線充足)

白光(光線充足)

程式碼與投影片下載

相關閱讀

2010年5月18日 星期二

不同手勢的 Fourier Descriptor

  繼之前觀察矩形在不同維度的傅立葉描述子之變化後,這次拿不同手勢來做實驗,一共有0、1、5三種手勢。從以下的影片可以發現,當手指頭越多,一團一團的包也會越多;另外,當維度越接近起始維度時,這些包就會比較像長條狀。##ReadMore##



  傅立葉描述子在頻率域擷取特徵,以描述物體的形狀。至於後續還可以研究(or改善)的部份有三個:
  1. 利用主成分分析,找出主要維度的特徵,以減少特徵比對所需的時間。
  2. 對特徵的各分量做正規化,例如:全部同除以第一個分量。如此可避免同種手形、不同大小(遠近)所造成的誤判。
  3. 觀察不同手形的傅立描述子的強度(spectrum),看看哪幾個分量比較大。這或許和 PCA 有異曲同工之妙(?)。

程式碼下載

相關連結

2010年5月17日 星期一

Nvidia 3D Vision

Nvidia 的 3D 顯示技術採用「Shutter Class」的方式,透過 IR-Emitter,讓眼鏡與 120Hz LCD,以每 1/120 秒的速度,更新左右眼看到的畫面。

  
至於把影像丟給顯示卡做繪圖的部分,是透過 DirextX 的 Direct3D APIs,所以,有了 Nvidia 的硬體配備,想要寫一個 3D 的應用程式,還得先把 Direct3D 入門一下。我有提供簡單的範例(ex: create image、camera preview)和投影片介紹,有興趣可以參考一下。

回到 Nvidia 3D Vision 的議題,他所提供的 solution 是:不管 3D 的來源為何,網路攝影機、3D video、3D game 等等,只要把左右眼的影像「side by side」放在 Direct3D 所提供的 surface 物件中,並透過指定的介面送給顯示卡,它就會做後續呈現的動作了。我把整個架構圖歸納如下:


另外,為了驗證來源是否合法,需要在 surface 下加入 signature,這也是許多人不知道的事情,若沒有加入此簽章,就會變成同時顯示兩張被延展的影像,也就是沒效果的意思。至於如何設定,看範例吧!


目前 Nvidia 釋出的驅動程式版本,在處理 3D 顯示,有全螢幕播放的限制,或許之後的版本就可以 window mode 播放了,如果真能實現,還可以結合 MSN、Skype 做 3D 視訊的應用。


程式碼與投影片下載


相關連結

2010年4月22日 星期四

利用 OpenCV 做人臉偵測

OpenCV 有提供人臉偵測的範例、以及 training model (.xml)。在稍作修改後,整個人臉偵測的流程如下:##ReadMore##
  1. 載入 model
    (CvHaarClassifierCascade*)cvLoad("haarcascade_frontalface_alt.xml");
  2. 對來源影像作 down-scale
    pyramidImg = cvCreateImage(cvSize(image->width/2,image->height/2), IPL_DEPTH_8U, 3);
    cvPyrDown(image, pyramidImg, CV_GAUSSIAN_5x5);
    scale = 2;
  3. 偵測人臉
    CvMemStorage* storage = cvCreateMemStorage(0);
    CvSeq *faces;
    faces = cvHaarDetectObjects(pyramidImg, cascade, storage, 1.2, 2, CV_HAAR_DO_CANNY_PRUNING);
  4. 將找到的人臉框出來 (or 移除)
    當偵測完畢,faces 會記錄人臉的位置,數量可能不只一個。在原先的範例,會針對每一個人臉的區域,再做偵測眼睛的動作,但我沒有這個需求,所以修改成

    • 找出最大的人臉區域:最大半徑。
    • 將它移除:避免干擾到手。
我是安裝 OpenCV2.0,裡頭的範例會使用 namespace cv,核心程式都是 object-base。執行OpenCV2.0 提供的人臉偵測時發現,CPU 使用率比舊版還高出 50% 左右,所以我就維持舊版的寫法,當然,想在 1.0、1.1 執行都沒問題,只要記得把 link library 改掉,例如:「cv200.lib -> cv.lib」。底下是執行影片:(Resolution 320x240、CPU 使用率 30%、Debug mode)




程式碼下載

參考資料

2010年3月15日 星期一

Arm-removal

  在參考互動雙足式機器人之設計與實現這篇論文後,實作了去手臂的模組,目的是避免含有手臂的手會大大降低辨識率。該方法採用逆時針旋轉來統計每個膚色點與重心的距離,如下圖所示:(引用論文圖片)


  這種直方圖統計的方式在影像處理課本第十章:表示與描述就看的到。所以,有了這個觀念,剩下的我想怎麼做就怎麼做囉!底下是整個去手臂的流程:


整個流程有幾點補充:
  1. Histogram Statistics:水平為角度、垂直為最遠距離。
  2. Low-pass filter:用 7x7 的遮罩,做二值影像的低通濾波。
  3. Post-processing:在統計每個膚色點與重心的角度,會因為計算誤差,使相鄰點得到同樣的角度,導致直方圖某些角度沒有值,所以我做了平滑化。
  4. threshold1、threshold2:前者用來決定是否有手臂存在;後者則是決定去手臂的程度。
  5. Find valley:這部份需配合下圖作說明,當決定要去手勢時,就開始往兩側找山谷,也就是傾斜度逐漸平緩的點。有時候坡度會緩和一下又繼續往下降,加入 threshold2,目的就是希望找到比較正確的山谷。

  另外,我將去手臂的目標鎖定在下半圓(180~359),也就是預設手不會從上面垂下來,這樣可以避免五指併攏被判斷成手臂的問題。

程式碼下載

2010年1月4日 星期一

Motion Detection

  「Motion Detection」是第一次接觸電腦視覺時遇到的問題,這個算法試圖從連續影像中找出「移動物體」,處理上可以從相鄰影像、或者以一張背景影像為基礎,而我是採用前者,過程如下:(門檻值設為 20)


  接著就是如何從這張二值影像找出物體,這部分可以參考「Digital Image Processing 2/e」一書中的第 11 章:表示與描述,當中提到「Chain Code」與「Polygonal Approximation」,至於我實作的方法是「Connected Component」,非上述兩者...XD。

  最後一個要考量的議題是如何取捨這些移動物體,我的作法是傳入一個 function pointer 給 motion detector 物件來使用,這樣一來,就可以自行決定 filter condition,function pointer prototype 如下:
typedef bool (*filter)(int cond1, int cond2, int threshold1, int threshold2);

實作範例
bool MyFilter(int cond1, int cond2, int threshold1, int threshold2)
{
if(cond1 < (threshold1/16) || cond2 < (threshold2/16)) return true;
else return false;
}

程式碼下載

參考資料:


2009年12月17日 星期四

找出二值影像的邊緣並序列化

  標題提到的「序列化」,意思是依序記錄邊緣上的點,以便之後取出該物體的 Fourier Descriptor。找邊緣的演算法很多:canny、snake 等,不過這些我都不會,所以我先用了 sobel 來做,但由於 sobel 會讓物體的邊緣「增厚」,這樣對於我之後的序列化會有困難。所以必須換別的做法。


  因為來源是二值影像,所以這邊想到一個更簡潔,效果我覺得一定好的方式 (自我感覺良好:P),整個過程分三步驟:
  1. 對影像做水平掃描,若像素 A 的左或右為背景,則像素 A 為邊緣。
  2. 對影像做垂直掃描,若像素 A 的上或下為背景,則像素 A 為邊緣。
  3. 將步驟一得到的水平影像和步驟二的垂直影像做「logical OR operation」。
  這樣一來,就可以將物體的邊緣找出來,且不會有邊緣增厚的問題發生,接下來就是「序列化」的動作。因為物體是連通,所以邊緣不會有斷掉的問題發生,我只要從某一點出發,朝周圍八個方向走訪即可〈Depth First Search 的應用〉,就是一定可以走出迷宮的老鼠..^_^。



程式碼下載 (DFS is non-recursive version)

判斷兩矩形是否重疊

  這個概念其實和高中數學提到的兩圓交於兩點一樣意思,差別只在矩形有兩個變量:寬與高。不過判斷上同樣簡單,只要計算出兩矩形中心的水平距離和鉛直距離,並滿足以下條件##ReadMore##
(distHor < (W1/2 + W2/2) && distVer < (H1/2 + H2/2))


程式碼下載 (矩形要自己框,框完一個就按任意鍵,再框下一個)

2009年12月16日 星期三

Fourier Descriptor

  在「Digital Image Processing 2/e」一書中的第 11 章:表示與描述〈Reprentation and Description〉,裡頭介紹幾種描述物體的方法,例如:鏈碼〈chain code〉、骨架〈skeletonizing〉、邊界描述子〈boundary descriptor〉、紋理〈texture〉等,這些動作其實就是在找物體的特徵,一旦決定了特徵,才能透過辨識引擎去做訓練與辨識。

  上述提到的邊界描述子有幾種描述方式,其中一種稱作「Fourier Descriptor」,這裡先做個簡單的介紹。這個方法是透過一維傅立葉轉換,將邊界資料轉換後,取其頻譜〈spectrum〉來當作特徵。有學過影像處理應該都知道,傅立葉轉換的高頻部份用來描述邊緣、細節、甚至是雜訊;低頻則是描述平滑區域、整體形狀。所以在實際應用上,就是把原始的 N 筆邊界資料,轉換後,取其前 M 筆的頻譜值來當特徵〈M <= N〉。      

  至於該如何量化邊界資料,其實課本說明得很清楚,我們只要把每個點 (x,y) 「視為」x+yi,也就是複數平面上的點,接著,從某點出發,順(或逆)時針依序記錄下來即可。說得更明白一點就是用 Complex[] 陣列紀錄啦!至於將每個點看成一個複數,好處有二(我猜的):
  1. 不改變物體形狀。
  2. 用一維傅立葉即可。(降維度的味道,因為本來是影像嘛^_^)

  因為這個演算法蠻簡單得,我就自己實作出來,並且驗證課本的正方形範例,不過,要注意一點,由於程式中的座標系和數學上的直角坐標系,Y 座標是上下顛倒,所以需要轉換一下。






程式碼下載

2009年12月15日 星期二

cv::Mat 與 IplImage 之間的轉換

從「OpenCV 1.1pre」版本開始對以前的 API,以 c++ 包裝,定義成 namespace cv,這意味著想寫 c-style 或 c++ style 的程式皆可。所以,在處理影像的部份,就有「cv::Mat」和「IplImage」,至於該如何轉換,請看以下程式碼片段:

IplImage -> cv::Mat
IplImage* iplImg = cvLoadImage("lena.jpg");
cv::Mat cvMatImg(iplImg, 0);
cv::imshow("cv::Mat Show", cvMatImg);

cv::Mat -> IplImage
IplImage iplImg2;
iplImg2= IplImage(cvMatImg);
cvShowImage("IplImage2 Show", &iplImg2);
在第一個範例中,我們可以看到 cv::Mat 的建構子有兩個參數,比較重要的是第二個參數,如果設定成 0,代表不複製影像,也就是兩個變數的 row data 共用同個記憶體位置,header 則各自有。

接著,第二個範例,從第二行可以看到 cv::Mat 呼叫成員函式 operator IplImage(),至於行為上就沒得選擇,一定是 row data 共用同個記憶體位置,header 各自有。

底下是 operator IplImage() 的實作內容,定義在 cxmat.hpp;類別則宣告在 cxcore.hpp...
inline Mat::operator IplImage() const
{
IplImage img;
cvInitImageHeader(&img, size(), cvIplDepth(flags), channels());
cvSetData(&img, data, (int)step);
return img;
}
 
Blogger Templates