OpenCV學(xué)習(xí)筆記(五)卡爾曼濾波器
卡爾曼濾波器 – Kalman Filter
1. 什么是卡爾曼濾波器 (What is the Kalman Filter?)
在學(xué)習(xí)卡爾曼濾波器之前,首先看看為什么叫“卡爾曼”。跟其他著名的理論(例如傅立葉變換,泰勒級數(shù)等等)一樣,卡爾曼也是一個人的名字,而跟他們不同的是,他是個現(xiàn)代人!
卡爾曼全名Rudolf Emil Kalman,匈牙利數(shù)學(xué)家,1930年出生于匈牙利首都布達佩斯。1953,1954年于麻省理工學(xué)院分別獲得電機工程學(xué)士及碩士學(xué)位。1957年于哥倫比亞大學(xué)獲得博士學(xué)位。我們現(xiàn)在要學(xué)習(xí)的卡爾曼濾波器,正是源于他的博士論文和1960年發(fā)表的論文《A New Approach to Linear Filtering and Prediction Problems》(線性濾波與預(yù)測問題的新方法)。如果對這編論文有興趣,可以到這里的地址下載: http://www.cs./~welch/media/pdf/Kalman1960.pdf。
簡單來說,卡爾曼濾波器是一個“optimal recursive data processing algorithm(最優(yōu)化自回歸數(shù)據(jù)處理算法)”。對于解決很大部分的問題,他是最優(yōu),效率最高甚至是最有用的。他的廣泛應(yīng)用已經(jīng)超過30年,包括機器人導(dǎo)航,控制,傳感器數(shù)據(jù)融合甚至在軍事方面的雷達系統(tǒng)以及導(dǎo)彈追蹤等等。近年來更被應(yīng)用于計算機圖像處理,例如頭臉識別,圖像分割,圖像邊緣檢測等等。
2.卡爾曼濾波器的介紹 (Introduction to the Kalman Filter)
為了可以更加容易的理解卡爾曼濾波器,這里會應(yīng)用形象的描述方法來講解,而不是像大多數(shù)參考書那樣羅列一大堆的數(shù)學(xué)公式和數(shù)學(xué)符號。但是,他的5條公式是其核心內(nèi)容。結(jié)合現(xiàn)代的計算機,其實卡爾曼的程序相當?shù)暮唵?,只要你理解了他的?/span>5條公式。
在介紹他的5條公式之前,先讓我們來根據(jù)下面的例子一步一步的探索。
假設(shè)我們要研究的對象是一個房間的溫度。根據(jù)你的經(jīng)驗判斷,這個房間的溫度是恒定的,也就是下一分鐘的溫度等于現(xiàn)在這一分鐘的溫度(假設(shè)我們用一分鐘來做時間單位)。假設(shè)你對你的經(jīng)驗不是100%的相信,可能會有上下偏差幾度。我們把這些偏差看成是高斯白噪聲(White Gaussian Noise),也就是這些偏差跟前后時間是沒有關(guān)系的而且符合高斯分配(Gaussian Distribution)。另外,我們在房間里放一個溫度計,但是這個溫度計也不準確的,測量值會比實際值偏差。我們也把這些偏差看成是高斯白噪聲。
好了,現(xiàn)在對于某一分鐘我們有兩個有關(guān)于該房間的溫度值:你根據(jù)經(jīng)驗的預(yù)測值(系統(tǒng)的預(yù)測值)和溫度計的值(測量值)。下面我們要用這兩個值結(jié)合他們各自的噪聲來估算出房間的實際溫度值。
假如我們要估算k時刻的是實際溫度值。首先你要根據(jù)k-1時刻的溫度值,來預(yù)測k時刻的溫度。因為你相信溫度是恒定的,所以你會得到k時刻的溫度預(yù)測值是跟k-1時刻一樣的,假設(shè)是23度,同時該值的高斯噪聲的偏差是5度(5是這樣得到的:如果k-1時刻估算出的最優(yōu)溫度值的偏差是3,你對自己預(yù)測的不確定度是4度,他們平方相加再開方,就是5)。然后,你從溫度計那里得到了k時刻的溫度值,假設(shè)是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比較?。ū容^相信溫度計),所以估算出的最優(yōu)溫度值偏向溫度計的值。
現(xiàn)在我們已經(jīng)得到k時刻的最優(yōu)溫度值了,下一步就是要進入k+1時刻,進行新的最優(yōu)估算。到現(xiàn)在為止,好像還沒看到什么自回歸的東西出現(xiàn)。對了,在進入k+1時刻之前,我們還要算出k時刻那個最優(yōu)值(24.56度)的偏差。算法如下:((1-Kg)*5^2)^0.5=2.35。這里的5就是上面的k時刻你預(yù)測的那個23度溫度值的偏差,得出的2.35就是進入k+1時刻以后k時刻估算出的最優(yōu)溫度值的偏差(對應(yīng)于上面的3)。
就是這樣,卡爾曼濾波器就不斷的把covariance遞歸,從而估算出最優(yōu)的溫度值。他運行的很快,而且它只保留了上一時刻的covariance。上面的Kg,就是卡爾曼增益(Kalman Gain)。他可以隨不同的時刻而改變他自己的值,是不是很神奇!
下面就要言歸正傳,討論真正工程系統(tǒng)上的卡爾曼。
3. 卡爾曼濾波器算法 (The Kalman Filter Algorithm)
在這一部分,我們就來描述源于Dr Kalman 的卡爾曼濾波器。下面的描述,會涉及一些基本的概念知識,包括概率(Probability),隨即變量(Random Variable),高斯或正態(tài)分配(Gaussian Distribution)還有State-space Model等等。但對于卡爾曼濾波器的詳細證明,這里不能一一描述。
首先,我們先要引入一個離散控制過程的系統(tǒng)。該系統(tǒng)可用一個線性隨機微分方程(Linear Stochastic Difference equation)來描述: X(k)=A X(k-1)+B U(k)+W(k) 再加上系統(tǒng)的測量值: Z(k)=H X(k)+V(k) 上兩式子中,X(k)是k時刻的系統(tǒng)狀態(tài),U(k)是k時刻對系統(tǒng)的控制量。A和B是系統(tǒng)參數(shù),對于多模型系統(tǒng),他們?yōu)榫仃嚒?/span>Z(k)是k時刻的測量值,H是測量系統(tǒng)的參數(shù),對于多測量系統(tǒng),H為矩陣。W(k)和V(k)分別表示過程和測量的噪聲。他們被假設(shè)成高斯白噪聲(White Gaussian Noise),他們的covariance 分別是Q,R(這里我們假設(shè)他們不隨系統(tǒng)狀態(tài)變化而變化)。
對于滿足上面的條件(線性隨機微分系統(tǒng),過程和測量都是高斯白噪聲),卡爾曼濾波器是最優(yōu)的信息處理器。下面我們來用他們結(jié)合他們的covariances 來估算系統(tǒng)的最優(yōu)化輸出(類似上一節(jié)那個溫度的例子)。
首先我們要利用系統(tǒng)的過程模型,來預(yù)測下一狀態(tài)的系統(tǒng)。假設(shè)現(xiàn)在的系統(tǒng)狀態(tài)是k,根據(jù)系統(tǒng)的模型,可以基于系統(tǒng)的上一狀態(tài)而預(yù)測出現(xiàn)在狀態(tài): X(k|k-1)=A X(k-1|k-1)+B U(k) ……….. (1) 式(1)中,X(k|k-1)是利用上一狀態(tài)預(yù)測的結(jié)果,X(k-1|k-1)是上一狀態(tài)最優(yōu)的結(jié)果,U(k)為現(xiàn)在狀態(tài)的控制量,如果沒有控制量,它可以為0。
到現(xiàn)在為止,我們的系統(tǒng)結(jié)果已經(jīng)更新了,可是,對應(yīng)于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)對應(yīng)的covariance,P(k-1|k-1)是X(k-1|k-1)對應(yīng)的covariance,A’表示A的轉(zhuǎn)置矩陣,Q是系統(tǒng)過程的covariance。式子1,2就是卡爾曼濾波器5個公式當中的前兩個,也就是對系統(tǒng)的預(yù)測。
現(xiàn)在我們有了現(xiàn)在狀態(tài)的預(yù)測結(jié)果,然后我們再收集現(xiàn)在狀態(tài)的測量值。結(jié)合預(yù)測值和測量值,我們可以得到現(xiàn)在狀態(tài)(k)的最優(yōu)化估算值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)
到現(xiàn)在為止,我們已經(jīng)得到了k狀態(tài)下最優(yōu)的估算值X(k|k)。但是為了要另卡爾曼濾波器不斷的運行下去直到系統(tǒng)過程結(jié)束,我們還要更新k狀態(tài)下X(k|k)的covariance: P(k|k)=(I-Kg(k) H)P(k|k-1) ……… (5) 其中I 為1的矩陣,對于單模型單測量,I=1。當系統(tǒng)進入k+1狀態(tài)時,P(k|k)就是式子(2)的P(k-1|k-1)。這樣,算法就可以自回歸的運算下去。
卡爾曼濾波器的原理基本描述了,式子1,2,3,4和5就是他的5 個基本公式。根據(jù)這5個公式,可以很容易的實現(xiàn)計算機的程序
4月4日
OpenCV學(xué)習(xí)筆記(四)運動物體跟蹤的camshift算法CamShift算法 Back Projection計算。 Mean Shift算法 CamShift算法 1 Back Projection計算 1. 計算被跟蹤目標的色彩直方圖。在各種色彩空間中,只有HSI空間(或與HSI類似的色彩空間)中的H分量可以表示顏色信息。所以在具體的計算過程中,首先將其他的色彩空間的值轉(zhuǎn)化到HSI空間,然后會其中的H分量做1D直方圖計算。 2. 根據(jù)獲得的色彩直方圖將原始圖像轉(zhuǎn)化成色彩概率分布圖像,這個過程就被稱作"Back Projection"。 在OpenCV中的直方圖函數(shù)中,包含Back Projection的函數(shù),函數(shù)原型是: void cvCalcBackProject(IplImage** img, CvArr** backproject, const CvHistogram* hist); 傳遞給這個函數(shù)的參數(shù)有三個: 1. IplImage** img:存放原始圖像,輸入。 2. CvArr** backproject:存放Back Projection結(jié)果,輸出。 3. CvHistogram* hist:存放直方圖,輸入
下面就給出計算Back Projection的OpenCV代碼。 1.準備一張只包含被跟蹤目標的圖片,將色彩空間轉(zhuǎn)化到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); //轉(zhuǎn)化到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值做適當?shù)牧炕幚?在這里我們將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.結(jié)果:result即為我們需要的. 2) Mean Shift算法 這里來到了CamShift算法,OpenCV實現(xiàn)的第二部分,這一次重點討論Mean Shift算法。 在討論Mean Shift算法之前,首先討論在2D概率分布圖像中,如何計算某個區(qū)域的重心(Mass Center)的問題,重心可以通過以下公式來計算: 1.計算區(qū)域內(nèi)0階矩 for(int i=0;i<height;i++) for(int j=0;j<width;j++) M00+=I(i,j) 2.區(qū)域內(nèi)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.計算此時窗口內(nèi)的Mass Center. 3.調(diào)整窗口的中心到Mass Center. 4.重復(fù)2和3,直到窗口中心"會聚",即每次窗口移動的距離小于一定的閾值。
在OpenCV中,提供Mean Shift算法的函數(shù),函數(shù)的原型是: int cvMeanShift(IplImage* imgprob,CvRect windowIn, CvTermCriteria criteria,CvConnectedComp* out);
需要的參數(shù)為: 1.IplImage* imgprob:2D概率分布圖像,傳入; 2.CvRect windowIn:初始的窗口,傳入; 3.CvTermCriteria criteria:停止迭代的標準,傳入; 4.CvConnectedComp* out:查詢結(jié)果,傳出。 (注:構(gòu)造CvTermCriteria變量需要三個參數(shù),一個是類型,另一個是迭代的最大次數(shù),最后一個表示特定的閾值。例如可以這樣構(gòu)造criteria:criteria=cvTermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS,10,0.1)。)
返回的參數(shù): 1.int:迭代的次數(shù)。
實現(xiàn)代碼:暫時缺 3) CamShift算法 在了解了MeanShift算法以后,我們將MeanShift算法擴展到連續(xù)圖像序列(一般都是指視頻圖像序列),這樣就形成了CamShift算法。CamShift算法的全稱是"Continuously Apaptive Mean-SHIFT",它的基本思想是視頻圖像的所有幀作MeanShift運算,并將上一幀的結(jié)果(即Search Window的中心和大?。┳鳛橄乱粠琈eanShift算法的Search Window的初始值,如此迭代下去,就可以實現(xiàn)對目標的跟蹤。整個算法的具體步驟分5步: Step 1:將整個圖像設(shè)為搜尋區(qū)域。 Step 2:初始話Search Window的大小和位置。 Step 3:計算Search Window內(nèi)的彩色概率分布,此區(qū)域的大小比Search Window要稍微大一點。 Step 4:運行MeanShift。獲得Search Window新的位置和大小。 Step 5:在下一幀視頻圖像中,用Step 3獲得的值初始化Search Window的位置和大小。跳轉(zhuǎn)到Step 3繼續(xù)運行。 2.實現(xiàn) 在OpenCV中,有實現(xiàn)CamShift算法的函數(shù),此函數(shù)的原型是: cvCamShift(IplImage* imgprob, CvRect windowIn, CvTermCriteria criteria, CvConnectedComp* out, CvBox2D* box=0); 其中: imgprob:色彩概率分布圖像。 windowIn:Search Window的初始值。 Criteria:用來判斷搜尋是否停止的一個標準。 out:保存運算結(jié)果,包括新的Search Window的位置和面積。 box:包含被跟蹤物體的最小矩形。
說明: 1.在OpenCV 4.0 beta的目錄中,有CamShift的例子。遺憾的是這個例子目標的跟蹤是半自動的,即需要人手工選定一個目標。我正在努力嘗試全自動的目標跟蹤,希望可以和大家能在這方面與大家交流。 3月31日
OpenCV學(xué)習(xí)筆記(三)人臉檢測的代碼分析OpenCV學(xué)習(xí)筆記(三)人臉檢測的代碼分析
一、預(yù)備知識: 1、動態(tài)內(nèi)存存儲及操作函數(shù) 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; 內(nèi)存存儲器是一個可用來存儲諸如序列,輪廓,圖形,子劃分等動態(tài)增長數(shù)據(jù)結(jié)構(gòu)的底層結(jié)構(gòu)。它是由一系列以同等大小的內(nèi)存塊構(gòu)成,呈列表型 ---bottom 域指的是列首,top 域指的是當前指向的塊但未必是列尾.在bottom和top之間所有的塊(包括bottom, 不包括top)被完全占據(jù)了空間;在 top和列尾之間所有的塊(包括塊尾,不包括top)則是空的;而top塊本身則被占據(jù)了部分空間 -- free_space 指的是top塊剩余的空字節(jié)數(shù)。新分配的內(nèi)存緩沖區(qū)(或顯示的通過 cvMemStorageAlloc 函數(shù)分配,或隱示的通過 cvSeqPush, cvGraphAddEdge等高級函數(shù)分配)總是起始于當前塊(即top塊)的剩余那部分,如果剩余那部分能滿足要求(夠分配的大?。?。分配后,free_space 就減少了新分配的那部分內(nèi)存大小,外加一些用來保存適當列型的附加大小。當top塊的剩余空間無法滿足被分配的塊(緩沖區(qū))大小時,top塊的下一個存儲塊被置為當前塊(新的top塊) -- free_space 被置為先前分配的整個塊的大小。如果已經(jīng)不存在空的存儲塊(即:top塊已是列尾),則必須再分配一個新的塊(或從parent那繼承,見 cvCreateChildMemStorage)并將該塊加到列尾上去。于是,存儲器(memory storage)就如同棧(Stack)那樣, bottom指向棧底,(top, free_space)對指向棧頂。棧頂可通過 cvSaveMemStoragePos保存,通過 cvRestoreMemStoragePos 恢復(fù)指向, 通過 cvClearStorage 重置。
CvMemBlock
內(nèi)存存儲塊結(jié)構(gòu) typedef struct CvMemBlock { struct CvMemBlock* prev; struct CvMemBlock* next; } CvMemBlock; CvMemBlock 代表一個單獨的內(nèi)存存儲塊結(jié)構(gòu)。 內(nèi)存存儲塊中的實際數(shù)據(jù)存儲在 header塊 之后(即:存在一個頭指針 head 指向的塊 header ,該塊不存儲數(shù)據(jù)),于是,內(nèi)存塊的第 i 個字節(jié)可以通過表達式 ((char*)(mem_block_ptr+1))[i] 獲得。然而,通常沒必要直接去獲得存儲結(jié)構(gòu)的域。
CvMemStoragePos 內(nèi)存存儲塊地址 typedef struct CvMemStoragePos { CvMemBlock* top; int free_space; } CvMemStoragePos; 該結(jié)構(gòu)(如以下所說)保存棧頂?shù)牡刂?,棧頂可以通過 cvSaveMemStoragePos 保存,也可以通過 cvRestoreMemStoragePos 恢復(fù)。
________________________________________ cvCreateMemStorage 創(chuàng)建內(nèi)存塊 CvMemStorage* cvCreateMemStorage( int block_size=0 ); block_size:存儲塊的大小以字節(jié)表示。如果大小是 0 byte, 則將該塊設(shè)置成默認值 當前默認大小為64k.
函數(shù) cvCreateMemStorage 創(chuàng)建一內(nèi)存塊并返回指向塊首的指針。起初,存儲塊是空的。頭部(即:header)的所有域值都為 0,除了 block_size 外. ________________________________________ cvCreateChildMemStorage 創(chuàng)建子內(nèi)存塊 CvMemStorage* cvCreateChildMemStorage( CvMemStorage* parent ); parent 父內(nèi)存塊
函數(shù) cvCreateChildMemStorage 創(chuàng)建一類似于普通內(nèi)存塊的子內(nèi)存塊,除了內(nèi)存分配/釋放機制不同外。當一個子存儲塊需要一個新的塊加入時,它就試圖從parent 那得到這樣一個塊。如果 parent 中 還未被占據(jù)空間的那些塊中的第一個塊是可獲得的,就獲取第一個塊(依此類推),再將該塊從 parent 那里去除。如果不存在這樣的塊,則 parent 要么分配一個,要么從它自己 parent (即:parent 的 parent) 那借個過來。換句話說,完全有可能形成一個鏈或更為復(fù)雜的結(jié)構(gòu),其中的內(nèi)存存儲塊互為 child/ parent 關(guān)系(父子關(guān)系)。當子存儲結(jié)構(gòu)被釋放或清除,它就把所有的塊還給各自的 parent. 在其他方面,子存儲結(jié)構(gòu)同普通存儲結(jié)構(gòu)一樣。 子存儲結(jié)構(gòu)在下列情況中是非常有用的。想象一下,如果用戶需要處理存儲在某個塊中的動態(tài)數(shù)據(jù),再將處理的結(jié)果存放在該塊中。在使用了最簡單的方法處理后,臨時數(shù)據(jù)作為輸入和輸出數(shù)據(jù)被存放在了同一個存儲塊中,于是該存儲塊看上去就類似下面處理后的樣子: Dynamic data processing without using child storage. 結(jié)果,在存儲塊中,出現(xiàn)了垃圾(臨時數(shù)據(jù))。然而,如果在開始處理數(shù)據(jù)前就先建立一個子存儲塊,將臨時數(shù)據(jù)寫入子存儲塊中并在最后釋放子存儲塊,那么最終在 源/目的存儲塊 (source / destination storage) 中就不會出現(xiàn)垃圾, 于是該存儲塊看上去應(yīng)該是如下形式:Dynamic data processing using a child storage. cvReleaseMemStorage
釋放內(nèi)存塊 void cvReleaseMemStorage( CvMemStorage** storage ); storage: 指向被釋放了的存儲塊的指針
函數(shù) cvReleaseMemStorage 釋放所有的存儲(內(nèi)存)塊 或者 將它們返回給各自的 parent(如果需要的話)。 接下來再釋放 header塊(即:釋放頭指針 head 指向的塊 = free(head))并清除指向該塊的指針(即:head = NULL)。在釋放作為 parent 的塊之前,先清除各自的 child 塊。 cvClearMemStorage
清空內(nèi)存存儲塊 void cvClearMemStorage( CvMemStorage* storage ); storage:存儲存儲塊
函數(shù) cvClearMemStorage 將存儲塊的 top 置到存儲塊的頭部(注:清空存儲塊中的存儲內(nèi)容)。該函數(shù)并不釋放內(nèi)存(僅清空內(nèi)存)。假使該內(nèi)存塊有一個父內(nèi)存塊(即:存在一內(nèi)存塊與其有父子關(guān)系),則函數(shù)就將所有的塊返回給其 parent. cvMemStorageAlloc
在存儲塊中分配以內(nèi)存緩沖區(qū) void* cvMemStorageAlloc( CvMemStorage* storage, size_t size ); storage:內(nèi)存塊. size:緩沖區(qū)的大小. 函數(shù) cvMemStorageAlloc 在存儲塊中分配一內(nèi)存緩沖區(qū)。該緩沖區(qū)的大小不能超過內(nèi)存塊的大小,否則就會導(dǎo)致運行時錯誤。緩沖區(qū)的地址被調(diào)整為CV_STRUCT_ALIGN 字節(jié) (當前為 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')。如果參數(shù)為負數(shù),函數(shù)就計算該字符串的長度。 函數(shù) cvMemStorageAlloString 在存儲塊中創(chuàng)建了一字符串的拷貝。它返回一結(jié)構(gòu),該結(jié)構(gòu)包含字符串的長度(該長度或通過用戶傳遞,或通過計算得到)和指向被拷貝了的字符串的指針。 cvSaveMemStoragePos
保存內(nèi)存塊的位置(地址) void cvSaveMemStoragePos( const CvMemStorage* storage, CvMemStoragePos* pos ); storage:內(nèi)存塊.
pos:內(nèi)存塊頂部位置。 函數(shù) cvSaveMemStoragePos 將存儲塊的當前位置保存到參數(shù) pos 中。 函數(shù) cvRestoreMemStoragePos 可進一步獲取該位置(地址)。 cvRestoreMemStoragePos
恢復(fù)內(nèi)存存儲塊的位置 void cvRestoreMemStoragePos( CvMemStorage* storage, CvMemStoragePos* pos ); storage:內(nèi)存塊. pos:新的存儲塊的位置 函數(shù) cvRestoreMemStoragePos 通過參數(shù) pos 恢復(fù)內(nèi)存塊的位置。該函數(shù)和函數(shù) cvClearMemStorage 是釋放被占用內(nèi)存塊的唯一方法。注意:沒有什么方法可去釋放存儲塊中被占用的部分內(nèi)存。 2、分類器結(jié)構(gòu)及操作函數(shù): 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 個具有相應(yīng)權(quán)重的矩形組成
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 */ 所有的結(jié)構(gòu)都代表一個級聯(lián)boosted Haar分類器。級聯(lián)有下面的等級結(jié)構(gòu):
Cascade: Stage1: Classifier11: Feature11 Classifier12: Feature12 ... Stage2: Classifier21: Feature21 ... ... 整個等級可以手工構(gòu)建,也可以利用函數(shù)cvLoadHaarClassifierCascade從已有的磁盤文件或嵌入式基中導(dǎo)入。 特征檢測用到的函數(shù): cvLoadHaarClassifierCascade 從文件中裝載訓(xùn)練好的級聯(lián)分類器或者從OpenCV中嵌入的分類器數(shù)據(jù)庫中導(dǎo)入 CvHaarClassifierCascade* cvLoadHaarClassifierCascade( const char* directory, CvSize orig_window_size ); directory :訓(xùn)練好的級聯(lián)分類器的路徑 orig_window_size:級聯(lián)分類器訓(xùn)練中采用的檢測目標的尺寸。因為這個信息沒有在級聯(lián)分類器中存儲,所有要單獨指出。 函數(shù) cvLoadHaarClassifierCascade 用于從文件中裝載訓(xùn)練好的利用海爾特征的級聯(lián)分類器,或者從OpenCV中嵌入的分類器數(shù)據(jù)庫中導(dǎo)入。分類器的訓(xùn)練可以應(yīng)用函數(shù)haartraining(詳細察看opencv/apps/haartraining) 函數(shù) 已經(jīng)過時了?,F(xiàn)在的目標檢測分類器通常存儲在 XML 或 YAML 文件中,而不是通過路徑導(dǎo)入。從文件中導(dǎo)入分類器,可以使用函數(shù) cvLoad 。 cvReleaseHaarClassifierCascade
釋放haar classifier cascade。 void cvReleaseHaarClassifierCascade( CvHaarClassifierCascade** cascade ); cascade :雙指針類型指針指向要釋放的cascade. 指針由函數(shù)聲明。 函數(shù) cvReleaseHaarClassifierCascade 釋放cascade的動態(tài)內(nèi)存,其中cascade的動態(tài)內(nèi)存或者是手工創(chuàng)建,或者通過函數(shù) 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 分類器級聯(lián)的內(nèi)部標識形式 storage 用來存儲檢測到的一序列候選目標矩形框的內(nèi)存區(qū)域。 scale_factor 在前后兩次相繼的掃描中,搜索窗口的比例系數(shù)。例如1.1指將搜索窗口依次擴大10%。 min_neighbors 構(gòu)成檢測目標的相鄰矩形的最小個數(shù)(缺?。?)。如果組成檢測目標的小矩形的個數(shù)和小于min_neighbors-1 都會被排除。如果min_neighbors 為 0, 則函數(shù)不做任何操作就返回所有的被檢候選矩形框,這種設(shè)定值一般用在用戶自定義對檢測結(jié)果的組合程序上。 flags 操作方式。當前唯一可以定義的操作方式是 CV_HAAR_DO_CANNY_PRUNING。如果被設(shè)定,函數(shù)利用Canny邊緣檢測器來排除一些邊緣很少或者很多的圖像區(qū)域,因為這樣的區(qū)域一般不含被檢目標。人臉檢測中通過設(shè)定閾值使用了這種方法,并因此提高了檢測速度。 min_size 檢測窗口的最小尺寸。缺省的情況下被設(shè)為分類器訓(xùn)練時采用的樣本尺寸(人臉檢測中缺省大小是~20×20)。 函數(shù) cvHaarDetectObjects 使用針對某目標物體訓(xùn)練的級聯(lián)分類器在圖像中找到包含目標物體的矩形區(qū)域,并且將這些區(qū)域作為一序列的矩形框返回。函數(shù)以不同比例大小的掃描窗口對圖像進行幾次搜索(察看cvSetImagesForHaarClassifierCascade)。 每次都要對圖像中的這些重疊區(qū)域利用cvRunHaarClassifierCascade進行檢測。 有時候也會利用某些繼承(heuristics)技術(shù)以減少分析的候選區(qū)域,例如利用 Canny 裁減 (prunning)方法。 函數(shù)在處理和收集到候選的方框(全部通過級聯(lián)分類器各層的區(qū)域)之后,接著對這些區(qū)域進行組合并且返回一系列各個足夠大的組合中的平均矩形。調(diào)節(jié)程序中的缺省參數(shù)(scale_factor=1.1, min_neighbors=3, flags=0)用于對目標進行更精確同時也是耗時較長的進一步檢測。為了能對視頻圖像進行更快的實時檢測,參數(shù)設(shè)置通常是:scale_factor=1.2, min_neighbors=2, flags=CV_HAAR_DO_CANNY_PRUNING, min_size=<minimum possible face size> (例如, 對于視頻會議的圖像區(qū)域). cvSetImagesForHaarClassifierCascade
為隱藏的cascade(hidden cascade)指定圖像 void cvSetImagesForHaarClassifierCascade( CvHaarClassifierCascade* cascade, const CvArr* sum, const CvArr* sqsum, const CvArr* tilted_sum, double scale ); cascade 隱藏 Harr 分類器級聯(lián) (Hidden Haar classifier cascade), 由函數(shù) cvCreateHidHaarClassifierCascade生成 sum 32-比特,單通道圖像的積分圖像(Integral (sum) 單通道 image of 32-比特 integer format). 這幅圖像以及隨后的兩幅用于對快速特征的評價和亮度/對比度的歸一化。 它們都可以利用函數(shù) cvIntegral從8-比特或浮點數(shù) 單通道的輸入圖像中得到。 sqsum 單通道64比特圖像的平方和圖像 tilted_sum 單通道32比特整數(shù)格式的圖像的傾斜和(Tilted sum) scale cascade的窗口比例. 如果 scale=1, 就只用原始窗口尺寸檢測 (只檢測同樣尺寸大小的目標物體) - 原始窗口尺寸在函數(shù)cvLoadHaarClassifierCascade中定義 (在 "<default_face_cascade>"中缺省為24x24), 如果scale=2, 使用的窗口是上面的兩倍 (在face cascade中缺省值是48x48 )。 這樣盡管可以將檢測速度提高四倍,但同時尺寸小于48x48的人臉將不能被檢測到。 函數(shù) cvSetImagesForHaarClassifierCascade 為hidden classifier cascade 指定圖像 and/or 窗口比例系數(shù)。 如果圖像指針為空,會繼續(xù)使用原來的圖像(i.e. NULLs 意味這"不改變圖像")。比例系數(shù)沒有 "protection" 值,但是原來的值可以通過函數(shù) cvGetHaarClassifierCascadeScale 重新得到并使用。這個函數(shù)用于對特定圖像中檢測特定目標尺寸的cascade分類器的設(shè)定。函數(shù)通過cvHaarDetectObjects進行內(nèi)部調(diào)用,但當需要在更低一層的函數(shù)cvRunHaarClassifierCascade中使用的時候,用戶也可以自行調(diào)用。 cvRunHaarClassifierCascade
在給定位置的圖像中運行 cascade of boosted classifier int cvRunHaarClassifierCascade( CvHaarClassifierCascade* cascade, CvPoint pt, int start_stage=0 ); cascade Haar 級聯(lián)分類器
pt 待檢測區(qū)域的左上角坐標。待檢測區(qū)域大小為原始窗口尺寸乘以當前設(shè)定的比例系數(shù)。當前窗口尺寸可以通過cvGetHaarClassifierCascadeWindowSize重新得到。 start_stage 級聯(lián)層的初始下標值(從0開始計數(shù))。函數(shù)假定前面所有每層的分類器都已通過。這個特征通過函數(shù)cvHaarDetectObjects內(nèi)部調(diào)用,用于更好的處理器高速緩沖存儲器。 函數(shù) cvRunHaarHaarClassifierCascade 用于對單幅圖片的檢測。在函數(shù)調(diào)用前首先利用 cvSetImagesForHaarClassifierCascade設(shè)定積分圖和合適的比例系數(shù) (=> 窗口尺寸)。當分析的矩形框全部通過級聯(lián)分類器每一層的時返回正值(這是一個候選目標),否則返回0或負值。 二、例程分析:
例子:利用級聯(lián)的Haar classifiers尋找檢測目標(e.g. faces). #include "cv.h" #include "highgui.h" //讀取訓(xùn)練好的分類器。
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); //創(chuàng)建動態(tài)內(nèi)存 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 );//函數(shù) cvPyrDown 使用 Gaussian 金字塔分解對輸入圖像向下采樣。首先它對輸入圖像用指定濾波器進行卷積,然后通過拒絕偶數(shù)的行與列來下采樣圖像。 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 ); //釋放動態(tài)內(nèi)存 } /* 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;
} 關(guān)鍵代碼很簡單,裝載分類器,對輸入圖像進行金字塔采樣,然后用cv的函數(shù)進行檢測目標,最后輸出檢測到的目標矩形。 OpenCV學(xué)習(xí)筆記(二)基于Haar-like特征的層疊推進分類器快速目標檢測OpenCV學(xué)習(xí)筆記之二――基于Haar-like特征的層疊推進分類器快速目標檢測 一、簡介 目標檢測方法最初由Paul Viola [Viola01]提出,并由Rainer Lienhart [Lienhart02]對這一方法進行了改善。該方法的基本步驟為: 首先,利用樣本(大約幾百幅樣本圖片)的 harr 特征進行分類器訓(xùn)練,得到一個級聯(lián)的boosted分類器。 分類器中的"級聯(lián)"是指最終的分類器是由幾個簡單分類器級聯(lián)組成。在圖像檢測中,被檢窗口依次通過每一級分類器, 這樣在前面幾層的檢測中大部分的候選區(qū)域就被排除了,全部通過每一級分類器檢測的區(qū)域即為目標區(qū)域。 分類器訓(xùn)練完以后,就可以應(yīng)用于輸入圖像中的感興趣區(qū)域(與訓(xùn)練樣本相同的尺寸)的檢測。檢測到目標區(qū)域(汽車或人臉)分類器輸出為1,否則輸出為0。為了檢測整副圖像,可以在圖像中移動搜索窗口,檢測每一個位置來確定可能的目標。 為了搜索不同大小的目標物體,分類器被設(shè)計為可以進行尺寸改變,這樣比改變待檢圖像的尺寸大小更為有效。所以,為了在圖像中檢測未知大小的目標物體,掃描程序通常需要用不同比例大小的搜索窗口對圖片進行幾次掃描。 目前支持這種分類器的boosting技術(shù)有四種: Discrete Adaboost, Real Adaboost, Gentle Adaboost and Logitboost。 "boosted" 即指級聯(lián)分類器的每一層都可以從中選取一個boosting算法(權(quán)重投票),并利用基礎(chǔ)分類器的自我訓(xùn)練得到。 根據(jù)上面的分析,目標檢測分為三個步驟: 1、 樣本的創(chuàng)建 2、 訓(xùn)練分類器 3、 利用訓(xùn)練好的分類器進行目標檢測。 二、樣本創(chuàng)建 訓(xùn)練樣本分為正例樣本和反例樣本,其中正例樣本是指待檢目標樣本(例如人臉或汽車等),反例樣本指其它任意圖片,所有的樣本圖片都被歸一化為同樣的尺寸大小(例如,20x20)。 負樣本 負樣本可以來自于任意的圖片,但這些圖片不能包含目標特征。負樣本由背景描述文件來描述。背景描述文件是一個文本文件,每一行包含了一個負樣本圖片的文件名(基于描述文件的相對路徑)。該文件必須手工創(chuàng)建。 e.g: 負樣本描述文件的一個例子: 假定目錄結(jié)構(gòu)如下: /img img1.jpg img2.jpg bg.txt 則背景描述文件bg.txt的內(nèi)容為: img/img1.jpg img/img2.jpg 正樣本 正樣本由程序craatesample程序來創(chuàng)建。該程序的源代碼由OpenCV給出,并且在bin目錄下包含了這個可執(zhí)行的程序。 正樣本可以由單個的目標圖片或者一系列的事先標記好的圖片來創(chuàng)建。 Createsamples程序的命令行參數(shù): 命令行參數(shù): -vec <vec_file_name> 訓(xùn)練好的正樣本的輸出文件名。 -img<image_file_name> 源目標圖片(例如:一個公司圖標) -bg<background_file_name> 背景描述文件。 -num<number_of_samples> 要產(chǎn)生的正樣本的數(shù)量,和正樣本圖片數(shù)目相同。 -bgcolor<background_color> 背景色(假定當前圖片為灰度圖)。背景色制定了透明色。對于壓縮圖片,顏色方差量由bgthresh參數(shù)來指定。則在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> 最大旋轉(zhuǎn)角度,以弧度為單位。 -show 如果指定,每個樣本會被顯示出來,按下"esc"會關(guān)閉這一開關(guān),即不顯示樣本圖片,而創(chuàng)建過程繼續(xù)。這是個有用的debug選項。 -w<sample_width> 輸出樣本的寬度(以像素為單位) -h(huán)《sample_height》 輸出樣本的高度,以像素為單位。 注:正樣本也可以從一個預(yù)先標記好的圖像集合中獲取。這個集合由一個文本文件來描述,類似于背景描述文件。每一個文本行對應(yīng)一個圖片。每行的第一個元素是圖片文件名,第二個元素是對象實體的個數(shù)。后面緊跟著的是與之匹配的矩形框(x, y, 寬度,高度)。 下面是一個創(chuàng)建樣本的例子: 假定我們要進行人臉的檢測,有5個正樣本圖片文件img1.bmp,…img5.bmp;有2個背景圖片文件:bg1.bmp,bg2.bmp,文件目錄結(jié)構(gòu)如下: positive img1.bmp …… Img5.bmp negative bg1.bmp bg2.bmp info.dat bg.txt 正樣本描述文件info.dat的內(nèi)容如下: Positive/imag1.bmp 1 0 0 24 28 …… Positive/imag5.bmp 1 0 0 24 28 圖片img1.bmp包含了單個目標對象實體,矩形為(0,0,24,28)。 注意:要從圖片集中創(chuàng)建正樣本,要用-info參數(shù)而不是用-img參數(shù)。 -info <collect_file_name> 標記特征的圖片集合的描述文件。 背景(負樣本)描述文件的內(nèi)容如下: nagative/bg1.bmp nagative/bg2.bmp 我們用一個批處理文件run.bat來進行正樣本的創(chuàng)建:該文件的內(nèi)容如下: 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可執(zhí)行程序,生成的正樣本文件a.vec在e:/face目錄下。 三、訓(xùn)練分類器 樣本創(chuàng)建之后,接下來要訓(xùn)練分類器,這個過程是由haartraining程序來實現(xiàn)的。該程序源碼由OpenCV自帶,且可執(zhí)行程序在OpenCV安裝目錄的bin目錄下。 Haartraining的命令行參數(shù)如下: -data<dir_name> 存放訓(xùn)練好的分類器的路徑名。 -vec<vec_file_name> 正樣本文件名(由trainingssamples程序或者由其他的方法創(chuàng)建的) -bg<background_file_name> 背景描述文件。 -npos<number_of_positive_samples>, -nneg<number_of_negative_samples> 用來訓(xùn)練每一個分類器階段的正/負樣本。合理的值是:nPos = 7000;nNeg = 3000 -nstages<number_of_stages> 訓(xùn)練的階段數(shù)。 -nsplits<number_of_splits> 決定用于階段分類器的弱分類器。如果1,則一個簡單的stump classifier被使用。如果是2或者更多,則帶有number_of_splits個內(nèi)部節(jié)點的CART分類器被使用。 -mem<memory_in_MB> 預(yù)先計算的以MB為單位的可用內(nèi)存。內(nèi)存越大則訓(xùn)練的速度越快。 -sym(default) -nonsym 指定訓(xùn)練的目標對象是否垂直對稱。垂直對稱提高目標的訓(xùn)練速度。例如,正面部是垂直對稱的。 -minhitrate《min_hit_rate》 每個階段分類器需要的最小的命中率。總的命中率為min_hit_rate的number_of_stages次方。 -maxfalsealarm<max_false_alarm_rate> 沒有階段分類器的最大錯誤報警率??偟腻e誤警告率為max_false_alarm_rate的number_of_stages次方。 -weighttrimming<weight_trimming> 指定是否使用權(quán)修正和使用多大的權(quán)修正。一個基本的選擇是0.9 -eqw -mode<basic(default)|core|all> 選擇用來訓(xùn)練的haar特征集的種類。basic僅僅使用垂直特征。all使用垂直和45度角旋轉(zhuǎn)特征。 -w《sample_width》 -h(huán)《sample_height》 訓(xùn)練樣本的尺寸,(以像素為單位)。必須和訓(xùn)練樣本創(chuàng)建的尺寸相同。 一個訓(xùn)練分類器的例子: 同上例,分類器訓(xùn)練的過程用一個批處理文件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 訓(xùn)練結(jié)束后,會在目錄data下生成一些子目錄,即為訓(xùn)練好的分類器。 注:OpenCv的某些版本可以將這些目錄中的分類器直接轉(zhuǎn)換成xml文件。但在實際的操作中,haartraining程序卻好像永遠不會停止,而且沒有生成xml文件,后來在OpenCV的yahoo論壇上找到一個haarconv的程序,才將分類器轉(zhuǎn)換為xml文件,其中的原因尚待研究。 四、目標檢測
OpenCV的cvHaarDetectObjects()函數(shù)(在haarFaceDetect演示程序中示例)被用來做偵測。關(guān)于該檢測的詳細分析,將在下面的筆記中詳細描述。 3月29日
一個合格的程序員該做的事情
程序員每天該做的事 1、總結(jié)自己一天任務(wù)的完成情況 最好的方式是寫工作日志,把自己今天完成了什么事情,遇見了什么問題都記錄下來,日后翻看好處多多 2、考慮自己明天應(yīng)該做的主要工作 把明天要做的事情列出來,并按照優(yōu)先級排列,第二天應(yīng)該把自己效率最高的時間分配給最重要的工作 3、考慮自己一天工作中失誤的地方,并想出避免下一次再犯的方法 出錯不要緊,最重要的是不要重復(fù)犯相同的錯誤,那是愚蠢 4、考慮自己一天工作完成的質(zhì)量和效率能否還能提高 一天只提高1%,365天你的效率就能提高多少倍你知道嗎? (1+0.01)^365 = 37 倍 5、看一個有用的新聞網(wǎng)站或讀一張有用的報紙,了解業(yè)界動態(tài) 閉門造車是不行的,了解一下別人都在做什么,對自己能帶來很多啟示 6、記住一位同事的名字及其特點 你認識公司的所有同事嗎?你了解他們嗎? 7、清理自己的代碼 今天完成的代碼,把中間的調(diào)試信息,測試代碼清理掉,按照編碼風(fēng)格整理好,注釋都寫好了嗎? 8、清理自己的桌面 當日事當日畢,保持清潔干勁的桌面才能讓你工作時不分心,程序員特別要把電腦的桌面清理干凈 程序員每月該做的事 1、至少和一個同事一起吃飯或喝茶 2、自我考核一次 3、對你的同事考核一次 3、制定下月的計劃,確定下月的工作重點 4、總結(jié)自己工作質(zhì)量改進狀況 5、有針對性地對一項工作指標做深入地分析并得出改進的方案 6、與老板溝通一次 程序員每年該做的事 1、年終總結(jié) 2、兌現(xiàn)給自己、給家人的承諾 3、下年度工作規(guī)劃 4、掌握一項新技術(shù) 5、推出一種新產(chǎn)品 6、與父母團聚一次
3月28日
OpenCV學(xué)習(xí)筆記(一)概述和系統(tǒng)配置
一、概述 |
|
來自: 牛人的尾巴 > 《機器人設(shè)計》