OpenCV學習筆記(二十六)——小試SVM演算法ml OpenCV學習筆記(二十七)——基於級聯分類器的目標檢測objdect OpenCV學習筆記(二十八)——光流法對運動目標跟蹤Video Ope
OpenCV學習筆記(二十六)——小試SVM演算法ml
總感覺自己停留在碼農的初級階段,要想更上一層,就得靜下心來,好好研究一下演算法的東西。OpenCV作為一個計算機視覺的開源庫,肯定不會只停留在數字影象處理的初級階段,我也得加油,深入研究它的演算法庫。就從ml入手吧,最近做東西遇到隨機森林,被搞的頭大,深深感覺自己肚子裡貨太少,關鍵時刻調不出東西來。切勿浮躁,一點點研究吧。
這次就先介紹一下機器學習中的一個常用演算法SVM演算法,即支援向量機Support Vector Machine(SVM),是一種有監督學習方法,更多介紹請見維基百科http://zh.wikipedia.org/wiki/SVM
OpenCV開發SVM演算法是基於LibSVM軟體包開發的,LibSVM是臺灣大學林智仁(Lin Chih-Jen)等開發設計的一個簡單、易於使用和快速有效的SVM模式識別與迴歸的軟體包。用OpenCV使用SVM演算法的大概流程是
1)設定訓練樣本集
需要兩組資料,一組是資料的類別,一組是資料的向量資訊。
2)設定SVM引數
利用CvSVMParams類實現類內的成員變數svm_type表示SVM型別:
CvSVM::C_SVC C-SVC
CvSVM::NU_SVCv-SVC
CvSVM::ONE_CLASS一類SVM
CvSVM::EPS_SVRe-SVR
CvSVM::NU_SVRv-SVR
成員變數kernel_type表示核函式的型別:
CvSVM::LINEAR線性:u‘v
CvSVM::POLY多項式:(r*u'v + coef0)^degree
CvSVM::RBFRBF函式:exp(-r|u-v|^2)
CvSVM::SIGMOIDsigmoid函式:tanh(r*u'v + coef0)
成員變數degree針對多項式核函式degree的設定,gamma針對多項式/rbf/sigmoid核函式的設定,coef0針對多項式/sigmoid核函式的設定,Cvalue為損失函式,在C-SVC、e-SVR、v-SVR中有效,nu設定v-SVC、一類SVM和v-SVR引數,p為設定e-SVR中損失函式的值,class_weightsC_SVC的權重,term_crit為SVM訓練過程的終止條件。其中預設值degree = 0,gamma = 1,coef0 = 0,Cvalue = 1,nu = 0,p = 0,class_weights = 0
3)訓練SVM
呼叫CvSVM::train函式建立SVM模型,第一個引數為訓練資料,第二個引數為分類結果,最後一個引數即CvSVMParams
4)用這個SVM進行分類
呼叫函式CvSVM::predict實現分類
5)獲得支援向量
除了分類,也可以得到SVM的支援向量,呼叫函式CvSVM::get_support_vector_count獲得支援向量的個數,CvSVM::get_support_vector獲得對應的索引編號的支援向量。
實現程式碼如下:
- // step 1:
- float labels[4] = {1.0, -1.0, -1.0, -1.0};
- Mat labelsMat(3, 1, CV_32FC1, labels);
- float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
- Mat trainingDataMat(3, 2, CV_32FC1, trainingData);
- // step 2:
- CvSVMParams params;
- params.svm_type = CvSVM::C_SVC;
- params.kernel_type = CvSVM::LINEAR;
- params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER, 100, 1e-6);
- // step 3:
- CvSVM SVM;
- SVM.train(trainingDataMat, labelsMat, Mat(), Mat(), params);
- // step 4:
- Vec3b green(0, 255, 0), blue(255, 0, 0);
- for (int i=0; i<image.rows; i++)
- {
- for (int j=0; j<image.cols; j++)
- {
- Mat sampleMat = (Mat_<float>(1,2) << i,j);
- float response = SVM.predict(sampleMat);
- if (fabs(response-1.0) < 0.0001)
- {
- image.at<Vec3b>(j, i) = green;
- }
- elseif (fabs(response+1.0) < 0.001)
- {
- image.at<Vec3b>(j, i) = blue;
- }
- }
- }
- // step 5:
- int c = SVM.get_support_vector_count();
- for (int i=0; i<c; i++)
- {
- constfloat* v = SVM.get_support_vector(i);
- }
OpenCV學習筆記(二十七)——基於級聯分類器的目標檢測objdect
OpenCV支援的目標檢測的方法是利用樣本的Haar特徵進行的分類器訓練,得到的級聯boosted分類器(Cascade Classification)。注意,新版本的C++介面除了Haar特徵以外也可以使用LBP特徵。
先介紹一下相關的結構,級聯分類器的計算特徵值的基礎類FeatureEvaluator,功能包括讀操作read、複製clone、獲得特徵型別getFeatureType,分配圖片分配視窗的操作setImage、setWindow,計算有序特徵calcOrd,計算絕對特徵calcCat,建立分類器特徵的結構create函式。級聯分類器類CascadeClassifier。目標級聯矩形的分組函式groupRectangles。
接下來,我嘗試使用CascadeClassifier這個級聯分類器類檢測視訊流中的目標(haar支援的目標有人臉、人眼、嘴、鼻、身體。這裡嘗試比較成熟的人臉和眼鏡)。用load函式載入XML分類器檔案(目前提供的分類器包括Haar分類器和LBP分類器(LBP分類器資料較少))具體步驟如下:
這裡再補充一點:後來我又進行了一些實驗,對正面人臉分類器進行了實驗,總共有4個,alt、alt2、alt_tree、default。對比下來發現alt和alt2的效果比較好,alt_tree耗時較長,default是一個輕量級的,經常出現誤檢測。所以還是推薦大家使用haarcascade_frontalface_atl.xml和haarcascade_frontalface_atl2.xml。
1)載入級聯分類器
呼叫CascadeClassifier類成員函式load實現,程式碼為:
- CascadeClassifier face_cascade;
- face_cascade.load("haarcascade_frontalface_alt.xml");
2)讀取視訊流
這部分比較基礎啦~~從檔案中讀取影象序列,讀取視訊檔案,讀取攝像頭視訊流看過我之前的文章,這3種方法應該瞭然於心。
3)對每一幀使用該分類器
這裡先將影象變成灰度圖,對它應用直方圖均衡化,做一些預處理的工作。接下來檢測人臉,呼叫detectMultiScale函式,該函式在輸入影象的不同尺度中檢測物體,引數image為輸入的灰度影象,objects為得到被檢測物體的矩形框向量組,scaleFactor為每一個影象尺度中的尺度引數,預設值為1.1,minNeighbors引數為每一個級聯矩形應該保留的鄰近個數(沒能理解這個引數,-_-|||),預設為3,flags對於新的分類器沒有用(但目前的haar分類器都是舊版的,CV_HAAR_DO_CANNY_PRUNING利用Canny邊緣檢測器來排除一些邊緣很少或者很多的影象區域,CV_HAAR_SCALE_IMAGE就是按比例正常檢測,CV_HAAR_FIND_BIGGEST_OBJECT只檢測最大的物體,CV_HAAR_DO_ROUGH_SEARCH只做初略檢測),預設為0.minSize和maxSize用來限制得到的目標區域的範圍。這裡呼叫的程式碼如下:
- face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CV_HAAR_SCALE_IMAGE, Size(30, 30) );
4)顯示目標
這個也比較簡單,呼叫ellips函式將剛才得到的faces矩形框都顯示出來
更進一步,也可以在得到的每一幅人臉中得到人眼的位置,呼叫的分類器檔案為haarcascade_eye_tree_eyeglasses.xml,先將臉部區域選為興趣區域ROI,重複上訴步驟即可,這裡就不詳細介紹了。當然,感興趣的朋友也可以試試其他的xml檔案作為分類器玩一下啊,感覺LBP特徵雖然xml檔案的大小很小,但效果還可以,不過我沒有做過多的測試。光說不練假把式,最後貼上效果圖和原始碼的下載地址
OpenCV學習筆記(二十八)——光流法對運動目標跟蹤Video
OpenCV配套的教程Tutorials對於Video的部分,沒有例項進行說明,我只能摸石頭過河啦,之前試過一個camShift做目標檢測,這次試一試光流法做運動估計。這裡使用的光流法是比較常用的 Lucas-Kanade方法。對於光流法的原理,我就不過多介紹了,主要講使用OpenCV如何實現。
首先利用goodFeaturesToTrack函式得到影象中的強邊界作為跟蹤的特徵點,接下來要呼叫calcOpticalFlowPyrLK函式,輸入兩幅連續的影象,並在第一幅影象裡選擇一組特徵點,輸出為這組點在下一幅影象中的位置。再把得到的跟蹤結果過濾一下,去掉不好的特徵點。再把特徵點的跟蹤路徑標示出來。說著好簡單哦~~
OpenCV學習筆記(二十九)——視訊前景的提取Video
視訊捕捉的物件中,背景通常保持不變。一般分析中關注移動的前景物體,威力提取出前景物體,需要建立背景的模型,將模型和當前幀進行比對檢測前景物體。前景提取應用非常廣泛,特別是在智慧監控領域中。
如果有不含前景物體的背景圖片,提取前景的工作相對容易,只需要比對當前幀和背景圖片的不同,呼叫函式absdiff實現。但是大多數情況,獲得背景圖片是不可能的,比如在複雜的場景下,或者有光線條件的變化。因此,就需要動態的變換背景。一種簡單的辦法是對所觀察到的圖片取平均,但這樣做也有很多弊端,首先,這種辦法在計算背景圖片的前需要輸入大量的圖片,其次我們進行取平均的過程中不能有前景物體進入。所以一種相對好的辦法是動態建立背景圖片並實時更新。
具體的實現過程主要分為兩部分:一部分是呼叫absdiff函式找出當前圖片和背景圖片的區別,這之中使用了threshold函式去除為前景,當前圖片畫素與背景圖片畫素變化超過一定閾值的時候才認定其為前景;另一個工作是更新背景圖片,呼叫函式accumulateWeighted,根據權重引數可以調整背景更新的速度,將當前圖片更新到背景中,這裡巧妙利用得到的前景提取結果作為mask,在更新背景圖片的過程中避免了前景的干擾。
雖然可以調整閾值引數和權重更新速度調節前景提取的結果,但從測試視訊可以發現,樹葉的運動對結果的干擾還是不小的,特別對於第一幀出現前景的情況,由於後續更新背景都是對前景mask後對背景進行更新的,所以第一幀的前景部分對背景的影響因子很難被更新掉。這裡提出一種改進的辦法——混合高斯模型。可以使一個畫素具有更多的資訊,這樣可以有效的減少類似樹葉的不停飄動,水波的不停盪漾這種對前景的干擾。這個精密的演算法比之前我所介紹的簡單方法要複雜很多,不易實現。還好,OpenCV已經為我們做好了相關工作,將其封裝在類BackgroundSubtractorMOG,使用起來非常方便。實現程式碼如下:
- Mat frame;
- Mat foreground; // 前景圖片
- namedWindow("Extracted Foreground");
- // 混合高斯物體
- BackgroundSubtractorMOG mog;
- bool stop(false);
- while (!stop)
- {
- if (!capture.read(frame))
- {
- break;
- }
- // 更新背景圖片並且輸出前景
- mog(frame, foreground, 0.01);
- // 輸出的前景圖片並不是2值圖片,要處理一下顯示
- threshold(foreground, foreground, 128, 255, THRESH_BINARY_INV);
新程式的效果圖如下,下載地址為http://download.csdn.net/detail/yang_xian521/3815366
OpenCV學習筆記(三十)——解開VideoInput面紗highgui
最近做一個東西,攝像頭使用的高清攝像頭,採集出來的視訊是D1格式(720*480)。使用VideoCapture發現速度很忙,網上的朋友說VideoCapture提供的讀寫功能採用VFW,效率低下且有些格式支援不好。而 OpenCV 2.0 內建了videoInput Library,可以自動在VFW和DirectShow間切換。videoInput是老外寫的功能強大的開源視訊處理庫。是一個第三方庫,2.0~2.2的版本專門有一個3rdparty對該庫進行支援,而在最新的2.3版本中,已經講videoInput庫整合到highgui中了,想使用它的話,只需要在cmake中設定巨集WITH_VIDEOiNPUT=OFF/ON即可。好像我使用的2.3.1自帶的那個build資料夾下面的庫就是在ON條件下編譯得到的,所以就不用cmake重新編譯了。2.3中使用手冊和教程對VideoInput類隻字未提,我只好自己摸索了。還好有原始碼可以看,開源偉大。
網上見過其他朋友寫過2.2實現VideoInput的使用,我實驗發現2.3中的使用方法基本沒有變化。後面再把配套例程奉上,先把VideoInput類內的公有成員函式一一介紹一下,該類還有個相關的類是VideoDevice。包括控制是否在控制檯輸出資訊開關setVerbose函式,打印出可用視訊裝置資訊的函式listDevices,之後可以得到裝置名函式getDeviceName,視訊捕捉的回撥函式設定函式setUseCallback,調整捕捉幀率的函式setIdealFramerate(預設30fps,可修改,但不能被保證準確,directshow會嘗試一個鄰近的幀率),防止裝置休眠重新連線的函式setAutoReconnectOnFreeze,開啟裝置函式setupDevice,在setpuDevice之前可以設定視訊制式,呼叫函式為setFormat,檢測是否有新的幀函式isFrameNew,檢測視訊是否開啟isDeviceSetup,獲得資料的函式getPixels(注意這裡獲得的資料時uchar型的指標),顯示視訊設定視窗函式showSettingsWindow,控制視訊設定的相關函式有setVideoSettingFilter、setVideoSettingFilterPct、getVideoSettingFilter、setVideoSettingCamera、setVideoSettingCameraPct、getVideoSettingCamera,獲得視訊寬高資訊的函式有getWidth、getHeight、getSize,停止裝置函式stopDevice,重啟裝置函式restartDevice。
講了這麼多函式,還是直接上程式碼說話吧,我這是找的VideoInput註釋中的一個例程。
- //create a videoInput object
- videoInput VI;
- //Prints out a list of available devices and returns num of devices found
- int numDevices = VI.listDevices();
- int device1 = 0; //this could be any deviceID that shows up in listDevices
- int device2 = 1; //this could be any deviceID that shows up in listDevices
- //if you want to capture at a different frame rate (default is 30)
- //specify it here, you are not guaranteed to get this fps though.
- //VI.setIdealFramerate(dev, 60);
- //setup the first device - there are a number of options:
- VI.setupDevice(device1); //setup the first device with the default settings
- //VI.setupDevice(device1, VI_COMPOSITE); //or setup device with specific connection type
- //VI.setupDevice(device1, 320, 240); //or setup device with specified video size
- //VI.setupDevice(device1, 320, 240, VI_COMPOSITE); //or setup device with video size and connection type
- //VI.setFormat(device1, VI_NTSC_M); //if your card doesn't remember what format it should be
- //call this with the appropriate format listed above
- //NOTE: must be called after setupDevice!
- //optionally setup a second (or third, fourth ...) device - same options as above
- VI.setupDevice(device2);
- //As requested width and height can not always be accomodated
- //make sure to check the size once the device is setup
- int width = VI.getWidth(device1);
- int height = VI.getHeight(device1);
- int size = VI.getSize(device1);
- unsigned char * yourBuffer1 = new unsigned char[size];
- unsigned char * yourBuffer2 = new unsigned char[size];
- //to get the data from the device first check if the data is new
- if(VI.isFrameNew(device1)){
- VI.getPixels(device1, yourBuffer1, false, false); //fills pixels as a BGR (for openCV) unsigned char array - no flipping
- VI.getPixels(device1, yourBuffer2, true, true); //fills pixels as a RGB (for openGL) unsigned char array - flipping!
- }
- //same applies to device2 etc
- //to get a settings dialog for the device
- VI.showSettingsWindow(device1);
- //Shut down devices properly
- VI.stopDevice(device1);
- VI.stopDevice(device2);
from: http://blog.csdn.net/yang_xian521/article/category/910716