OpenCV 常用算法

OpenCV 常用算法

<script>function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>  
6月8日

關於直方圖

    圖像直方圖是圖像處理中一種十分重要的圖像分析工具,它描述了一幅圖像的灰度級內 容。
   
    從數學上來說圖像直方圖是圖像各灰度值統計特性與圖像灰度值的函數,它統計一幅圖像中各個灰度級出現的次數或概率;
   
    從圖形上來說,它是一個二維圖, 橫座標表示圖像中各個像素點的灰度級,縱座標爲各個灰度級上圖像各個像素點出現的 次數或概率。任何一幅圖像的直方圖都包含了豐富的信息,它主要用在圖象分割,圖像灰度變換等處理過程中。
   
    最合適直方圖的典型特徵是從左端到右端的每一個輝度級別都有相對應的圖形存在,也就是說,值是平均分佈的,而且圖像應該服從正態分佈,即中間大,兩頭小的情況。
 

1.正常曝光的照片

2.
曝光不足的照片


3.
曝光過渡的照片

4.
反差過低的照片

5.
反差過高的照片

 
 

    
    沒有任何兩幅圖像的直方圖是完全一樣的。 因此,利用直方圖可以在目標跟蹤時有所作爲。
6月1日

你夠魯棒嗎

魯棒是Robust的音譯,也就是健壯和強壯的意思。
魯棒性(robustness)就是系統的健壯性。它是在異常和危險情況下系統生存的關鍵。比如說,計算機軟件在輸入錯誤、磁盤故障、網絡過載或有意攻擊情況下,能否不死機、不崩潰,就是該軟件的魯棒性。所謂“魯棒性”,是指控制系統在一定(結構,大小)的參數攝動下,維持某些性能的特性。根據對性能的不同定義,可分爲穩定魯棒性和性能魯棒性。以閉環系統的魯棒性作爲目標設計得到的固定控制器稱爲魯棒控制器。

4月12日

OpenCV學習筆記(五)卡爾曼濾波器

卡爾曼濾波器 Kalman Filter

1    什麼是卡爾曼濾波器

What is the Kalman Filter?

在學習卡爾曼濾波器之前,首先看看爲什麼叫“卡爾曼”。跟其他著名的理論(例如傅立葉變換,泰勒級數等等)一樣,卡爾曼也是一個人的名字,而跟他們不同的是,他是個現代人!

卡爾曼全名Rudolf Emil Kalman,匈牙利數學家,1930年出生於匈牙利首都布達佩斯。19531954年於麻省理工學院分別獲得電機工程學士及碩士學位。1957年於哥倫比亞大學獲得博士學位。我們現在要學習的卡爾曼濾波器,正是源於他的博士論文和1960年發表的論文《A New Approach to Linear Filtering and Prediction Problems》(線性濾波與預測問題的新方法)。如果對這編論文有興趣,可以到這裏的地址下載: http://www.cs.unc.edu/~welch/media/pdf/Kalman1960.pdf

簡單來說,卡爾曼濾波器是一個“optimal recursive data processing algorithm(最優化自迴歸數據處理算法)”。對於解決很大部分的問題,他是最優,效率最高甚至是最有用的。他的廣泛應用已經超過30年,包括機器人導航,控制,傳感器數據融合甚至在軍事方面的雷達系統以及導彈追蹤等等。近年來更被應用於計算機圖像處理,例如頭臉識別,圖像分割,圖像邊緣檢測等等。

2.卡爾曼濾波器的介紹

Introduction to the Kalman Filter

爲了可以更加容易的理解卡爾曼濾波器,這裏會應用形象的描述方法來講解,而不是像大多數參考書那樣羅列一大堆的數學公式和數學符號。但是,他的5條公式是其核心內容。結合現代的計算機,其實卡爾曼的程序相當的簡單,只要你理解了他的那5條公式。

在介紹他的5條公式之前,先讓我們來根據下面的例子一步一步的探索。

假設我們要研究的對象是一個房間的溫度。根據你的經驗判斷,這個房間的溫度是恆定的,也就是下一分鐘的溫度等於現在這一分鐘的溫度(假設我們用一分鐘來做時間單位)。假設你對你的經驗不是100%的相信,可能會有上下偏差幾度。我們把這些偏差看成是高斯白噪聲(White Gaussian Noise),也就是這些偏差跟前後時間是沒有關係的而且符合高斯分配(Gaussian Distribution)。另外,我們在房間裏放一個溫度計,但是這個溫度計也不準確的,測量值會比實際值偏差。我們也把這些偏差看成是高斯白噪聲。

好了,現在對於某一分鐘我們有兩個有關於該房間的溫度值:你根據經驗的預測值(系統的預測值)和溫度計的值(測量值)。下面我們要用這兩個值結合他們各自的噪聲來估算出房間的實際溫度值。

假如我們要估算k時刻的是實際溫度值。首先你要根據k-1時刻的溫度值,來預測k時刻的溫度。因爲你相信溫度是恆定的,所以你會得到k時刻的溫度預測值是跟k-1時刻一樣的,假設是23度,同時該值的高斯噪聲的偏差是5度(5是這樣得到的:如果k-1時刻估算出的最優溫度值的偏差是3,你對自己預測的不確定度是4度,他們平方相加再開方,就是5)。然後,你從溫度計那裏得到了k時刻的溫度值,假設是25度,同時該值的偏差是4度。

由於我們用於估算k時刻的實際溫度有兩個溫度值,分別是23度和25度。究竟實際溫度是多少呢?相信自己還是相信溫度計呢?究竟相信誰多一點,我們可以用他們的covariance來判斷。因爲Kg^2=5^2/(5^2+4^2),所以Kg=0.78,我們可以估算出k時刻的實際溫度值是:23+0.78*(25-23)=24.56度。可以看出,因爲溫度計的covariance比較小(比較相信溫度計),所以估算出的最優溫度值偏向溫度計的值。

現在我們已經得到k時刻的最優溫度值了,下一步就是要進入k+1時刻,進行新的最優估算。到現在爲止,好像還沒看到什麼自迴歸的東西出現。對了,在進入k+1時刻之前,我們還要算出k時刻那個最優值(24.56度)的偏差。算法如下:((1-Kg)*5^2)^0.5=2.35。這裏的5就是上面的k時刻你預測的那個23度溫度值的偏差,得出的2.35就是進入k+1時刻以後k時刻估算出的最優溫度值的偏差(對應於上面的3)。

就是這樣,卡爾曼濾波器就不斷的把covariance遞歸,從而估算出最優的溫度值。他運行的很快,而且它只保留了上一時刻的covariance。上面的Kg,就是卡爾曼增益(Kalman Gain)。他可以隨不同的時刻而改變他自己的值,是不是很神奇!

下面就要言歸正傳,討論真正工程系統上的卡爾曼。

3    卡爾曼濾波器算法

The Kalman Filter Algorithm

在這一部分,我們就來描述源於Dr Kalman 的卡爾曼濾波器。下面的描述,會涉及一些基本的概念知識,包括概率(Probability),隨即變量(Random Variable),高斯或正態分配(Gaussian Distribution)還有State-space Model等等。但對於卡爾曼濾波器的詳細證明,這裏不能一一描述。

首先,我們先要引入一個離散控制過程的系統。該系統可用一個線性隨機微分方程(Linear Stochastic Difference equation)來描述:

X(k)=A X(k-1)+B U(k)+W(k)

再加上系統的測量值:

Z(k)=H X(k)+V(k)

上兩式子中,X(k)k時刻的系統狀態,U(k)k時刻對系統的控制量。AB是系統參數,對於多模型系統,他們爲矩陣。Z(k)k時刻的測量值,H是測量系統的參數,對於多測量系統,H爲矩陣。W(k)V(k)分別表示過程和測量的噪聲。他們被假設成高斯白噪聲(White Gaussian Noise),他們的covariance 分別是QR(這裏我們假設他們不隨系統狀態變化而變化)。

對於滿足上面的條件(線性隨機微分系統,過程和測量都是高斯白噪聲),卡爾曼濾波器是最優的信息處理器。下面我們來用他們結合他們的covariances 來估算系統的最優化輸出(類似上一節那個溫度的例子)。

首先我們要利用系統的過程模型,來預測下一狀態的系統。假設現在的系統狀態是k,根據系統的模型,可以基於系統的上一狀態而預測出現在狀態:

X(k|k-1)=A X(k-1|k-1)+B U(k) ……….. (1)

(1)中,X(k|k-1)是利用上一狀態預測的結果,X(k-1|k-1)是上一狀態最優的結果,U(k)爲現在狀態的控制量,如果沒有控制量,它可以爲0

到現在爲止,我們的系統結果已經更新了,可是,對應於X(k|k-1)covariance還沒更新。我們用P表示covariance

P(k|k-1)=A P(k-1|k-1) A+Q ……… (2)

(2)中,P(k|k-1)X(k|k-1)對應的covarianceP(k-1|k-1)X(k-1|k-1)對應的covarianceA’表示A的轉置矩陣,Q是系統過程的covariance。式子12就是卡爾曼濾波器5個公式當中的前兩個,也就是對系統的預測。

現在我們有了現在狀態的預測結果,然後我們再收集現在狀態的測量值。結合預測值和測量值,我們可以得到現在狀態(k)的最優化估算值X(k|k)

X(k|k)= X(k|k-1)+Kg(k) (Z(k)-H X(k|k-1)) ……… (3)

其中Kg爲卡爾曼增益(Kalman Gain)

Kg(k)= P(k|k-1) H / (H P(k|k-1) H + R) ……… (4)

到現在爲止,我們已經得到了k狀態下最優的估算值X(k|k)。但是爲了要另卡爾曼濾波器不斷的運行下去直到系統過程結束,我們還要更新k狀態下X(k|k)covariance

P(k|k)=I-Kg(k) HP(k|k-1) ……… (5)

其中I 1的矩陣,對於單模型單測量,I=1。當系統進入k+1狀態時,P(k|k)就是式子(2)P(k-1|k-1)。這樣,算法就可以自迴歸的運算下去。

卡爾曼濾波器的原理基本描述了,式子12345就是他的5 個基本公式。根據這5個公式,可以很容易的實現計算機的程序

 

4月4日

OpenCV學習筆記(四)運動物體跟蹤的camshift算法

CamShift算法
簡介
CamShift算法,即"Continuously Apative Mean-Shift"算法,是一種運動跟蹤算法。它主要通過視頻圖像中運動物體的顏色信息來達到跟蹤的目的。我把這個算法分解成三個部分,便於理解:

Back Projection計算。

Mean Shift算法

CamShift算法

1 Back Projection計算
計算Back Projection的步驟是這樣的:

1. 計算被跟蹤目標的色彩直方圖。在各種色彩空間中,只有HSI空間(或與HSI類似的色彩空間)中的H分量可以表示顏色信息。所以在具體的計算過程中,首先將其他的色彩空間的值轉化到HSI空間,然後會其中的H分量做1D直方圖計算。

2. 根據獲得的色彩直方圖將原始圖像轉化成色彩概率分佈圖像,這個過程就被稱作"Back Projection"。

在OpenCV中的直方圖函數中,包含Back Projection的函數,函數原型是:

   void cvCalcBackProject(IplImage** img, CvArr** backproject, const CvHistogram* hist);

傳遞給這個函數的參數有三個:

1. IplImage** img:存放原始圖像,輸入。

2. CvArr** backproject:存放Back Projection結果,輸出。

3. CvHistogram* hist:存放直方圖,輸入

下面就給出計算Back Projection的OpenCV代碼。

1.準備一張只包含被跟蹤目標的圖片,將色彩空間轉化到HSI空間,獲得其中的H分量:

  IplImage* target=cvLoadImage("target.bmp",-1);  //裝載圖片

  IplImage* target_hsv=cvCreateImage( cvGetSize(target), IPL_DEPTH_8U, 3 );

  IplImage* target_hue=cvCreateImage( cvGetSize(target), IPL_DEPTH_8U, 3 );

  cvCvtColor(target,target_hsv,CV_BGR2HSV);       //轉化到HSV空間

  cvSplit( target_hsv, target_hue, NULL, NULL, NULL );    //獲得H分量

2.計算H分量的直方圖,即1D直方圖:

  IplImage* h_plane=cvCreateImage( cvGetSize(target_hsv),IPL_DEPTH_8U,1 );

  int hist_size[]={255};          //將H分量的值量化到[0,255]

  float* ranges[]={ {0,360} };    //H分量的取值範圍是[0,360)

  CvHistogram* hist=cvCreateHist(1, hist_size, ranges, 1);

  cvCalcHist(&target_hue, hist, 0, NULL);

在這裏需要考慮H分量的取值範圍的問題,H分量的取值範圍是[0,360),這個取值範圍的值不能用一個byte來表示,爲了能用一個byte表示,需要將H值做適當的量化處理,在這裏我們將H分量的範圍量化到[0,255].

4.計算Back Projection:

  IplImage* rawImage;

  //----------------------------------------------

  //get from video frame,unsigned byte,one channel

  //----------------------------------------------

  IplImage* result=cvCreateImage(cvGetSize(rawImage),IPL_DEPTH_8U,1);

  cvCalcBackProject(&rawImage,result,hist);

5.結果:result即爲我們需要的.

2) Mean Shift算法
 

這裏來到了CamShift算法,OpenCV實現的第二部分,這一次重點討論Mean Shift算法。

在討論Mean Shift算法之前,首先討論在2D概率分佈圖像中,如何計算某個區域的重心(Mass Center)的問題,重心可以通過以下公式來計算:

1.計算區域內0階矩

for(int i=0;i<height;i++)

  for(int j=0;j<width;j++)

     M00+=I(i,j)

2.區域內1階矩:

for(int i=0;i<height;i++)

  for(int j=0;j<width;j++)

  {

    M10+=i*I(i,j);

    M01+=j*I(i,j);

  }

3.則Mass Center爲:

Xc=M10/M00; Yc=M01/M00

接下來,討論Mean Shift算法的具體步驟,Mean Shift算法可以分爲以下4步:

1.選擇窗的大小和初始位置.

2.計算此時窗口內的Mass Center.

3.調整窗口的中心到Mass Center.

4.重複2和3,直到窗口中心"會聚",即每次窗口移動的距離小於一定的閾值。

在OpenCV中,提供Mean Shift算法的函數,函數的原型是:

int cvMeanShift(IplImage* imgprob,CvRect windowIn,

                    CvTermCriteria criteria,CvConnectedComp* out);

需要的參數爲:

1.IplImage* imgprob:2D概率分佈圖像,傳入;

2.CvRect windowIn:初始的窗口,傳入;

3.CvTermCriteria criteria:停止迭代的標準,傳入;

4.CvConnectedComp* out:查詢結果,傳出。

(注:構造CvTermCriteria變量需要三個參數,一個是類型,另一個是迭代的最大次數,最後一個表示特定的閾值。例如可以這樣構造criteria:criteria=cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS,10,0.1)。)

返回的參數:

1.int:迭代的次數。

實現代碼:暫時缺

3) CamShift算法
1.原理

在瞭解了MeanShift算法以後,我們將MeanShift算法擴展到連續圖像序列(一般都是指視頻圖像序列),這樣就形成了CamShift算法。CamShift算法的全稱是"Continuously Apaptive Mean-SHIFT",它的基本思想是視頻圖像的所有幀作MeanShift運算,並將上一幀的結果(即Search Window的中心和大小)作爲下一幀MeanShift算法的Search Window的初始值,如此迭代下去,就可以實現對目標的跟蹤。整個算法的具體步驟分5步:

Step 1:將整個圖像設爲搜尋區域。

Step 2:初始話Search Window的大小和位置。

Step 3:計算Search Window內的彩色概率分佈,此區域的大小比Search Window要稍微大一點。

Step 4:運行MeanShift。獲得Search Window新的位置和大小。

Step 5:在下一幀視頻圖像中,用Step 3獲得的值初始化Search Window的位置和大小。跳轉到Step 3繼續運行。

2.實現

在OpenCV中,有實現CamShift算法的函數,此函數的原型是:

  cvCamShift(IplImage* imgprob, CvRect windowIn,

                CvTermCriteria criteria,

                CvConnectedComp* out, CvBox2D* box=0);

其中:

   imgprob:色彩概率分佈圖像。

   windowIn:Search Window的初始值。

   Criteria:用來判斷搜尋是否停止的一個標準。

   out:保存運算結果,包括新的Search Window的位置和麪積。

   box:包含被跟蹤物體的最小矩形。

說明:

1.在OpenCV 4.0 beta的目錄中,有CamShift的例子。遺憾的是這個例子目標的跟蹤是半自動的,即需要人手工選定一個目標。我正在努力嘗試全自動的目標跟蹤,希望可以和大家能在這方面與大家交流。


 

3月31日

OpenCV學習筆記(三)人臉檢測的代碼分析

 
OpenCV學習筆記(三)人臉檢測的代碼分析
一、預備知識:
1、動態內存存儲及操作函數
CvMemStorage
typedef struct CvMemStorage
{
    struct CvMemBlock* bottom;/* first allocated block */
    struct CvMemBlock* top; /* the current memory block - top of the stack */
    struct CvMemStorage* parent; /* borrows new blocks from */
    int block_size; /* block size */
    int free_space; /* free space in the top block (in bytes) */
} CvMemStorage;
內存存儲器是一個可用來存儲諸如序列,輪廓,圖形,子劃分等動態增長數據結構的底層結構。它是由一系列以同等大小的內存塊構成,呈列表型 ---bottom 域指的是列首,top 域指的是當前指向的塊但未必是列尾.在bottom和top之間所有的塊(包括bottom, 不包括top)被完全佔據了空間;在 top和列尾之間所有的塊(包括塊尾,不包括top)則是空的;而top塊本身則被佔據了部分空間 -- free_space 指的是top塊剩餘的空字節數。新分配的內存緩衝區(或顯示的通過 cvMemStorageAlloc 函數分配,或隱示的通過 cvSeqPush, cvGraphAddEdge等高級函數分配)總是起始於當前塊(即top塊)的剩餘那部分,如果剩餘那部分能滿足要求(夠分配的大小)。分配後,free_space 就減少了新分配的那部分內存大小,外加一些用來保存適當列型的附加大小。當top塊的剩餘空間無法滿足被分配的塊(緩衝區)大小時,top塊的下一個存儲塊被置爲當前塊(新的top塊) --  free_space 被置爲先前分配的整個塊的大小。如果已經不存在空的存儲塊(即:top塊已是列尾),則必須再分配一個新的塊(或從parent那繼承,見 cvCreateChildMemStorage)並將該塊加到列尾上去。於是,存儲器(memory storage)就如同棧(Stack)那樣, bottom指向棧底,(top, free_space)對指向棧頂。棧頂可通過 cvSaveMemStoragePos保存,通過 cvRestoreMemStoragePos 恢復指向, 通過 cvClearStorage 重置。
CvMemBlock
內存存儲塊結構
typedef struct CvMemBlock
{
    struct CvMemBlock* prev;
    struct CvMemBlock* next;
} CvMemBlock;
CvMemBlock 代表一個單獨的內存存儲塊結構。 內存存儲塊中的實際數據存儲在 header塊 之後(即:存在一個頭指針 head 指向的塊 header ,該塊不存儲數據),於是,內存塊的第 i 個字節可以通過表達式 ((char*)(mem_block_ptr+1))[i] 獲得。然而,通常沒必要直接去獲得存儲結構的域。
CvMemStoragePos
內存存儲塊地址
typedef struct CvMemStoragePos
{
    CvMemBlock* top;
    int free_space;
} CvMemStoragePos;
該結構(如以下所說)保存棧頂的地址,棧頂可以通過 cvSaveMemStoragePos 保存,也可以通過 cvRestoreMemStoragePos 恢復。
________________________________________
cvCreateMemStorage
創建內存塊
CvMemStorage* cvCreateMemStorage( int block_size=0 );
 block_size:存儲塊的大小以字節表示。如果大小是 0 byte, 則將該塊設置成默認值  當前默認大小爲64k.
函數 cvCreateMemStorage 創建一內存塊並返回指向塊首的指針。起初,存儲塊是空的。頭部(即:header)的所有域值都爲 0,除了 block_size 外.
________________________________________
cvCreateChildMemStorage
創建子內存塊
CvMemStorage* cvCreateChildMemStorage( CvMemStorage* parent );
parent    父內存塊
函數 cvCreateChildMemStorage 創建一類似於普通內存塊的子內存塊,除了內存分配/釋放機制不同外。當一個子存儲塊需要一個新的塊加入時,它就試圖從parent 那得到這樣一個塊。如果 parent 中 還未被佔據空間的那些塊中的第一個塊是可獲得的,就獲取第一個塊(依此類推),再將該塊從 parent  那裏去除。如果不存在這樣的塊,則 parent 要麼分配一個,要麼從它自己 parent (即:parent 的 parent) 那借個過來。換句話說,完全有可能形成一個鏈或更爲複雜的結構,其中的內存存儲塊互爲 child/ parent 關係(父子關係)。當子存儲結構被釋放或清除,它就把所有的塊還給各自的 parent. 在其他方面,子存儲結構同普通存儲結構一樣。
子存儲結構在下列情況中是非常有用的。想象一下,如果用戶需要處理存儲在某個塊中的動態數據,再將處理的結果存放在該塊中。在使用了最簡單的方法處理後,臨時數據作爲輸入和輸出數據被存放在了同一個存儲塊中,於是該存儲塊看上去就類似下面處理後的樣子: Dynamic data processing without using child storage. 結果,在存儲塊中,出現了垃圾(臨時數據)。然而,如果在開始處理數據前就先建立一個子存儲塊,將臨時數據寫入子存儲塊中並在最後釋放子存儲塊,那麼最終在 源/目的存儲塊 (source / destination storage) 中就不會出現垃圾, 於是該存儲塊看上去應該是如下形式:Dynamic data processing using a child storage.
cvReleaseMemStorage
釋放內存塊
void cvReleaseMemStorage( CvMemStorage** storage );
storage: 指向被釋放了的存儲塊的指針
函數 cvReleaseMemStorage 釋放所有的存儲(內存)塊 或者 將它們返回給各自的 parent(如果需要的話)。 接下來再釋放 header塊(即:釋放頭指針 head 指向的塊 = free(head))並清除指向該塊的指針(即:head = NULL)。在釋放作爲 parent 的塊之前,先清除各自的 child 塊。
cvClearMemStorage
清空內存存儲塊
void cvClearMemStorage( CvMemStorage* storage );
storage:存儲存儲塊
函數 cvClearMemStorage 將存儲塊的 top 置到存儲塊的頭部(注:清空存儲塊中的存儲內容)。該函數並不釋放內存(僅清空內存)。假使該內存塊有一個父內存塊(即:存在一內存塊與其有父子關係),則函數就將所有的塊返回給其 parent.
cvMemStorageAlloc
在存儲塊中分配以內存緩衝區
void* cvMemStorageAlloc( CvMemStorage* storage, size_t size );
storage:內存塊.
size:緩衝區的大小.
函數 cvMemStorageAlloc 在存儲塊中分配一內存緩衝區。該緩衝區的大小不能超過內存塊的大小,否則就會導致運行時錯誤。緩衝區的地址被調整爲CV_STRUCT_ALIGN 字節 (當前爲 sizeof(double)).
cvMemStorageAllocString
在存儲塊中分配一文本字符串
typedef struct CvString
{
    int len;
    char* ptr;
}
CvString;
CvString cvMemStorageAllocString( CvMemStorage* storage, const char* ptr, int len=-1 );
storage:存儲塊
ptr:字符串
len:字符串的長度(不計算'/0')。如果參數爲負數,函數就計算該字符串的長度。
函數 cvMemStorageAlloString 在存儲塊中創建了一字符串的拷貝。它返回一結構,該結構包含字符串的長度(該長度或通過用戶傳遞,或通過計算得到)和指向被拷貝了的字符串的指針。
cvSaveMemStoragePos
保存內存塊的位置(地址)
void cvSaveMemStoragePos( const CvMemStorage* storage, CvMemStoragePos* pos );
storage:內存塊.
pos:內存塊頂部位置。
函數 cvSaveMemStoragePos 將存儲塊的當前位置保存到參數 pos 中。 函數 cvRestoreMemStoragePos 可進一步獲取該位置(地址)。
cvRestoreMemStoragePos
恢復內存存儲塊的位置
void cvRestoreMemStoragePos( CvMemStorage* storage, CvMemStoragePos* pos );
storage:內存塊.
pos:新的存儲塊的位置
函數 cvRestoreMemStoragePos 通過參數 pos 恢復內存塊的位置。該函數和函數 cvClearMemStorage 是釋放被佔用內存塊的唯一方法。注意:沒有什麼方法可去釋放存儲塊中被佔用的部分內存。
2、分類器結構及操作函數:
CvHaarFeature
#define CV_HAAR_FEATURE_MAX  3
typedef struct CvHaarFeature
{
    int  tilted; 
    struct
    {
        CvRect r;
        float weight;
} rect[CV_HAAR_FEATURE_MAX];
 
}
CvHaarFeature;
一個 harr 特徵由 2-3 個具有相應權重的矩形組成
titled :/* 0 means up-right feature, 1 means 45--rotated feature */
rect[CV_HAAR_FEATURE_MAX];  /* 2-3 rectangles with weights of opposite signs and       with absolute values inversely proportional to the areas of the rectangles. if rect[2].weight !=0, then  the feature consists of 3 rectangles, otherwise it consists of 2 */
CvHaarClassifier
typedef struct CvHaarClassifier
{
    int count; 
    CvHaarFeature* haar_feature;
    float* threshold;
    int* left;
    int* right;
    float* alpha;
}
CvHaarClassifier;
/* a single tree classifier (stump in the simplest case) that returns the response for the feature   at the particular image location (i.e. pixel sum over subrectangles of the window) and gives out a value depending on the responce */
int count;  /* number of nodes in the decision tree */
 /* these are "parallel" arrays. Every index i corresponds to a node of the decision tree (root has 0-th index).
left[i] - index of the left child (or negated index if the left child is a leaf)
right[i] - index of the right child (or negated index if the right child is a leaf)
threshold[i] - branch threshold. if feature responce is <= threshold, left branch                      is chosen, otherwise right branch is chosed.
alpha[i] - output value correponding to the leaf. */
CvHaarStageClassifier
typedef struct CvHaarStageClassifier
{
    int  count;  /* number of classifiers in the battery */
    float threshold; /* threshold for the boosted classifier */
    CvHaarClassifier* classifier; /* array of classifiers */
    /* these fields are used for organizing trees of stage classifiers,
       rather than just stright cascades */
    int next;
    int child;
    int parent;
}
CvHaarStageClassifier;

/* a boosted battery of classifiers(=stage classifier): the stage classifier returns 1 if the sum of the classifiers' responces is greater than threshold and 0 otherwise */
int  count;  /* number of classifiers in the battery */
float threshold; /* threshold for the boosted classifier */
CvHaarClassifier* classifier; /* array of classifiers */
/* these fields are used for organizing trees of stage classifiers, rather than just stright cascades */
CvHaarClassifierCascade
typedef struct CvHidHaarClassifierCascade CvHidHaarClassifierCascade;
typedef struct CvHaarClassifierCascade
{
    int  flags;
    int  count;
    CvSize orig_window_size;
    CvSize real_window_size;
    double scale;
    CvHaarStageClassifier* stage_classifier;
    CvHidHaarClassifierCascade* hid_cascade;
}
CvHaarClassifierCascade;

/* cascade or tree of stage classifiers */
int  flags; /* signature */
int  count; /* number of stages */
CvSize orig_window_size; /* original object size (the cascade is trained for) */
/* these two parameters are set by cvSetImagesForHaarClassifierCascade */
CvSize real_window_size; /* current object size */
double scale; /* current scale */
CvHaarStageClassifier* stage_classifier; /* array of stage classifiers */
CvHidHaarClassifierCascade* hid_cascade; /* hidden optimized representation of the cascade, created by cvSetImagesForHaarClassifierCascade */
所有的結構都代表一個級聯boosted Haar分類器。級聯有下面的等級結構:
    Cascade:
        Stage1:
            Classifier11:
                Feature11
            Classifier12:
                Feature12
            ...
        Stage2:
            Classifier21:
                Feature21
            ...
        ...
整個等級可以手工構建,也可以利用函數cvLoadHaarClassifierCascade從已有的磁盤文件或嵌入式基中導入。
特徵檢測用到的函數:
cvLoadHaarClassifierCascade
從文件中裝載訓練好的級聯分類器或者從OpenCV中嵌入的分類器數據庫中導入
CvHaarClassifierCascade* cvLoadHaarClassifierCascade(
                         const char* directory,
                         CvSize orig_window_size );
directory :訓練好的級聯分類器的路徑
orig_window_size:級聯分類器訓練中採用的檢測目標的尺寸。因爲這個信息沒有在級聯分類器中存儲,所有要單獨指出。
函數 cvLoadHaarClassifierCascade 用於從文件中裝載訓練好的利用海爾特徵的級聯分類器,或者從OpenCV中嵌入的分類器數據庫中導入。分類器的訓練可以應用函數haartraining(詳細察看opencv/apps/haartraining)
函數 已經過時了。現在的目標檢測分類器通常存儲在  XML 或 YAML 文件中,而不是通過路徑導入。從文件中導入分類器,可以使用函數 cvLoad 。
cvReleaseHaarClassifierCascade
釋放haar classifier cascade。
void cvReleaseHaarClassifierCascade( CvHaarClassifierCascade** cascade );
cascade :雙指針類型指針指向要釋放的cascade. 指針由函數聲明。
函數 cvReleaseHaarClassifierCascade 釋放cascade的動態內存,其中cascade的動態內存或者是手工創建,或者通過函數 cvLoadHaarClassifierCascade 或 cvLoad分配。
cvHaarDetectObjects
檢測圖像中的目標
typedef struct CvAvgComp
{
CvRect rect; /* bounding rectangle for the object (average rectangle of a group) */
int neighbors; /* number of neighbor rectangles in the group */
}
CvAvgComp;
CvSeq* cvHaarDetectObjects( const CvArr* image,
CvHaarClassifierCascade* cascade,
                            CvMemStorage* storage,
                            double scale_factor=1.1,
                            int min_neighbors=3, int flags=0,
                            CvSize min_size=cvSize(0,0) );
image 被檢圖像
cascade harr 分類器級聯的內部標識形式
storage 用來存儲檢測到的一序列候選目標矩形框的內存區域。
scale_factor 在前後兩次相繼的掃描中,搜索窗口的比例係數。例如1.1指將搜索窗口依次擴大10%。
min_neighbors 構成檢測目標的相鄰矩形的最小個數(缺省-1)。如果組成檢測目標的小矩形的個數和小於min_neighbors-1 都會被排除。如果min_neighbors 爲 0, 則函數不做任何操作就返回所有的被檢候選矩形框,這種設定值一般用在用戶自定義對檢測結果的組合程序上。
flags 操作方式。當前唯一可以定義的操作方式是 CV_HAAR_DO_CANNY_PRUNING。如果被設定,函數利用Canny邊緣檢測器來排除一些邊緣很少或者很多的圖像區域,因爲這樣的區域一般不含被檢目標。人臉檢測中通過設定閾值使用了這種方法,並因此提高了檢測速度。 
min_size 檢測窗口的最小尺寸。缺省的情況下被設爲分類器訓練時採用的樣本尺寸(人臉檢測中缺省大小是~20×20)。
函數 cvHaarDetectObjects 使用針對某目標物體訓練的級聯分類器在圖像中找到包含目標物體的矩形區域,並且將這些區域作爲一序列的矩形框返回。函數以不同比例大小的掃描窗口對圖像進行幾次搜索(察看cvSetImagesForHaarClassifierCascade)。 每次都要對圖像中的這些重疊區域利用cvRunHaarClassifierCascade進行檢測。 有時候也會利用某些繼承(heuristics)技術以減少分析的候選區域,例如利用 Canny 裁減 (prunning)方法。 函數在處理和收集到候選的方框(全部通過級聯分類器各層的區域)之後,接着對這些區域進行組合並且返回一系列各個足夠大的組合中的平均矩形。調節程序中的缺省參數(scale_factor=1.1, min_neighbors=3, flags=0)用於對目標進行更精確同時也是耗時較長的進一步檢測。爲了能對視頻圖像進行更快的實時檢測,參數設置通常是:scale_factor=1.2, min_neighbors=2, flags=CV_HAAR_DO_CANNY_PRUNING, min_size=<minimum possible face size> (例如, 對於視頻會議的圖像區域).
cvSetImagesForHaarClassifierCascade
爲隱藏的cascade(hidden cascade)指定圖像
void cvSetImagesForHaarClassifierCascade( CvHaarClassifierCascade* cascade,
                                          const CvArr* sum, const CvArr* sqsum,
                                          const CvArr* tilted_sum, double scale );
cascade 隱藏 Harr 分類器級聯 (Hidden Haar classifier cascade), 由函數 cvCreateHidHaarClassifierCascade生成
sum 32-比特,單通道圖像的積分圖像(Integral (sum) 單通道 image of 32-比特 integer format). 這幅圖像以及隨後的兩幅用於對快速特徵的評價和亮度/對比度的歸一化。 它們都可以利用函數 cvIntegral從8-比特或浮點數 單通道的輸入圖像中得到。
sqsum 單通道64比特圖像的平方和圖像
tilted_sum 單通道32比特整數格式的圖像的傾斜和(Tilted sum)
scale cascade的窗口比例. 如果 scale=1, 就只用原始窗口尺寸檢測 (只檢測同樣尺寸大小的目標物體) - 原始窗口尺寸在函數cvLoadHaarClassifierCascade中定義 (在 "<default_face_cascade>"中缺省爲24x24), 如果scale=2, 使用的窗口是上面的兩倍 (在face cascade中缺省值是48x48 )。 這樣儘管可以將檢測速度提高四倍,但同時尺寸小於48x48的人臉將不能被檢測到。
函數 cvSetImagesForHaarClassifierCascade 爲hidden classifier cascade 指定圖像 and/or 窗口比例係數。 如果圖像指針爲空,會繼續使用原來的圖像(i.e. NULLs 意味這"不改變圖像")。比例係數沒有 "protection" 值,但是原來的值可以通過函數 cvGetHaarClassifierCascadeScale 重新得到並使用。這個函數用於對特定圖像中檢測特定目標尺寸的cascade分類器的設定。函數通過cvHaarDetectObjects進行內部調用,但當需要在更低一層的函數cvRunHaarClassifierCascade中使用的時候,用戶也可以自行調用。
cvRunHaarClassifierCascade
在給定位置的圖像中運行 cascade of boosted classifier
int cvRunHaarClassifierCascade( CvHaarClassifierCascade* cascade,
                                CvPoint pt, int start_stage=0 );
cascade Haar 級聯分類器
pt 待檢測區域的左上角座標。待檢測區域大小爲原始窗口尺寸乘以當前設定的比例係數。當前窗口尺寸可以通過cvGetHaarClassifierCascadeWindowSize重新得到。
start_stage 級聯層的初始下標值(從0開始計數)。函數假定前面所有每層的分類器都已通過。這個特徵通過函數cvHaarDetectObjects內部調用,用於更好的處理器高速緩衝存儲器。
函數 cvRunHaarHaarClassifierCascade 用於對單幅圖片的檢測。在函數調用前首先利用 cvSetImagesForHaarClassifierCascade設定積分圖和合適的比例係數 (=> 窗口尺寸)。當分析的矩形框全部通過級聯分類器每一層的時返回正值(這是一個候選目標),否則返回0或負值。
二、例程分析:
例子:利用級聯的Haar classifiers尋找檢測目標(e.g. faces).
#include "cv.h"
#include "highgui.h"
//讀取訓練好的分類器。
CvHaarClassifierCascade* load_object_detector( const char* cascade_path )
{
    return (CvHaarClassifierCascade*)cvLoad( cascade_path );
}

void detect_and_draw_objects( IplImage* image,
                              CvHaarClassifierCascade* cascade,
                              int do_pyramids )
{
    IplImage* small_image = image;
    CvMemStorage* storage = cvCreateMemStorage(0); //創建動態內存
    CvSeq* faces;
    int i, scale = 1;
    /* if the flag is specified, down-scale the 輸入圖像 to get a
       performance boost w/o loosing quality (perhaps) */
    if( do_pyramids )
    {
        small_image = cvCreateImage( cvSize(image->width/2,image->height/2), IPL_DEPTH_8U, 3 );
        cvPyrDown( image, small_image, CV_GAUSSIAN_5x5 );//函數 cvPyrDown 使用 Gaussian 金字塔分解對輸入圖像向下採樣。首先它對輸入圖像用指定濾波器進行卷積,然後通過拒絕偶數的行與列來下采樣圖像。
        scale = 2;
    }
    /* use the fastest variant */
    faces = cvHaarDetectObjects( small_image, cascade, storage, 1.2, 2, CV_HAAR_DO_CANNY_PRUNING );
    /* draw all the rectangles */
    for( i = 0; i < faces->total; i++ )
    {
        /* extract the rectanlges only */
        CvRect face_rect = *(CvRect*)cvGetSeqElem( faces, i, 0 );
        cvRectangle( image, cvPoint(face_rect.x*scale,face_rect.y*scale),
                     cvPoint((face_rect.x+face_rect.width)*scale,
                             (face_rect.y+face_rect.height)*scale),
                     CV_RGB(255,0,0), 3 );
    }
    if( small_image != image )
        cvReleaseImage( &small_image );
    cvReleaseMemStorage( &storage );  //釋放動態內存
}
/* takes image filename and cascade path from the command line */
int main( int argc, char** argv )
{
    IplImage* image;
    if( argc==3 && (image = cvLoadImage( argv[1], 1 )) != 0 )
    {
        CvHaarClassifierCascade* cascade = load_object_detector(argv[2]);
        detect_and_draw_objects( image, cascade, 1 );
        cvNamedWindow( "test", 0 );
        cvShowImage( "test", image );
        cvWaitKey(0);
        cvReleaseHaarClassifierCascade( &cascade );
        cvReleaseImage( &image );
    }
    return 0;
}
關鍵代碼很簡單,裝載分類器,對輸入圖像進行金字塔採樣,然後用cv的函數進行檢測目標,最後輸出檢測到的目標矩形。

OpenCV學習筆記(二)基於Haar-like特徵的層疊推進分類器快速目標檢測


OpenCV學習筆記之二――基於Haar-like特徵的層疊推進分類器快速目標檢測
一、簡介
目標檢測方法最初由Paul Viola [Viola01]提出,並由Rainer Lienhart [Lienhart02]對這一方法進行了改善。該方法的基本步驟爲: 首先,利用樣本(大約幾百幅樣本圖片)的 harr 特徵進行分類器訓練,得到一個級聯的boosted分類器。
分類器中的"級聯"是指最終的分類器是由幾個簡單分類器級聯組成。在圖像檢測中,被檢窗口依次通過每一級分類器, 這樣在前面幾層的檢測中大部分的候選區域就被排除了,全部通過每一級分類器檢測的區域即爲目標區域。
分類器訓練完以後,就可以應用於輸入圖像中的感興趣區域(與訓練樣本相同的尺寸)的檢測。檢測到目標區域(汽車或人臉)分類器輸出爲1,否則輸出爲0。爲了檢測整副圖像,可以在圖像中移動搜索窗口,檢測每一個位置來確定可能的目標。 爲了搜索不同大小的目標物體,分類器被設計爲可以進行尺寸改變,這樣比改變待檢圖像的尺寸大小更爲有效。所以,爲了在圖像中檢測未知大小的目標物體,掃描程序通常需要用不同比例大小的搜索窗口對圖片進行幾次掃描。
目前支持這種分類器的boosting技術有四種: Discrete Adaboost, Real Adaboost, Gentle Adaboost and Logitboost。
"boosted" 即指級聯分類器的每一層都可以從中選取一個boosting算法(權重投票),並利用基礎分類器的自我訓練得到。
根據上面的分析,目標檢測分爲三個步驟:
1、 樣本的創建
2、 訓練分類器
3、 利用訓練好的分類器進行目標檢測。
二、樣本創建
訓練樣本分爲正例樣本和反例樣本,其中正例樣本是指待檢目標樣本(例如人臉或汽車等),反例樣本指其它任意圖片,所有的樣本圖片都被歸一化爲同樣的尺寸大小(例如,20x20)。
負樣本
負樣本可以來自於任意的圖片,但這些圖片不能包含目標特徵。負樣本由背景描述文件來描述。背景描述文件是一個文本文件,每一行包含了一個負樣本圖片的文件名(基於描述文件的相對路徑)。該文件必須手工創建。
e.g: 負樣本描述文件的一個例子:
假定目錄結構如下:
/img
img1.jpg
img2.jpg
bg.txt
則背景描述文件bg.txt的內容爲:
img/img1.jpg
img/img2.jpg
正樣本
正樣本由程序craatesample程序來創建。該程序的源代碼由OpenCV給出,並且在bin目錄下包含了這個可執行的程序。
正樣本可以由單個的目標圖片或者一系列的事先標記好的圖片來創建。
Createsamples程序的命令行參數:
命令行參數:
-vec <vec_file_name>
訓練好的正樣本的輸出文件名。
-img<image_file_name>
源目標圖片(例如:一個公司圖標)
-bg<background_file_name>
背景描述文件。
-num<number_of_samples>
要產生的正樣本的數量,和正樣本圖片數目相同。
-bgcolor<background_color>
背景色(假定當前圖片爲灰度圖)。背景色制定了透明色。對於壓縮圖片,顏色方差量由bgthresh參數來指定。則在bgcolor-bgthresh和bgcolor+bgthresh中間的像素被認爲是透明的。
-bgthresh<background_color_threshold>
-inv
如果指定,顏色會反色
-randinv
如果指定,顏色會任意反色
-maxidev<max_intensity_deviation>
背景色最大的偏離度。
-maxangel<max_x_rotation_angle>
-maxangle<max_y_rotation_angle>,
-maxzangle<max_x_rotation_angle>
最大旋轉角度,以弧度爲單位。
-show
如果指定,每個樣本會被顯示出來,按下"esc"會關閉這一開關,即不顯示樣本圖片,而創建過程繼續。這是個有用的debug選項。
-w<sample_width>
輸出樣本的寬度(以像素爲單位)
-h《sample_height》
輸出樣本的高度,以像素爲單位。
注:正樣本也可以從一個預先標記好的圖像集合中獲取。這個集合由一個文本文件來描述,類似於背景描述文件。每一個文本行對應一個圖片。每行的第一個元素是圖片文件名,第二個元素是對象實體的個數。後面緊跟着的是與之匹配的矩形框(x, y, 寬度,高度)。
下面是一個創建樣本的例子:
假定我們要進行人臉的檢測,有5個正樣本圖片文件img1.bmp,…img5.bmp;有2個背景圖片文件:bg1.bmp,bg2.bmp,文件目錄結構如下:
positive
  img1.bmp
  ……
  Img5.bmp
negative
  bg1.bmp
  bg2.bmp
info.dat
bg.txt
正樣本描述文件info.dat的內容如下:
Positive/imag1.bmp 1 0 0 24 28
……
Positive/imag5.bmp 1 0 0 24 28
圖片img1.bmp包含了單個目標對象實體,矩形爲(0,0,24,28)。
注意:要從圖片集中創建正樣本,要用-info參數而不是用-img參數。
-info <collect_file_name>
標記特徵的圖片集合的描述文件。
背景(負樣本)描述文件的內容如下:
nagative/bg1.bmp
nagative/bg2.bmp
我們用一個批處理文件run.bat來進行正樣本的創建:該文件的內容如下:
cd  e:/face/bin
CreateSamples   -vec e:/face/a.vec
 -info e:/face/info.dat
-bg e:/face/bg.txt
-num 5
-show
-w 24
 -h 28
其中e:/face/bin目錄包含了createsamples可執行程序,生成的正樣本文件a.vec在e:/face目錄下。
三、訓練分類器
樣本創建之後,接下來要訓練分類器,這個過程是由haartraining程序來實現的。該程序源碼由OpenCV自帶,且可執行程序在OpenCV安裝目錄的bin目錄下。
Haartraining的命令行參數如下:
-data<dir_name>
存放訓練好的分類器的路徑名。
-vec<vec_file_name>
正樣本文件名(由trainingssamples程序或者由其他的方法創建的)
-bg<background_file_name>
背景描述文件。
-npos<number_of_positive_samples>,
-nneg<number_of_negative_samples>
用來訓練每一個分類器階段的正/負樣本。合理的值是:nPos = 7000;nNeg = 3000
-nstages<number_of_stages>
訓練的階段數。
-nsplits<number_of_splits>
決定用於階段分類器的弱分類器。如果1,則一個簡單的stump classifier被使用。如果是2或者更多,則帶有number_of_splits個內部節點的CART分類器被使用。
-mem<memory_in_MB>
預先計算的以MB爲單位的可用內存。內存越大則訓練的速度越快。
-sym(default)
-nonsym
指定訓練的目標對象是否垂直對稱。垂直對稱提高目標的訓練速度。例如,正面部是垂直對稱的。
-minhitrate《min_hit_rate》
每個階段分類器需要的最小的命中率。總的命中率爲min_hit_rate的number_of_stages次方。
-maxfalsealarm<max_false_alarm_rate>
沒有階段分類器的最大錯誤報警率。總的錯誤警告率爲max_false_alarm_rate的number_of_stages次方。
-weighttrimming<weight_trimming>
指定是否使用權修正和使用多大的權修正。一個基本的選擇是0.9
-eqw
-mode<basic(default)|core|all>
選擇用來訓練的haar特徵集的種類。basic僅僅使用垂直特徵。all使用垂直和45度角旋轉特徵。
-w《sample_width》
-h《sample_height》
訓練樣本的尺寸,(以像素爲單位)。必須和訓練樣本創建的尺寸相同。
一個訓練分類器的例子:
同上例,分類器訓練的過程用一個批處理文件run2.bat來完成:
cd e:/face/bin
haartraining -data e:/face/data
-vec e:/face/a.vec
-bg e:/face/bg.txt
-npos 5
-nneg 2
 -w 24
 -h 28
訓練結束後,會在目錄data下生成一些子目錄,即爲訓練好的分類器。
注:OpenCv的某些版本可以將這些目錄中的分類器直接轉換成xml文件。但在實際的操作中,haartraining程序卻好像永遠不會停止,而且沒有生成xml文件,後來在OpenCV的yahoo論壇上找到一個haarconv的程序,纔將分類器轉換爲xml文件,其中的原因尚待研究。
四、目標檢測
OpenCV的cvHaarDetectObjects()函數(在haarFaceDetect演示程序中示例)被用來做偵測。關於該檢測的詳細分析,將在下面的筆記中詳細描述。

 
3月29日

一個合格的程序員該做的事情


2006.03.15  來自:CSDN 選自Mailbomb 的blog  
 

  程序員每天該做的事

1、總結自己一天任務的完成情況

最好的方式是寫工作日誌,把自己今天完成了什麼事情,遇見了什麼問題都記錄下來,日後翻看好處多多

2、考慮自己明天應該做的主要工作

把明天要做的事情列出來,並按照優先級排列,第二天應該把自己效率最高的時間分配給最重要的工作

3、考慮自己一天工作中失誤的地方,並想出避免下一次再犯的方法

出錯不要緊,最重要的是不要重複犯相同的錯誤,那是愚蠢

4、考慮自己一天工作完成的質量和效率能否還能提高

一天只提高1%,365天你的效率就能提高多少倍你知道嗎? (1+0.01)^365 = 37 倍

5、看一個有用的新聞網站或讀一張有用的報紙,瞭解業界動態

閉門造車是不行的,瞭解一下別人都在做什麼,對自己能帶來很多啓示

6、記住一位同事的名字及其特點

你認識公司的所有同事嗎?你瞭解他們嗎?

7、清理自己的代碼

今天完成的代碼,把中間的調試信息,測試代碼清理掉,按照編碼風格整理好,註釋都寫好了嗎?

8、清理自己的桌面

當日事當日畢,保持清潔幹勁的桌面才能讓你工作時不分心,程序員特別要把電腦的桌面清理乾淨

程序員每月該做的事

1、至少和一個同事一起吃飯或喝茶
不光了解自己工作夥伴的工作,還要了解他們的生活

2、自我考覈一次
相對正式地考覈自己一下,你對得起這個月的工資嗎?

3、對你的同事考覈一次
你的同事表現怎麼樣?哪些人值得學習,哪些人需要幫助?

3、制定下月的計劃,確定下月的工作重點

4、總結自己工作質量改進狀況
自己的質量提高了多少?

5、有針對性地對一項工作指標做深入地分析並得出改進的方案
可以是對自己的,也可以是對公司的,一定要深入地分析後拿出自己的觀點來。要想在老闆面前說得上話,做的成事,工作上功夫要做足。

6、與老闆溝通一次
最好是面對面地溝通,好好表現一下自己,虛心聽取老闆的意見,更重要的是要了解老闆當前關心的重點

程序員每年該做的事

1、年終總結
每個公司都會做的事情,但你真正認真地總結過自己嗎?

2、兌現給自己、給家人的承諾
給老婆、兒子的新年禮物買了沒有?給自己的呢?

3、下年度工作規劃
好好想想自己明年的發展目標,爭取升職/加薪、跳槽還是自己出來幹?

4、掌握一項新技術
至少是一項,作爲程序員一年要是一項新技術都學不到手,那就一定會被淘汰。
掌握可不是看本書就行的,要真正懂得應用,最好你能夠寫一篇教程發表到你的blog

5、推出一種新產品
可以是一個真正的產品,也可以只是一個類庫,只要是你創造的東西就行,讓別人使用它,也爲世界作點貢獻。當然如果真的很有價值,收點註冊費也是應該的

6、與父母團聚一次
常回家看看,常回家看看

 

3月28日

OpenCV學習筆記(一)概述和系統配置


OpenCV學習筆記(一)概述和系統配置

一、概述
OpenCV是英特爾公司於1999年在俄羅斯設立的軟件開發中心“Software Development Center”開發的。該公司一直致力於基於個人電腦的計算機視覺應用的開發,可以實時追蹤的視覺用戶接口技術的普及爲目標。初步擬定應用於Human-Computer Interaction(HCI,人機互動)、物體確定、面孔識別、表情識別,移動物體追蹤、自主運動(Ego-motion)、移動機器人等領域。因此,“OpenCV 2.1將提供給玩具製造商及機器人製造商等從事計算機視覺相關技術的各類企業/團體”
    OpenCV將以公開源碼的方式提供,也就是接受方有權在修改之後另行向第三方提供。源代碼(C語言)中包括有庫(Library)的所有功能。詳細情況刊登在OpenCV的WWW站點上。
英特爾公司解釋說,“速度更高的微處理器、廉價的數碼相機以及USB 2等技術使高速視頻捕獲(Video Capture)成爲可能,因此,基於普通個人電腦的實時計算機視覺將有望實現”。
OpenCV的最新版本爲beta5。
二、OpenCV組成部分
目前OpenCV包含下面幾個部分:
    cvcore:一些基本函數(各種數據類型的基本運算等)
    cv:    圖像處理和計算機視覺功能(圖像處理,結構分析,運動分析,物體跟蹤,模式識別,攝像機定標)
    cvaux:一些實驗性的函數(view morphing,三維跟蹤,pca,hmm)
    highgui 用戶交互部分(GUI,圖像視頻I/O,系統調用函數)
    另外還有cvcam,不過linux版本已經拋棄。windows版本中將directX支持加入highgui後,cvcam將被徹底去掉。
三、OpenCV特點
OpenCV是Intel公司開發的圖像處理和計算機視覺函數庫,它有以下特點:
1) 開放C源碼
2) 基於Intel處理器指令集開發的優化代碼
3) 統一的結構和功能定義
4) 強大的圖像和矩陣運算能力
5) 方便靈活的用戶接口
6)同時支持MS-WINDOWS、LINUX平臺
四、下載OpenCV
    http://www.sourceforge.net/projects/opencvlibrary
五、參考資料
    =》源代碼及文檔下載:SOURCEFORGE.NET
http://sourceforge.net/projects/opencvlibrary/
    =》INTEL的OPENCV主頁:
http://www.intel.com/research/mrl/research/opencv/
    =》YAHOO OPENCV 的郵件列表:
http://groups.yahoo.com/group/OpenCV/
    =》CMU(卡耐基-梅隆大學)的計算機視覺主頁:
http://www-2.cs.cmu.edu/afs/cs/project/cil/ftp/html/vision.html
    =》OPENCV 更爲詳細的介紹
http://www.assuredigit.com//incoming/sourcecode/opencv/chinese_docs/index.htm
    =》OPENCV 的常用問題與解答
http://www.assuredigit.com//incoming/sourcecode/opencv/chinese_docs/faq.htm
    =》OPENCV 的安裝指南
http://www.assuredigit.com//incoming/sourcecode/opencv/chinese_docs/install
    =》更多的最新資料,請訪問
http://blog.csdn.net/hunnishhttp://rocee.bokee.com
六、創建一個 DeveloperStudio 項目來開始 OpenCV
1. 在 Developer Studio 中創建新的應用程序:
選擇菜單 "File"->"New..."->"Projects" . 選擇 "Win32 Application" 或 "Win32 console application" - 後者是更簡單的方法。
鍵入項目名稱,並且選擇存儲位置
可以爲項目創建一個單獨的 workspace ("Create new workspace") , 也可以將新的項目加入到當前的 workspace 中 ("Add to current workspace").
單擊 "next" 
選擇 "An empty project", 點擊 "Finish", "OK".
經過以上步驟,Developer Studio 會創建一個項目目錄 (缺省情況下,目錄名就是項目名), .dsp 文件以及.dsw,.ncb ... ,如果你創建自己的workspace.
2添加文件到 project 中:
選擇菜單"File"->"New..."->"Files" .
選擇"C++ Source File", 鍵入文件名,點擊"OK"
增加 OpenCV 相關的 頭文件目錄 #include :
        #include "cv.h"
        /* #inlcude "cvaux.h" // experimental stuff (if need) */
        #include "highgui.h"
     
或者你可以拷貝部分已有的文件 (如:opencv/samples/c/morphology.c) 到項目目錄中,打開它,並且加入到項目中 (右鍵點擊編輯器的視圖 -> "Insert File into Project" -> ).
3配置項目:
選擇菜單"Project"->"Settings..."以激活項目配置對話框 .
在左邊選擇你的項目.
調節設置,對 Release 和 Debug 配置都有效:
選擇 "Settings For:"->"All Configurations"
選擇 "C/C++" tab -> "Preprocessor" category -> "Additional Include Directories:". 加入用逗號分隔的相對路徑 (對文件 .dsp 而言) 或絕對路徑
 d:/opencv/cxcore/include,d:/opencv/cv/include,d:/opencv/otherlibs/highgui, d:/opencv/cvaux/include(optionally,)
選擇 "Link" tab -> "Input" category -> "Additional library path:".
加入輸入庫所在的路徑 (cxcore[d].lib cv[d].lib hihghui[d].lib cvaux[d].lib)
d:/opencv/lib
調節 "Debug" 配置:
選擇 "Settings For:"->"Win32 Debug".
選擇 "Link" tab -> "General" category -> "Object/library modules". 加入空格分隔的 cvd.lib,cxcored.lib highguid.lib,cvauxd.lib (optionally)
可以改變輸出文件的名稱和位置。如想把產生的 .exe 文件放置於項目目錄而不是Debug/ 子目錄下,可在 "Link" tab -> "General" category -> "Output file name:" 中鍵入 ./d.exe 
調節 "Release" 配置
選擇 "Settings For:"->"Win32 Release".
選擇 "Link" tab -> "General" category -> "Object/library modules". 加入空格分隔的cv.lib cxcore.lib highgui.lib cvaux.lib (optionally)
4增加從屬性項目到 workspace 中:
選擇菜單: "Project" -> "Insert project into workspace".
選擇 opencv/cv/make/cv.dsp.
同樣步驟對 opencv/cvaux/make/cvaux.dsp, opencv/otherlibs/highgui/highgui.dsp.
設置從屬性:
選擇菜單: "Project" -> "Dependencies..."
對 "cv" 選擇 "cxcore",
對 "cvaux" 選擇 "cv", "cxcore",
對 "highgui" 選擇 "cxcore",
對你的項目,選擇所有的: "cxcore", "cv", "cvaux", "highgui".
從屬性配置保證了在源代碼被改變的情況下,自動重新編譯 opencv 庫.
5就這麼多。可以編譯並且運行一切了。
七、庫設置:
   靜態庫設置:
   Opencv程序需要靜態庫設置,其release版本的靜態庫在系統的lib目錄下,其debug版本的靜態庫需要重新全編譯所有的程序。
    動態庫設置:
   OPenCV啓動時需要一些動態庫的支持,這些動態庫必須放在系統目錄下或者當前目錄下。
    Cv097.dll,cvaux097.dll,cvcam097.dll,cxcore097.dll highguid097.dll,libguide40.dll


 

出租司機給"我"上的MBA課

Posted on 2006-03-15 01:14 劉潤
我要從徐家彙趕去機場,於是匆匆結束了一個會議,在美羅大廈前搜索出租車。一輛大衆發現了我,非常專業的、徑直的停在我的面前。這一停,於是有了後面的這個讓我深感震撼的故事,象上了一堂生動的MBA案例課。爲了忠實於這名出租車司機的原意,我憑記憶儘量重複他原來的話。
“去哪裏……好的,機場。我在徐家彙就喜歡做美羅大廈的生意。這裏我只做兩個地方。美羅大廈,均瑤大廈。你知道嗎?接到你之前,我在美羅大廈門口兜了兩圈,終於被我看到你了!從寫字樓裏出來的,肯定去的不近~~~”
“哦?你很有方法嘛!”我附和了一下。
“做出租車司機,也要用科學的方法。”他說。我一愣,頓時很有些興趣“什麼科學的方法?”
“要懂得統計。我做過精確的計算。我說給你聽啊。我每天開17個小時的車,每小時成本34.5元……”
“怎麼算出來的?”我追問。
“你算啊,我每天要交380元,油費大概210元左右。一天17小時,平均每小時固定成本22元,交給公司,平均每小時12.5元油費。這是不是就是34.5元?”,我有些驚訝。我打了10年的車,第一次聽到有出租車司機這麼計算成本。以前的司機都和我說,每公里成本0.3元,另外每天交多少錢之類的。
“成本是不能按公里算的,只能按時間算。你看,計價器有一個“檢查”功能。你可以看到一天的詳細記錄。我做過數據分析,每次載客之間的空駛時間平均爲7分鐘。如果上來一個起步價,10元,大概要開10分鐘。也就是每一個10元的客人要花17分鐘的成本,就是9.8元。不賺錢啊!如果說做浦東、杭州、青浦的客人是吃飯,做10元的客人連吃菜都算不上,只能算是撒了些味精。”
強!這位師傅聽上去真不象出租車司機,到象是一位成本覈算師。“那你怎麼辦呢?”我更感興趣了,繼續問。看來去機場的路上還能學到新東西。
“千萬不能被客戶拉了滿街跑。而是通過選擇停車的地點,時間,和客戶,主動地決定你要去的地方。”我非常驚訝,這聽上去很有意思。“有人說做出租車司機是靠運氣吃飯的職業。我以爲不是。你要站在客戶的位置上,從客戶的角度去思考。”這句話聽上去很專業,有點象很多商業管理培訓老師說的“put yourself into others' shoes.”
“給你舉個例子,醫院門口,一個拿着藥的,一個拿着臉盆的,你帶哪一個。”我想了想,說不知道。
“你要帶那個拿臉盆的。一般人病小痛的到醫院看一看,拿點藥,不一定會去很遠的醫院。拿着臉盆打車的,那是出院的。住院哪有不死人的?今天二樓的誰死了,明天三樓又死了一個。從醫院出來的人通常會有一種重獲新生的感覺,重新認識生命的意義,健康才最重要。那天這個說:走,去青浦。眼睛都不眨一下。你說他會打車到人民廣場,再去做青浦線嗎?絕對不會!”
我不由得開始佩服。
“再給你舉個例子。那天人民廣場,三個人在前面招手。一個年輕女子,拿着小包,剛買完東西。還有一對青年男女,一看就是逛街的。第三個是個裏面穿絨襯衫的,外面羽絨服的男子,拿着筆記本包。我看一個人只要3秒鐘。我毫不猶豫地停在這個男子面前。這個男的上車後說:延安高架、南北高架~~~還沒說後面就忍不住問,爲什麼你毫不猶豫地開到我面前?前面還有兩個人,他們要是想上車,我也不好意思和他們搶。我回答說,中午的時候,還有十幾分鍾就1點了。那個女孩子是中午溜出來買東西的,估計公司很近;那對男女是遊客,沒拿什麼東西,不會去很遠;你是出去辦事的,拿着筆記本包,一看就是公務。而且這個時候出去,估計應該不會近。那個男的就說,你說對了,去寶山。”
“那些在超市門口,地鐵口打車,穿着睡衣的人可能去很遠嗎?可能去機場嗎?機場也不會讓她進啊。”
有道理!我越聽越有意思。
“很多司機都抱怨,生意不好做啊,油價又漲了啊,都從別人身上找原因。我說,你永遠從別人身上找原因,你永遠不能提高。從自己身上找找看,問題出在哪裏。”這話聽起來好熟,好像是“如果你不能改變世界,就改變你自己”,或者Steven Corvey的“影響圈和關注圈”的翻版。“有一次,在南丹路一個人攔車,去田林。後來又有一次,一個人在南丹路攔車,還是去田林。我就問了,怎麼你們從南丹路出來的人,很多都是去田林呢?人家說,在南丹路有一個公共汽車總站,我們都是坐公共汽車從浦東到這裏,然後搭車去田林的。我恍然大悟。比如你看我們開過的這條路,沒有寫字樓,沒有酒店,什麼都沒有,只有公共汽車站,站在這裏攔車的多半都是剛下公共汽車的,再選擇一條最短路經打車。在這裏攔車的客戶通常不會高於15元。”
“所以我說,態度決定一切!”我聽十幾個總裁講過這句話,第一次聽出租車司機這麼說。
“要用科學的方法,統計學來做生意。天天等在地鐵站口排隊,怎麼能賺到錢?每個月就賺500塊錢怎麼養活老婆孩子?這就是在謀殺啊!慢性謀殺你的全家。要用知識武裝自己。學習知識可以把一個人變成聰明的人,一個聰明的人學習知識可以變成很聰明的人。一個很聰明的人學習知識,可以變成天才。”
“有一次一個人打車去火車站,問怎麼走。他說這麼這麼走。我說慢,上高架,再這麼這麼走。他說,這就繞遠了。我說,沒關係,你經常走你有經驗,你那麼走50塊,你按我的走法,等里程錶50塊了,我就翻表。你只給50快就好了,多的算我的。按你說的那麼走要50分鐘,我帶你這麼走只要25分鐘。最後,按我的路走,多走了4公里,快了25分鐘,我只收了50塊。乘客很高興,省了10元錢左右。這4公里對我來說就是1塊多錢的油錢。我相當於用1元多錢買了25分鐘。我剛纔說了,我一小時的成本34.5塊,我多合算啊!”
“在大衆公司,一般一個司機3、4千,拿回家。做的好的大概5千左右。頂級的司機大概每月能有7000。全大衆2萬個司機,大概只有2-3個司機,萬里挑一,每月能拿到8000以上。我就是這2-3個人中間的一個。而且很穩定,基本不會大的波動。”
太強了!到此爲止,我越來越佩服這個出租車司機。
“我常常說我是一個快樂的車伕。有人說,你是因爲賺的錢多,所以當然快樂。我對他們說,你們正好錯了。是因爲我有快樂、積極的心態,所以賺的錢多。”
說的多好啊!
“要懂得體味工作帶給你的美。堵在人民廣場的時候,很多司機抱怨,又堵車了!真是倒黴。千萬不要這樣,用心體會一下這個城市的美,外面有很多漂亮的女孩子經過,非常現代的高樓大廈,雖然買不起,但是卻可以用欣賞的眼光去享受。開車去機場,看着兩邊的綠色,冬天是白色的,多美啊。再看看里程錶,100多了,就更美了!每一樣工作都有她美麗的地方,我們要懂得從工作中體會這種美麗。”
“我10年前是強生公司的總教練。8年前在公司作過三個不同部門的部門經理。後來我不幹了,一個月就3、5千塊,沒意思。就主動來做司機。我願意做一個快樂的車伕。哈哈哈哈。”
到了機場,我給他留了一張名片,說:“你有沒有興趣這個星期五,到我辦公室,給微軟的員工講一講你怎麼開出租車的?你就當打着表,60公里一小時,你講多久,我就付你多少錢。給我電話。”
我迫不及待的在飛機上記錄下他這堂生動的MBA課。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章