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