一些小概念ios
1.反投影直方圖的結果是一個機率映射,體現了已知圖像內容出現在圖像中特定位置的機率。算法
2.機率映射可以找到最初的位置,從最初的位置開始並且迭代移動,便可以找到精確的位置,這就是均值漂移算法作的事情。ide
3.均值漂移算法是以迭代的方式鎖定函數的局部最大值的。函數
關於均值漂移算法的過程(opencv)ui
事實上均值漂移算法就是尋找提早定義尋找區域中數據點的重心,或者說加權平均值。將尋找區域中心移動到數據點的重心處,並反覆這個過程直到尋找區域重心收斂到一個穩定點。spa
OpenCV中定義了兩種終止條件:迭代最大次數以及窗體重心的位移值(低於該值即以爲算法收斂)。code
在OpenCV中實現這個過程的是meanshift函數,其源碼例如如下:orm
#include "_cv.h" CV_IMPL int cvMeanShift( const void* imgProb, CvRect windowIn, CvTermCriteria criteria, CvConnectedComp* comp ) { //CvMoments用來計算矩形的重心,面積等形狀特徵 CvMoments moments; int i = 0, eps; CvMat stub, *mat = (CvMat*)imgProb; CvMat cur_win; CvRect cur_rect = windowIn; CV_FUNCNAME( "cvMeanShift" ); //初始化跟蹤窗體 if( comp ) comp->rect = windowIn; //把0階矩和1階矩先初始化置零 moments.m00 = moments.m10 = moments.m01 = 0; __BEGIN__; CV_CALL( mat = cvGetMat( mat, &stub )); //各類輸入變量不符合要求時顯示錯誤信息 if( CV_MAT_CN( mat->type ) > 1 ) CV_ERROR( CV_BadNumChannels, cvUnsupportedFormat ); if( windowIn.height <= 0 || windowIn.width <= 0 ) CV_ERROR( CV_StsBadArg, "Input window has non-positive sizes" ); if( windowIn.x < 0 || windowIn.x + windowIn.width > mat->cols || windowIn.y < 0 || windowIn.y + windowIn.height > mat->rows ) CV_ERROR( CV_StsBadArg, "Initial window is not inside the image ROI" ); //迭代的標準,精度=1.0,迭代次數=100 CV_CALL( criteria = cvCheckTermCriteria( criteria, 1., 100 )); //精度eps=1 eps = cvRound( criteria.epsilon * criteria.epsilon ); //最大循環次數=最大迭代次數criteria.max_iter=100 for( i = 0; i < criteria.max_iter; i++ ) { int dx, dy, nx, ny; double inv_m00; //選取搜索區域,對該矩形區域計算它的0,1階矩 CV_CALL( cvGetSubRect( mat, &cur_win, cur_rect )); CV_CALL( cvMoments( &cur_win, &moments )); /* Calculating center of mass */ if( fabs(moments.m00) < DBL_EPSILON ) break; //搜索區域的質量m00 inv_m00 = moments.inv_sqrt_m00*moments.inv_sqrt_m00; //搜索區域的水平重心偏移dx dx = cvRound( moments.m10 * inv_m00 - windowIn.width*0.5 ); //搜索區域的垂直重心偏移dy dy = cvRound( moments.m01 * inv_m00 - windowIn.height*0.5 ); //搜索區域的重心座標(nx,ny) nx = cur_rect.x + dx; ny = cur_rect.y + dy; //跟蹤目標處於圖像邊緣時進行一些對應的處理 if( nx < 0 ) nx = 0; else if( nx + cur_rect.width > mat->cols ) nx = mat->cols - cur_rect.width; if( ny < 0 ) ny = 0; else if( ny + cur_rect.height > mat->rows ) ny = mat->rows - cur_rect.height; dx = nx - cur_rect.x; dy = ny - cur_rect.y; cur_rect.x = nx; cur_rect.y = ny; /* Check for coverage centers mass & window */ //精度達到要求時就能夠退出循環 if( dx*dx + dy*dy < eps ) break; } __END__; //對meanshift函數的返回值賦值 if( comp ) { comp->rect = cur_rect; comp->area = (float)moments.m00; } return i; }
對這個算法想細緻研究的同窗可以參考一下Dorin Comaniciu 等人2002年寫的:blog
《Mean Shift:A Robust Approach Toward Feature Space Analysis》圖片
實際樣例
樣例代碼
#include <opencv2\core\core.hpp> #include <opencv2\highgui\highgui.hpp> #include <opencv2\imgproc\imgproc.hpp> #include <opencv2\video\tracking.hpp> #include <iostream> #include <vector> using namespace std; #include "objectFinder.h" #include "colorhistogram.h" int main() { //讀取參考圖像 cv::Mat image= cv::imread("../3.jpg"); if (!image.data) return 0; //定義查找物體 cv::Mat imageROI= image(cv::Rect(85,200,64,64)); cv::rectangle(image, cv::Rect(85,200,64,64),cv::Scalar(0,0,255)); //顯示參考圖像 cv::namedWindow("第一張圖片,標記籃球位置"); cv::imshow("第一張圖片,標記籃球位置",image); //得到色度直方圖 ColorHistogram hc; cv::MatND colorhist= hc.getHueHistogram(imageROI); //讀入目標圖像 image= cv::imread("../4.jpg"); //顯示目標圖像 cv::namedWindow("第二張圖片"); cv::imshow("第二張圖片",image); //將RGB圖像圖像轉換爲HSV圖像 cv::Mat hsv; cv::cvtColor(image, hsv, CV_BGR2HSV); //分離圖像通道 vector<cv::Mat> v; cv::split(hsv,v); //消除飽和度較低的像素點 int minSat=65; cv::threshold(v[1],v[1],minSat,255,cv::THRESH_BINARY); cv::namedWindow("第二張圖片消除飽和度較低的像素點"); cv::imshow("第二張圖片消除飽和度較低的像素點",v[1]); //進行直方圖反投影 ObjectFinder finder; finder.setHistogram(colorhist); finder.setThreshold(0.3f); int ch[1]={0}; cv::Mat result= finder.find(hsv,0.0f,180.0f,ch,1); cv::namedWindow("第二張圖片進行直方圖反投影"); cv::imshow("第二張圖片進行直方圖反投影",result); //利用位運算消除低飽和度像素 cv::bitwise_and(result,v[1],result); cv::namedWindow("第二張圖片利用位運算進一步消除低飽和度像素點"); cv::imshow("第二張圖片利用位運算進一步消除低飽和度像素點",result); // 獲得反投影直方圖機率圖像 finder.setThreshold(-1.0f); result= finder.find(hsv,0.0f,180.0f,ch,1); cv::bitwise_and(result,v[1],result); cv::namedWindow("第二張圖片處理後的二值圖像"); cv::imshow("第二張圖片處理後的二值圖像",result); cv::Rect rect(85,200,64,64); cv::rectangle(image, rect, cv::Scalar(0,0,255)); cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER,10,0.01); cout << "均值漂移迭代次數 = " << cv::meanShift(result,rect,criteria) << endl; cv::rectangle(image, rect, cv::Scalar(0,255,0)); //展現結果圖 cv::namedWindow("查找結果,紅框爲第一幅圖中籃球位置,綠框爲現位置"); cv::imshow("查找結果,紅框爲第一幅圖中籃球位置,綠框爲現位置",image); cv::waitKey(); return 0; }
-END-