Pages

2009年12月17日 星期四

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

  標題提到的「序列化」,意思是依序記錄邊緣上的點,以便之後取出該物體的 Fourier Descriptor。找邊緣的演算法很多:cannysnake 等,不過這些我都不會,所以我先用了 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;
}

製作簡單圖表

開發過程中,常常需要偵錯,方法不外乎下中斷點、印訊息、寫檔,但想動態地觀察資料,就需要視覺化的呈現,像「ZedGraph」和「OWC11」都是不錯的選擇。但殺雞焉用牛刀,所以就自己寫個函式,配合 OpenCV 作呈現(因為他跨平台嘛)。

因為我的需求非常簡單,只要幫助我觀察資料就好,所以實作上非常簡單,在此不多加贅述,只要說明一下函式原型就好:

Broken Line(點我一下>///<)
bool BrokenLineGraph(IplImage *blImg, int *data,
int *range, int dataNum, bool reset);
  1. 1st parameter:秀資料的圖檔。
  2. 2nd parameter: 原始資料。
  3. 3th parameter:資料範圍,range[0] = min;range[1] = max。
  4. 4th parameter:資料量。
  5. 5th parameter:是否刷新圖檔。

Histogram
(點我一下>///<)
bool HistogramGraph(IplImage *hgImg, int *data,
int *range, int dataNum, bool reset, CvRNG *rng);
  1. 1st ~ 5th:同上。
  2. 6th parameter:亂數種子,用來取色。 
所以是非常簡單,對我而言夠用了。至於餵進來的資料多寡,會影響繪圖的結果,所以要提醒一點,圖檔寬度要大於等於資料量,或者分批丟進來!

為了加強跨平台的特性、與維持原始碼的"乾淨",我使用 CMake 這個工具來產生 makefile,不過我只完成一半(windows),有興趣的可以把另一半完成,至於詳細語法可以自行 google。

程式碼下載

參考資料:
 
Blogger Templates