附上一篇關(guān)于金字塔理論的詳細講解,想要深入了解金字塔追蹤原理的可以研究一下寂诱,這篇博文則相對簡單易懂些歉摧。http://blog.csdn.net/app_12062011/article/details/51880258
首先灿意,我們應(yīng)該明白什么是角點追蹤。
角點追蹤扫沼,即視頻中的特征點隨著幀的改變而移動,在不斷播放的視頻中,查找一個移動的物體軌跡的過程尤揣。
接下來酪穿,我們可以根據(jù)不斷的分析而自動代入金字塔的光流檢測矿瘦。
既然我們的目的是追蹤動態(tài)的角點,那么首先應(yīng)該有已確定的特征點,這時,可以使用自動設(shè)置參數(shù)進行特征點的查找渐行,查找后調(diào)節(jié)一下精度,就可以以備使用了铸董。我們還可以使用鼠標點擊的方式來確定特征點祟印,這里需要補充一個函數(shù):
onMouse函數(shù),函數(shù)的定義方式為:
static void onMouse( int event, int x, int y, int /*flags*/, void* /*param*/ )
其中粟害,event代表點擊事件蕴忆,x,y則代表點擊時鼠標指針的所在位置悲幅,引用方式為
namedWindow( "Demo", 1 );
setMouseCallback( "Demo", onMouse, 0 );
表示在某個窗口發(fā)生的點擊事件套鹅,點擊事件調(diào)用的函數(shù),以及傳給回調(diào)函數(shù)的參數(shù)夺艰,參數(shù)默認值為0芋哭。
當我們已經(jīng)在第一張圖片中固定到了特征點,那么接下來要做的就是在接下來的每幀圖片中找到 該特征點的位置了郁副。
在尋找特征值的位置的時候,則需要正式引入Lucas-Kanande金字塔的相關(guān)知識了豌习。
首先存谎,為了簡化追蹤的難度,LK光流法提出了以下三條假設(shè):
(1)亮度恒定肥隆,就是同一點隨著時間的變化既荚,其亮度不會發(fā)生改變。該假設(shè)用于得到光流法基本方程栋艳;
(2)小運動恰聘,這個也必須滿足,就是時間的變化不會引起位置的劇烈變化吸占,這樣灰度才能對位置求偏導(dǎo)晴叨。
(3)空間一致,一個場景上鄰近的點投影到圖像上也是鄰近點矾屯,且鄰近點速度一致兼蕊。這是Lucas-Kanade光流法特有的假定,因為光流法基本方程約束只有一個件蚕,而要求x孙技,y方向的速度产禾,有兩個未知變量。我們假定特征點鄰域內(nèi)做相似運動牵啦,就可以連立n多個方程求取x亚情,y方向的速度(n為特征點鄰域總點數(shù),包括該特征點)哈雏。
為了正確追蹤到角點势似,我們可以嘗試在原圖中角點的附近設(shè)置一個鄰域,由于是小運動僧著,那么兩幀圖片中的特征點位置變化也不會太大履因,我們只需要在鄰域中尋找符合原角點的亮度以及周圍環(huán)境信息的角點,確定其為運動后的角點即可盹愚。
Lucas-Kanade是一種廣泛使用的光流估計的差分方法栅迄,這個方法是由Bruce D. Lucas和Takeo Kanade發(fā)明的。它假設(shè)光流在像素點的鄰域是一個常數(shù)皆怕,然后使用最小二乘法對鄰域中的所有像素點求解基本的光流方程毅舆。
但是這是在假設(shè)完全成立的時候才正確的,一旦物體運動過快愈腾,那么會造成很大的誤差憋活,而且如果盲目擴大鄰域,算法的時間又是一個很大的問題虱黄,所以引出了金字塔分層悦即,針對仿射變換的改進Lucas-Kanade算法。
算法的關(guān)鍵內(nèi)容在于:當在鄰域內(nèi)尋找對應(yīng)特征點的時候橱乱,首先將原圖像進行縮小辜梳,縮小后的圖像相對的特征點的位移像素點就也會變小,可以先在縮小后的圖像中尋找匹配的特征值泳叠,尋找出位置之后再進行迭代作瞄,直到迭代到原圖像上的位置信息。
如圖所示危纫,最底層是分辨率最大的原圖像宗挥,向上依次是采樣1/2的低分辨率圖像,通常在使用當中金字塔的層數(shù)為3-4層即可种蝶。因為隨著圖像的移動契耿,算法可以應(yīng)對光流大于窗口尺寸的特征點跟蹤問題。
金字塔L-K光流通常用來估計圖像特征點的光流蛤吓,以提高圖像光流場的計算速度宵喂。
因此,使用Lucas-Kanande金字塔的步驟為:
首先会傲,計算金字塔最頂層圖像的光流锅棕,根據(jù)最頂層光流結(jié)果計算其次上層的光流初始值拙泽,再進一步估算其光流的精確值。最后裸燎,用計算的次上層光流結(jié)果估計下一層光流的初始值顾瞻,計算其精確的值后再繼續(xù)帶入下一層計算,直到金字塔的最底層德绿。
公式的推演可以查看:http://blog.csdn.net/zhe123zhe123zhe123/article/details/50397143
具體的實現(xiàn)步驟可以看代碼:
#include "opencv2/video/tracking.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <ctype.h>
using namespace cv;
using namespace std;
//--------------------------------【help( )函數(shù)】----------------------------------------------
// 描述:輸出幫助信息
//-------------------------------------------------------------------------------------------------
static void help()
{
//輸出歡迎信息和OpenCV版本
cout << "\n\n\t\t\t 當前使用的OpenCV版本為:" << CV_VERSION
<<"\n\n ----------------------------------------------------------------------------" ;
cout << "\n\n\t該Demo演示了 Lukas-Kanade基于光流的lkdemo\n";
cout << "\n\t程序默認從攝像頭讀入視頻荷荤,可以按需改為從視頻文件讀入圖像\n";
cout << "\n\t操作說明: \n"
"\t\t通過點擊在圖像中添加/刪除特征點\n"
"\t\tESC - 退出程序\n"
"\t\tr -自動進行追蹤\n"
"\t\tc - 刪除所有點\n"
"\t\tn - 開/光-夜晚模式\n"<< endl;
}
Point2f point;
bool addRemovePt = false;
//--------------------------------【onMouse( )回調(diào)函數(shù)】------------------------------------
// 描述:鼠標操作回調(diào)
//-------------------------------------------------------------------------------------------------
static void onMouse( int event, int x, int y, int /*flags*/, void* /*param*/ )
{
if( event == CV_EVENT_LBUTTONDOWN )
{
point = Point2f((float)x, (float)y);
addRemovePt = true;
}
}
//-----------------------------------【main( )函數(shù)】--------------------------------------------
// 描述:控制臺應(yīng)用程序的入口函數(shù),我們的程序從這里開始
//-------------------------------------------------------------------------------------------------
int main( int argc, char** argv )
{
help();
VideoCapture cap;
TermCriteria termcrit(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 20, 0.03);
Size subPixWinSize(10,10), winSize(31,31);
const int MAX_COUNT = 500;
bool needToInit = false;
bool nightMode = false;
cap.open(0);
if( !cap.isOpened() )
{
cout << "Could not initialize capturing...\n";
return 0;
}
namedWindow( "LK Demo", 1 );
setMouseCallback( "LK Demo", onMouse, 0 );
Mat gray, prevGray, image;
//數(shù)組為2*n移稳,每個元素存放的都是一個點信息
vector<Point2f> points[2];
Mat frame;
cap >> frame;
for(;;)
{
cap >> frame;
if( frame.empty() )
break;
frame.copyTo(image);
cvtColor(image, gray, COLOR_BGR2GRAY);
if( nightMode )
image = Scalar::all(0);
//如果需要自動尋找角點
if( needToInit )
{
// 自動初始化
//尋找角點蕴纳,將角點信息載入到points[1]數(shù)組中
//goodFeaturesToTrack函數(shù)的參數(shù)為:
//(原圖像,角點存放位置个粱,需要找到的最大角點數(shù)目古毛,品質(zhì)因子,可刪除角點范圍都许,感興趣區(qū)域稻薇,計算協(xié)方差矩陣時的窗口大小,是否使用Harris角點檢測胶征,Harris角點檢測K值)
goodFeaturesToTrack(gray, points[1], MAX_COUNT, 0.01, 10, Mat(), 3, 0, 0.04);
//將能夠?qū)⒔屈c位置精確到亞像素級精度塞椎。
// 輸入的源角點信息points[1],精度調(diào)整后角點信息依舊存放在points[1]中
cornerSubPix(gray, points[1], subPixWinSize, Size(-1,-1), termcrit);
addRemovePt = false;
}
else if( !points[0].empty() )
{
vector<uchar> status;
vector<float> err;
if(prevGray.empty())
gray.copyTo(prevGray);
//光流金字塔睛低,作用在于根據(jù)第一幀圖片的特征點所在位置找到另一幀圖片的特征點信息
//參數(shù)設(shè)置:
//1.第一幀圖像
//2.第二幀圖像(需要在這幅圖像上找到特征點改變后的位置)
//3.第一幀圖像中的特征點
//4.輸出在第二幀圖像中找到了對應(yīng)特征點的位置
//5.輸出特征向量案狠,每個特征點是否找到了改變后的位置,如果找到了暇昂,每個元素設(shè)置為1莺戒,沒有找到設(shè)置為0
//6.輸出出錯信息,誤差測量的類型在第10個參數(shù)中定義急波,如果沒有錯誤信息,則err為no defined
//7.在每個金字塔水平搜尋窗口的尺寸瘪校。
//8.最大的金字塔0層數(shù)澄暮;如果設(shè)置為0,不使用金字塔(單級)阱扬,如果設(shè)置為1泣懊,使用兩層,等等麻惶;如果金字塔是通過輸入算法將使用多層次的金字塔有但不超過 maxlevel馍刮。
//9.參數(shù)指定的迭代搜索算法終止準則
//10.參數(shù):optflow_use_initial_flow 使用初始估計,
//optflow_lk_get_min_eigenvals 使用最小特征值的誤差測量估計
//11.一個矩陣光流方程的特征值如果在窗口中小于該值窃蹋,則忽略不計
calcOpticalFlowPyrLK(prevGray, gray, points[0], points[1], status, err, winSize,
3, termcrit, 0, 0.001);
size_t i, k;
for( i = k = 0; i < points[1].size(); i++ )
{
if( addRemovePt )
{
//兩個像素點之間的距離
if( norm(point - points[1][i]) <= 5 )
{
addRemovePt = false;
continue;
}
}
if( !status[i] )
continue;
points[1][k++] = points[1][i];
circle( image, points[1][i], 3, Scalar(0,255,0), -1, 8);
}
points[1].resize(k);
}
if( addRemovePt && points[1].size() < (size_t)MAX_COUNT )
{
vector<Point2f> tmp;
tmp.push_back(point);
//輸入信息temp中卡啰,精度調(diào)整后角點信息存放在tmp中
cornerSubPix( gray, tmp, winSize, cvSize(-1,-1), termcrit);
points[1].push_back(tmp[0]);
addRemovePt = false;
}
needToInit = false;
imshow("LK Demo", image);
char c = (char)waitKey(10);
if( c == 27 )
break;
switch( c )
{
case 'r':
//自動尋找角點
needToInit = true;
break;
case 'c':
points[0].clear();
points[1].clear();
break;
case 'n':
nightMode = !nightMode;
break;
}
//交換兩個vector的所有元素
std::swap(points[1], points[0]);
cv::swap(prevGray, gray);
}
return 0;
}