星期一, 5月 21, 2012

Week 15: 物件追蹤的技術

1. 特定色彩之過濾

    a. YUV 色彩模型: Y 表亮度, 與光線強弱有關, UV 表色彩, 與物件本身色彩有關。

    b. 膚色: 90 < U < 120 且 120 < V < 150  延伸閱讀: 猜拳機 (國立中山大學電機系 曹文潔 碩士論文)

2. 背景相減法: 透過視訊畫面與背景影像的相減, 可以得知物件出現於視訊擷取視窗的位置。

    a. 如何利用預覽回呼函數建立背景影像?

    [背景影像的相關理論]

在攝影機不動的情況下拍攝靜物, 照理說, 連續兩個畫面(frame)應該是完全一樣的; 然而, 寫程式分析, 卻發現兩張畫面看起來一樣, 實際上卻有稍稍不同, 色彩值因為光線變化 (也許是因為光源閃爍, 或是空氣中的灰塵影響。), 讓不同畫面中, 相同位置的色彩值產生細微的不同。

假設在沒有雜訊的情況下, 我們所拍攝到的色彩值是 F(i,j)
第 k 個畫面的位置 (i,j) 上, 雜訊的函數為 Xk(i,j),

假設我們所擷取到的色彩值為 Fk(i,j), 因此,

Fk(i,j) = F(i,j) + Xk(i,j)

如果我們取連續 30 個畫面色彩值之總和,

F1(i,j) + F2(i,j) + ... + F30(i,j)
= [F(i,j) + X1(i,j)] + [F(i,j) + X2(i,j)] + ... + [F(i,j) + X30(i,j)]
= 30 F(i,j) + [ X1(i,j) + X2(i,j) + ... + X30(i,j) ]

如果雜訊的平均值是 0, 即

X1(i,j) + X2(i,j) + ... + X30(i,j) = 0 

再將上述式子除以 30 求平均, 那麼我們就可以得到 F(i,j)。

3. 範例程式 Webcam Programming (5)

範例程式 Webcam Programming (5) 的主題是物件追蹤, 換句話說, 就是如果在視訊畫面中, 出現了我們要追蹤的物件, 我們必須要能的知道物件出現的位置為何?

同樣地, 我們把範例程式 Webcam Programming (5) 分成三個小主題, 分別如下:

part A: 特定色彩之過濾
part B: 利用預覽回呼函數建立背景影像
part C: 差異偵測

我們在範例程式 Webcam Programming (5) 的 Preview Callback Function 功能列中, 建立了 Skin Color Detection、Background、Difference Detection 等 3 個功能, 分別對應到上述的三個小主題。圖 5-A-1 顯示了所建立的 Preview Callback Function 功能列。


圖 5-A-1

part A: 特定色彩之過濾

由於顏色是與 U、V 相關, 因此我們宣告了 4 個變數, 用來表示所要追蹤的顏色範圍。宣告指令如下:

int iMinU = 90, iMaxU = 120;
int iMinV = 120, iMaxV = 150;

然後, 在 Capture Window 分頁中, 我們新增了 4 個 TEdit 元件, 提供給使用者修改 U、V 色彩値的上界與下界。圖 5-A-2 顯示了新增的 4 個 TEdit 元件。當使用者改變了 TEdit 元件的値 (Text Property), 在事件處理程序中, 只要使用 StrToIntDef 函式, 就可以將 Text Property 由 string 資料型態轉換成 int 資料型態, 再設定成 U、V 色彩値的上界或下界。


圖 5-A-2

將色彩範圍設定為 90 < U < 120 且 120 < V < 150, 當像素點色彩滿足此範圍, 就將其像素色彩値改成綠色模式 ( Y 値保留不變, U、V 設定為綠色 ), 圖 5-A-3 則是顯示執行結果, 似乎很多不是膚色的區域都被標示成綠色模式了。


圖 5-A-3

part B: 利用預覽回呼函數建立背景影像

首先, 我們必須宣告兩個陣列, 一個用來儲存背景影像, 另一個則是作為累加影像之用。

unsigned char ucBackground[480][640][3];
int iTemp[480][640][3];

在程式尚未進入預覽回呼函數之前, 必須對上述陣列進行初始值設定, 並將 iFrameCounter 設定為 0。然後, 設定(打開)預覽回呼函式為 FrameCallbackBackgroundYUY2。

進入預覽回呼程序之後, 首先累加 iFrameCounter, 接著程式主要分成兩個部份:
情況(1) iFrameCounter 小於等於 30;
情況(2) iFrameCounter 大於 30 (其實就是等於 31 )。

情況(1) 的主要工作是累加影像色彩值到 iTemp 陣列之中, 在累加之前, 必須先將 YUY2 資料流中的 macro-pixel 的 YUYV 轉換回 RGB 色彩值。值得一提的是轉回 RGB 色彩值時, 可能會發生溢位(overflow)的情況, 即 RGB 色彩值超過 255 或小於 0, 必須強制 RGB 色彩值在 0 與 255 的範圍之間(clipping)。

情況(2) 的主要工作是將累加陣列中的值除以 30 , 求取平均值, 即為所建立的背景影像, 並將背景影像的色彩值放到imBackground 影像元件的記憶體之中( 透過 ScanLine Property 得知每一列的起始記憶體位址 )。最後, 使用影像元件的 Refresh() Method 就可以在 imBackground 看到所建立的背景影像了, 如圖 5-B-1。


圖 5-B-1 背景影像 ( 30 個畫面平均 )

圖 5-B-1 是利用回呼函數連續擷取 30 個畫面相加後, 再計算平均値所得到的背景影像。圖 5-B-2 則是用 GrabFrame 函數擷取單張畫面, 再用 File Save DIB 存檔的影像。兩張圖片相差不大, 這是由於環境光線穩定的緣故, 若是使用會閃爍的光源, 差異就會比較明顯。


圖 5-B-2 擷取單一畫框存檔

延伸思考: 針對 YUY2 格式的網路攝影機, 是否可以直接在 YUV 色彩模式中累加取平均值後, 得到 YUV 模式的背景影像? 若是需要在 C++ Builder 的影像元件中顯示, 可以再從 YUV 轉成 RGB 存到影像元件的記憶體!

根據理論推導與寫程式實驗發現, 確實是可以直接計算 YUV 色彩模型的平均影像, 用來當作背景影像。

將兩種方式分別得到的背景影像相減, 取絕對植後, 在影像元件中顯示出來, 確實呈現了一張很黑的畫面, 如圖 5-B-3 所示。但仔細分析其中的色彩値, 並非完全為 0, 最大値誤差值為 2, 如圖 5-B-3 所呈現的在座標 (270, 303) 的像素點, 其 RGB 色彩値為 (1, 1, 2), 表示兩張背景影像在座標 (270, 303) 色彩値中, 紅色相差 1, 綠色相差 1, 藍色相差 2。這些誤差是電腦本身在浮點資料表示的本來就有所極限所引發的截斷誤差(truncation error), 加上在計算上的四捨五入的捨入誤差(rounding error) 所造成。


圖 5-B-3 YUY2 格式攝影機用兩種方式分別計算背景影像, 再將兩張影像相減所的到的差異影像。

星期二, 5月 15, 2012

Week 14: 將圖片置入視訊擷取視窗之中 (2)

1. 物件偵測原理介紹
    a. 膚色偵測法
    b. 背景法

2. 歷屆期末專案介紹

3. 範例程式 Webcam Programming (4)

part D: 透過預覽回呼函數將圖片置入視訊擷取視窗之中

2009S 的課程中, 我花了很多時間在網路上找龍珠的圖片, 最後終於在 伊莉討論區 的這張圖中找到, 如圖 4-D-1。


圖 4-D-1

然後用 Photoshop 把龍珠擷取出來, 並且, 將四星龍珠, 一一修改成下列一星至七星龍珠。由於龍珠圖片中, 左上及右下各有一塊白色區域, 因此若背景色設定為白色, 將來將龍珠放進視訊時, 這兩塊白色區域就會也一併變成背景, 無法呈現出來, 因此, 我們將背景色改成純紅色。如圖 4-D-2。


圖 4-D-2

接著, 新增一個 TImage 元件到 Capture Window 分頁之中, 如圖 4-D-3。


圖 4-D-3

並將其 Name Property 改為 imDragonBall4, 然後點選兩下 Property Picture, 開啟如圖 4-D-4 的畫面, 並按 Button Load... 將四星龍珠讀取進來。


圖 4-D-4

最後, 按下 Button OK 之後, 就會呈現圖 4-D-5 的畫面, 四星龍珠已經顯示在影像元件 imDragonBall4 之中了。


圖 4-D-5

如果繼續將影像元件 imDragonBall4 的 Property Transparent 設定為 true, 這時四星龍珠圖片的背景色 - 紅色, 就不會顯示出來, 如圖 4-D-6。


圖 4-D-6

將影像載入(Load)之後, 還必須先將影像的色彩資料用陣列儲存起來備用。因此, 必須宣告一個四維陣列, 來儲存七顆龍珠的色彩資料。

unsigned char ucDragonBallRGB[7][50][50][3];
unsigned char ucDragonBallYUV[7][50][50][3];

然後將色彩讀取到陣列的程式, 寫在功能表 preview callback function | Object | Image 的事件處理程序中, 如圖 4-D-7 的功能表 。詳見範例程式 Webcam Programming (4), 程式中比較特殊的指令是 ScanLine[j]。透過 ScanLine 的位址, 可以將存在影像元件的像素色彩値讀取出來, 放到預先宣告的陣列 ucDragonBallRGB 之中。然後再將其轉換成 YUV 色彩値存入與先宣告的 ucDragonBallYUV 陣列之中。


圖 4-D-7

在預覽回呼函數 FrameCallBackImage 要注意的是: 只要介於座標 (iObjectX1, iObjectY1) 與 (iObjectX2, iObjectY2) 之間的區域就要置入龍珠圖片, 但龍珠圖片的背景區域必須忽略, 無須置入。如此, 就可以完成將龍珠圖片放進視訊擷取視窗了, 如圖 4-D-8。


圖 4-D-8

接下來, 要示範的是: 無須更改預覽回呼函數, 就可以隨使用者的設定, 變換在視訊擷取視窗的圖片的程式寫法。先將七顆龍珠的圖片, 放到視訊擷取視窗的分頁之中, 然後宣告一個變數來儲存使用者所選定的龍珠編號。

int iDragonBallID;

當使用者選定了某顆龍珠, 只要將變數 iDragonBallID 改成使用者所選定的編號即可。然後將預覽回呼函數 FrameCallBackImage 中, 原先寫定的値, 用變數取代, 這樣就可以讓使用者隨時改變要顯示的龍珠了, 如圖 4-D-9。


圖 4-D-9

延伸思考: 如何讓龍珠動起來?

4. 作業三(a) : 為你的視訊製作一個美麗的畫框, 如圖 4-D-10。

Step 1: 製作一張 640*480 的畫框 bmp 影像, 並選定特定顏色為顯示視訊區域之特定顏色。

Step 2: 在 C++ Builder 新增一個 TImage 影像元件, 並將畫框影像載入影像元件之中。

Step 3: 宣告一個陣列作為存放影像色彩值之用。

Step 4: 在設定預覽回呼函數之前, 將影像色彩值讀入陣列之中。

Step 5: 撰寫預覽回呼函數
提醒: 視訊大小與畫框大小目前是一樣大的, 因此只要去判斷畫框的顏色是否為特定顏色即可, 只要是特定顏色, 就顯示視訊內容, 否則, 則顯示畫框內容。


圖 4-D-10

作業三(b) : 將視訊擷取視窗設定成特定色調, 如棕色色調的復古模式, 藍色色調的海洋模式, 綠色色調的大自然模式...等等。

星期二, 5月 08, 2012

Week 13: 將圖片置入視訊擷取視窗之中 (1)

1. RGB 轉成 YUV 之公式

iY = (( 66 * iR + 129 * iG + 25 * iB + 128) >> 8) + 16;
iU = (( -38 * iR - 74 * iG + 112 * iB + 128) >> 8) + 128;
iV = (( 112 * iR - 94 * iG - 18 * iB + 128) >> 8) + 128;

2. 範例程式 Webcam Programming (4)

part C: 透過預覽回呼函數在視訊畫面中填入單一顏色的長方塊

part C 與 part D 都是要在視訊擷取視窗中加入物件, 因此, 在 Preview callback function | Object 建立 submenu。如圖 4-D-1, 在 Preview callback function | Object | Single Color Object 中, 視訊擷取視窗中將會被加入一個單色的矩形物件; 在 Preview callback function | Object | Image 中, 視訊擷取視窗中則會被加入一張影像。


圖 4-C-1

在使用以下指令設定好 preview callback function 之後,

capSetCallbackOnFrame(hwndVideo, &FrameCallBackSingleColorObject);

必須處理兩個問題, 第一, 矩形物件放置的位置與矩形物件的大小; 第二, 矩形的顏色。所以, 必須宣告公共變數來存放這些設定。

關於矩形物件的位置, 宣告 iObjectX1, iObjectY1 兩個變數來存放其左上角座標,

int iObjectX1, iObjectY1;

宣告 iObjectWidth, iObjectHeight 兩個變數來存放矩形物件的大小,

int iObjectWidth, iObjectHeight;

由於程式必須使用到物件的右下角座標, 因此再宣告 iObjectX2, iObjectY2 兩個變數來存放矩形物件的右下角座標,

int iObjectX2, iObjectY2;

在範例程式 Webcam Programming (4) 中, iObjectX2, iObjectY2 的計算方式如下:

iObjectX2 = iObjectX1 + iObjectWidth - 1;
iObjectY2 = iObjectY1 + iObjectHeight - 1;

關於矩形物件的顏色, 如果攝影機的視訊格式(Video Format) 是屬於 YUV 系列, 如 YUY2, 那就必須將 RGB 色彩轉換成 YUV 色彩, 在範例程式 Webcam Programming (4) 中, 宣告 iR, iG, iB 三個變數來存放 RGB 色彩値, 再另外宣告 iY, iU, iV 三個變數來存放 YUV 色彩値。

int iR, iG, iB;
int iY, iU, iV;

RGB 色彩轉換成 YUV 色彩的整數型C 語言轉換公式如下:

iY = (( 66 * iR + 129 * iG + 25 * iB + 128) >> 8) + 16;
iU = (( -38 * iR - 74 * iG + 112 * iB + 128) >> 8) + 128;
iV = (( 112 * iR - 94 * iG - 18 * iB + 128) >> 8) + 128;

最後, 就可以在回呼函數 FrameCallBackSingleColorObject 中, 在視訊擷取視窗的緩衝區記憶體的適當位置填入 YUV 色彩値了。做法請參閱範例程式 Webcam Programming (4)。程式的執行結果如圖 4-D-2。


圖 4-C-2

接下來, 我們要提的問題是: 如何新增一個功能, 讓使用者可以隨時修改矩型物件的色彩呢?

C++ Builder 的 Tool Palette 的 Dialog 類別之中, 有一個 TColorDialog 元件, 只要執行 Execute() Method, 就可以呼叫出一個讓使用者選擇色彩的對話盒, 選定所要的顏色之後, 被選定顏色的色彩値, 就會被儲存在 TColorDialog 元件的 Color Property 之中。因此, 在 Dialog 功能表中, 我們新增了一個 Color 功能, 如圖 4-D-3。


圖 4-C-3

範例程式 Webcam Programming (4) 提供如何分別使用 GetRValue, GetGValue, GetBValue 函式, 將儲存在 Color Property 中的色彩値轉換成所需的 iR, iG, iB。圖 4-D-4 顯示所呼叫出來的色彩對話盒及執行結果。


圖 4-C-4


3. 作業預告:

星期二, 5月 01, 2012

Week 12: 預覽回呼函數 preview callback function

1. 肢體感應遊戲的歷史

a. Sony PS2 Eye Toy
b. Wii
c. XBox Kinect

2. 預覽回呼函數 preview callback function

當視訊擷取驅動程式接收到來自視訊裝置的畫面(frame), 在 preview 模式之下, 會將此畫面顯示在所連結的視訊視窗之中。

我們可以透過 capSetCallbackOnFrame 這個函數來指定當發生 frame preview 之前, 先執行一個特定的 callback function, 即預覽回呼函數。這個預覽回呼函數的名稱可以由我們自由命名, 但是傳入的參數個數與其資料型態則必須符合 VFW 的規定。

// 使用者自行寫的預覽回呼函數必須在公用變數宣告區宣告
LRESULT CALLBACK FrameCallBack( HWND hwndCapture, PVIDEOHDR lpvhdr );

// 設定 callback function 與視訊擷取視窗之間的連結,
// 請將此行放到開啟視訊擷取視窗, 並開始 preview 的指令後

capSetCallbackOnFrame(hwndCapture, &FrameCallBack);

3. 如何關閉預覽回呼函數?

capSetCallbackOnFrame(hwndVideo, NULL);

4. VFW 函式介紹:

a. 取得視訊畫面基本資料 (capGetVideoFormat)

b. BITMAPINFO structure

c. BITMAPINFOHEADER structure

5. 範例程式 Webcam Programming (4): 預覽回呼函數

範例程式 Webcam Programming (4) 主要分成五個部分:
part A: 預覽回呼函數之設定與關閉
part B: 預覽回呼函數與視訊資料表頭結構
part C: 透過預覽回呼函數將預覽畫面變成灰階畫面
part D: 透過預覽回呼函數在視訊畫面中填入單一顏色的長方塊
part E: 透過預覽回呼函數將圖片置入視訊擷取視窗之中

part A: 預覽回呼函數之設定與關閉

本部分的主要目的是讓同學了解視訊擷取視窗之預覽回呼函數的設定與使用方法。要設定預覽回呼函數, 是透過 capSetCallbackOnFrame 函式完成的, 函式的輸入參數有二, 視訊擷取視窗的代碼(handle)與所設定預覽回呼函數之位址。當程式開啟視訊擷取視窗後, 顯示預覽的畫面總數到 TLabel 元件上。


圖 4-A-1


圖 4-A-2

part B : 預覽回呼函數與視訊資料表頭結構



圖 4-B-3


圖 4-B-4


part C : 透過預覽回呼函數將預覽畫面變成灰階畫面 

part C 的目的是讓同學有能力存取視訊擷取視窗的緩衝區記憶體中的畫面資料。


圖 4-B-5

延伸想法: 可以將視訊畫面變成特定色調嗎?



請參考 影像處理課程作業三。當然也可以透過 ColorDialog 讓使用者選定顏色後, 計算出所選定顏色的 YUV 値, 再將 UV 値放進 YUY2 攝影機的緩衝區記憶體, 就大功告成了!

補充資料: RGB pixel formats